{"version":3,"file":"create-offline-sync.cjs","names":[],"sources":["../../src/offline/create-offline-sync.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — outbox drain, pull-with-watermark,\n * conflict resolution and the single-flight guard are one loop by necessity: a pull\n * that lands mid-drain must not advance the watermark past records the outbox has\n * not pushed yet. Splitting the phases hands that ordering to whoever calls them in\n * the right sequence.\n */\nimport { randomId } from \"../utils/ids\";\nimport { createOfflineStore } from \"./create-offline-store\";\n\n/**\n * Mutation kinds queued in the outbox.\n *\n * `create`/`update` carry a snapshot of the record; `delete` needs only the\n * record id. The distinction between `create` and `update` is advisory — the\n * engine treats both as \"deliver this record\" and leaves the create-vs-update\n * decision to the app's `deliver` callback (a `PUT` upsert usually ignores it).\n */\nexport type OutboxOp = \"create\" | \"update\" | \"delete\";\n\n/**\n * A single queued mutation.\n *\n * @typeParam TPayload - The record snapshot shape carried by\n *   `create`/`update` entries (omitted for `delete`).\n */\nexport interface OutboxEntry<TPayload = unknown> {\n    /** Stable per-entry id (generated with {@link randomId}). */\n    id: string;\n    /** The mutation kind. */\n    op: OutboxOp;\n    /** Primary key of the record the mutation targets. */\n    recordId: string;\n    /**\n     * Epoch milliseconds when the mutation was queued, and the FIFO sort key for\n     * `listPending()` and the delivery loop. Strictly increasing per engine\n     * instance: entries queued inside the same millisecond are spaced by 1ms so\n     * the order never depends on how the index breaks a tie.\n     */\n    enqueuedAt: number;\n    /** How many delivery attempts have been made so far. */\n    attempts: number;\n    /** Last delivery error message, kept for UI/debug. */\n    lastError?: string;\n    /** Record snapshot for `create`/`update`. Omitted for `delete`. */\n    payload?: TPayload;\n}\n\n/**\n * Why a sync run was triggered. The listed values are the common ones; any\n * string is accepted so apps can add their own telemetry labels.\n */\nexport type SyncTrigger =\n    \"boot\" | \"online-event\" | \"after-mutation\" | \"manual\" | \"interval\" | (string & {});\n\n/**\n * One page of the server delta pull.\n *\n * @typeParam TRemote - The server-side item shape.\n */\nexport interface PullPage<TRemote> {\n    /** Items changed since the watermark, in this page. */\n    items: TRemote[];\n    /** Cursor for the next page, or `null` when this is the last page. */\n    nextCursor: string | null;\n    /**\n     * Server clock to persist as the next watermark once the whole delta is\n     * applied. `null` leaves the watermark unchanged.\n     */\n    serverTime: string | null;\n}\n\n/** Outcome of a single {@link OfflineSync.flush} run. */\nexport interface SyncRunSummary {\n    /** The trigger passed to `flush`. */\n    trigger: SyncTrigger;\n    /** Entries delivered to the server this run. */\n    succeeded: number;\n    /** Entries that failed and stay queued for the next run. */\n    failed: number;\n    /**\n     * Entries left untried because an earlier entry for the **same record**\n     * failed this run.\n     *\n     * They stay queued and are attempted next run, in order. Counted apart from\n     * `failed` because they were never sent: reporting them as failures would\n     * blame the server for a decision this engine made, and folding them in would\n     * make `succeeded + failed` stop accounting for the queue.\n     */\n    deferred: number;\n    /** Total wall-clock milliseconds the run took. */\n    durationMs: number;\n    /** `true` when the run was skipped because the device was offline. */\n    skipped: boolean;\n    /** Last delivery error message seen this run, or `null` when none failed. */\n    lastError: string | null;\n}\n\n/**\n * Coarse lifecycle phase of the sync engine, surfaced through\n * {@link OfflineSync.getState} / {@link OfflineSync.subscribe} for reactive UI.\n *\n * - `\"idle\"` — nothing in flight and the last run (if any) fully succeeded.\n * - `\"syncing\"` — a `flush` run is currently in progress.\n * - `\"offline\"` — the last run was skipped because the device was offline.\n * - `\"error\"` — the last run left at least one entry queued after a failure.\n */\nexport type SyncPhase = \"idle\" | \"syncing\" | \"offline\" | \"error\";\n\n/**\n * Immutable snapshot of the engine's reactive state. A fresh object is emitted\n * to subscribers on every change, so it is safe to use as a\n * `useSyncExternalStore` snapshot (reference equality signals \"no change\").\n */\nexport interface SyncState {\n    /** Current lifecycle phase. */\n    phase: SyncPhase;\n    /** Number of mutations still queued in the outbox. */\n    pending: number;\n    /** Summary of the most recent `flush` run, or `null` before the first. */\n    lastSummary: SyncRunSummary | null;\n    /** Message of the most recent delivery failure, or `null`. */\n    lastError: string | null;\n    /** Epoch ms of the last non-skipped run, or `null` before the first. */\n    lastSyncedAt: number | null;\n}\n\n/**\n * Pluggable persistence for the pull watermark (the \"changed since\" cursor).\n * Pass an object with a `storageKey` to use a `localStorage`-backed default.\n */\nexport interface WatermarkStore {\n    /** Read the current watermark, or `null` when none is stored. */\n    get: () => string | null;\n    /** Persist a new watermark. */\n    set: (value: string) => void;\n    /** Drop the watermark (e.g. on logout / account switch). */\n    clear: () => void;\n}\n\n/**\n * Configuration for {@link createOfflineSync}.\n *\n * The engine owns the outbox, the single-flight flush, the offline guard, the\n * paginated pull loop and the watermark; the three transport callbacks\n * (`deliver`, `pullPage`, `applyRemote`) are where the app plugs in its own\n * endpoints, record shape and conflict resolution.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n * @typeParam TRemote - Server item shape returned by `pullPage`.\n */\nexport interface OfflineSyncConfig<TPayload, TRemote> {\n    /** IndexedDB database name for the outbox (kept separate per queue). */\n    databaseName: string;\n    /** Outbox object-store name. Default `\"outbox\"`. */\n    tableName?: string;\n    /** Outbox schema version. Default `1`. */\n    version?: number;\n    /** Prefix for generated entry ids. Default `\"outbox\"`. */\n    idPrefix?: string;\n    /**\n     * Deliver one queued mutation to the server. Throwing keeps the entry\n     * queued (its `attempts`/`lastError` are bumped) for the next run.\n     */\n    deliver: (entry: OutboxEntry<TPayload>) => Promise<void>;\n    /** Fetch one page of the server delta since `since`, from `cursor`. */\n    pullPage: (since: string | null, cursor: string | null) => Promise<PullPage<TRemote>>;\n    /**\n     * Merge one pulled item into the local store. The app owns conflict\n     * resolution here (e.g. last-write-wins, keeping newer local pending\n     * edits, resolving tombstones and downloading blobs).\n     */\n    applyRemote: (item: TRemote) => Promise<void>;\n    /** Watermark persistence, or `{ storageKey }` for a `localStorage` default. */\n    watermark: WatermarkStore | { storageKey: string };\n    /** Called after an entry is delivered and acked. */\n    onEntryDelivered?: (entry: OutboxEntry<TPayload>) => void | Promise<void>;\n    /** Called after an entry fails delivery (it stays queued). */\n    onEntryFailed?: (entry: OutboxEntry<TPayload>, error: unknown) => void | Promise<void>;\n    /**\n     * Connectivity check. Default reads `navigator.onLine` (always online in\n     * non-browser environments). When it returns `false`, `flush` is skipped.\n     */\n    isOnline?: () => boolean;\n    /**\n     * Broadcast {@link SyncState} across browser tabs of the same origin via a\n     * `BroadcastChannel`, so every tab shows a coherent pending count / phase\n     * (e.g. one tab flushing drops the badge to zero everywhere). The outbox is\n     * already a shared IndexedDB; this only shares the in-memory state. Silently\n     * ignored where `BroadcastChannel` is unavailable. Default `false`.\n     *\n     * When the Web Locks API is available, `flush` is additionally coordinated\n     * *across* tabs: only one tab runs at a time and others skip while the lock\n     * is held (they pick up the result via the broadcast). Delivery should still\n     * be idempotent (upsert by client id). Without Web Locks, `flush` falls back\n     * to per-tab single-flight.\n     */\n    crossTab?: boolean;\n    /** Channel name when `crossTab` is on. Default `tempest-sync:${databaseName}`. */\n    broadcastChannelName?: string;\n}\n\n/**\n * Offline-first sync engine: a durable outbox plus a paginated delta pull.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n */\nexport interface OfflineSync<TPayload> {\n    /**\n     * Queue a mutation. Returns the generated entry id.\n     *\n     * @param op - The mutation kind.\n     * @param recordId - Primary key of the affected record.\n     * @param payload - Record snapshot (for `create`/`update`).\n     */\n    enqueue: (op: OutboxOp, recordId: string, payload?: TPayload) => Promise<string>;\n    /**\n     * Run a full sync (drain the outbox, then pull the delta). Concurrent\n     * calls share one in-flight promise, so triggers never overlap.\n     *\n     * @param trigger - Label for why the run happened. Default `\"manual\"`.\n     */\n    flush: (trigger?: SyncTrigger) => Promise<SyncRunSummary>;\n    /** Number of mutations still queued. */\n    pendingCount: () => Promise<number>;\n    /** The queued mutations in FIFO order. */\n    listPending: () => Promise<OutboxEntry<TPayload>[]>;\n    /** Drop every queued mutation (e.g. on logout). */\n    clearOutbox: () => Promise<void>;\n    /** Reset the pull watermark (e.g. on logout / account switch). */\n    resetWatermark: () => void;\n    /** Read the current reactive {@link SyncState} snapshot synchronously. */\n    getState: () => SyncState;\n    /**\n     * Subscribe to {@link SyncState} changes. The listener fires on every\n     * enqueue, flush transition and outbox clear.\n     *\n     * @param listener - Called with the new snapshot on each change.\n     * @returns An unsubscribe function.\n     */\n    subscribe: (listener: (state: SyncState) => void) => () => void;\n    /**\n     * Release resources held by the engine — currently the cross-tab\n     * `BroadcastChannel` when `crossTab` is enabled. Safe to call when none was\n     * opened. Optional in long-lived apps (the channel is reclaimed on tab\n     * close); call it in tests and on teardown.\n     */\n    dispose: () => void;\n}\n\n/**\n * Build a `localStorage`-backed {@link WatermarkStore}. Reads/writes are no-ops\n * when `localStorage` is unavailable (SSR / non-browser).\n *\n * @param key - The `localStorage` key.\n * @returns A watermark store persisting to `localStorage`.\n */\nfunction localStorageWatermark(key: string): WatermarkStore {\n    const available = typeof localStorage !== \"undefined\";\n    return {\n        get: () => (available ? localStorage.getItem(key) : null),\n        set: (value: string) => {\n            if (available) localStorage.setItem(key, value);\n        },\n        clear: () => {\n            if (available) localStorage.removeItem(key);\n        },\n    };\n}\n\n/**\n * Create an offline-first sync engine over an IndexedDB outbox.\n *\n * The engine drains queued mutations to the server (`deliver`) and pulls the\n * server delta back (`pullPage` + `applyRemote`), advancing a watermark so each\n * run only fetches what changed. `flush` is single-flight and skips cleanly\n * while offline; failed deliveries stay queued with their attempt count bumped.\n *\n * Dexie is an **optional peer dependency** (via {@link createOfflineStore}) —\n * install it (`npm i dexie`) when you use this engine.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n * @typeParam TRemote - Server item shape returned by `pullPage`.\n * @param config - Transport callbacks + outbox/watermark configuration.\n * @returns The sync handle (`enqueue`, `flush`, `pendingCount`, …).\n *\n * @example\n * const sync = createOfflineSync<Note, NoteDto>({\n *     databaseName: \"NotesOutbox\",\n *     watermark: { storageKey: \"notes.watermark\" },\n *     deliver: async (entry) => {\n *         if (entry.op === \"delete\") return api.remove(entry.recordId);\n *         await api.upsert(entry.recordId, entry.payload!);\n *     },\n *     pullPage: async (since, cursor) => {\n *         const page = await api.changes(since, cursor);\n *         return { items: page.items, nextCursor: page.next, serverTime: page.now };\n *     },\n *     applyRemote: async (dto) => {\n *         if (dto.deleted) return localStore.remove(dto.id);\n *         await localStore.save(fromDto(dto));\n *     },\n * });\n * await sync.enqueue(\"create\", note.id, note);\n * await sync.flush(\"after-mutation\");\n */\nexport function createOfflineSync<TPayload = unknown, TRemote = unknown>(\n    config: OfflineSyncConfig<TPayload, TRemote>,\n): OfflineSync<TPayload> {\n    const outbox = createOfflineStore<OutboxEntry<TPayload>, string>({\n        databaseName: config.databaseName,\n        version: config.version ?? 1,\n        tableName: config.tableName ?? \"outbox\",\n        indexes: \"&id, recordId, enqueuedAt\",\n        keyPath: \"id\",\n    });\n\n    const watermark: WatermarkStore =\n        \"get\" in config.watermark\n            ? config.watermark\n            : localStorageWatermark(config.watermark.storageKey);\n\n    const idPrefix = config.idPrefix ?? \"outbox\";\n    const isOnline =\n        config.isOnline ?? (() => (typeof navigator !== \"undefined\" ? navigator.onLine : true));\n\n    let inflight: Promise<SyncRunSummary> | null = null;\n\n    const listeners = new Set<(state: SyncState) => void>();\n    let state: SyncState = {\n        phase: \"idle\",\n        pending: 0,\n        lastSummary: null,\n        lastError: null,\n        lastSyncedAt: null,\n    };\n\n    const crossTab = config.crossTab === true;\n    const channelName = config.broadcastChannelName ?? `tempest-sync:${config.databaseName}`;\n    const channel: BroadcastChannel | null =\n        crossTab && typeof BroadcastChannel !== \"undefined\"\n            ? new BroadcastChannel(channelName)\n            : null;\n    let closed = false;\n\n    function notify(): void {\n        for (const listener of listeners) listener(state);\n    }\n\n    function setState(patch: Partial<SyncState>): void {\n        state = { ...state, ...patch };\n        notify();\n        if (channel && !closed) channel.postMessage(state);\n    }\n\n    if (channel) {\n        channel.onmessage = (event: MessageEvent<SyncState>) => {\n            if (closed) return;\n            state = event.data;\n            notify();\n        };\n    }\n\n    async function refreshPending(): Promise<void> {\n        setState({ pending: await outbox.count() });\n    }\n\n    /**\n     * Drain the outbox in `enqueuedAt` order, one record at a time.\n     *\n     * Once an entry for a record fails, the remaining entries **for that record**\n     * are left untried this run. `enqueuedAt` is a FIFO guarantee the engine goes\n     * out of its way to keep (see `nextEnqueuedAt`), precisely because delivering\n     * an `update` before the `create` it depends on is wrong — and continuing past\n     * a failure broke exactly that guarantee. With a `PUT` upsert `deliver`, which\n     * is the usual shape, the server would create the record from the *update*\n     * payload and the retried `create` would then overwrite it with the older\n     * snapshot: the user's edit disappears with nothing reported.\n     *\n     * Skipping is per record, not per run. Records are independent, so a record\n     * the server is rejecting must not hold up every other pending change.\n     *\n     * @param summary - Mutated with the per-entry outcome counts.\n     */\n    async function push(summary: SyncRunSummary): Promise<void> {\n        const entries = await outbox.list(undefined, { orderBy: \"enqueuedAt\" });\n        const blocked = new Set<string>();\n        for (const entry of entries) {\n            if (blocked.has(entry.recordId)) {\n                summary.deferred += 1;\n                continue;\n            }\n            try {\n                await config.deliver(entry);\n                await outbox.delete(entry.id);\n                await config.onEntryDelivered?.(entry);\n                summary.succeeded += 1;\n            } catch (cause) {\n                const message = cause instanceof Error ? cause.message : \"delivery failed\";\n                await outbox.update(entry.id, {\n                    attempts: entry.attempts + 1,\n                    lastError: message,\n                } as Partial<OutboxEntry<TPayload>>);\n                await config.onEntryFailed?.(entry, cause);\n                blocked.add(entry.recordId);\n                summary.failed += 1;\n                summary.lastError = message;\n            }\n        }\n    }\n\n    async function pull(): Promise<void> {\n        const since = watermark.get();\n        let cursor: string | null = null;\n        let serverTime: string | null;\n        do {\n            const page = await config.pullPage(since, cursor);\n            for (const item of page.items) {\n                await config.applyRemote(item);\n            }\n            cursor = page.nextCursor;\n            serverTime = page.serverTime;\n        } while (cursor);\n        if (serverTime) watermark.set(serverTime);\n    }\n\n    async function runOnce(trigger: SyncTrigger): Promise<SyncRunSummary> {\n        const startedAt = Date.now();\n        const summary: SyncRunSummary = {\n            trigger,\n            succeeded: 0,\n            failed: 0,\n            deferred: 0,\n            durationMs: 0,\n            skipped: false,\n            lastError: null,\n        };\n        if (!isOnline()) {\n            summary.skipped = true;\n            summary.durationMs = Date.now() - startedAt;\n            return summary;\n        }\n        await push(summary);\n        await pull();\n        summary.durationMs = Date.now() - startedAt;\n        return summary;\n    }\n\n    /**\n     * Run one sync pass, serialized across tabs when possible.\n     *\n     * With `crossTab` on and the Web Locks API available, the pass is wrapped in\n     * an `ifAvailable` lock request: whichever tab holds the lock runs, and the\n     * others get `null` back instead of duplicating the work — their `SyncState`\n     * catches up through the `BroadcastChannel`. The no-op summary keeps\n     * `skipped: false` on purpose: `skipped` means \"device offline\" (it maps to\n     * `phase: \"offline\"`), which is not what happened here. Falls back to a plain\n     * per-tab run when `crossTab` is off or Web Locks is missing.\n     *\n     * @param trigger - What asked for this run, carried into the summary.\n     * @returns The run summary, or an empty summary when another tab held the lock.\n     */\n    async function runGuarded(trigger: SyncTrigger): Promise<SyncRunSummary> {\n        const locks = typeof navigator !== \"undefined\" ? navigator.locks : undefined;\n        if (!crossTab || !locks) return runOnce(trigger);\n        const result = await locks.request(\n            `${channelName}:flush`,\n            { ifAvailable: true },\n            async (lock) => (lock ? runOnce(trigger) : null),\n        );\n        return (\n            result ?? {\n                trigger,\n                succeeded: 0,\n                failed: 0,\n                deferred: 0,\n                durationMs: 0,\n                skipped: false,\n                lastError: null,\n            }\n        );\n    }\n\n    void refreshPending();\n\n    let lastEnqueuedAt = 0;\n\n    /**\n     * Timestamp for a new outbox entry, guaranteed to be strictly greater than\n     * the previous one.\n     *\n     * `enqueuedAt` is the sort key for both `listPending()` and the delivery loop\n     * in `flush()`. A plain `Date.now()` ties whenever two mutations are queued\n     * within the same millisecond — which is normal in a burst — and a tie leaves\n     * the order to the index, so a `create` could be delivered *after* the\n     * `update` that depends on it. Advancing by 1ms on a tie keeps the queue FIFO\n     * at the cost of a timestamp that can run a few milliseconds ahead of the wall\n     * clock during a burst.\n     *\n     * Scope: this is per engine instance. Two tabs enqueueing in the same\n     * millisecond can still tie; cross-tab FIFO would need a persisted sequence,\n     * and the delivery loop is already single-flight across tabs via Web Locks.\n     */\n    function nextEnqueuedAt(): number {\n        const now = Date.now();\n        lastEnqueuedAt = now > lastEnqueuedAt ? now : lastEnqueuedAt + 1;\n        return lastEnqueuedAt;\n    }\n\n    return {\n        async enqueue(op, recordId, payload) {\n            const entry: OutboxEntry<TPayload> = {\n                id: randomId(idPrefix),\n                op,\n                recordId,\n                enqueuedAt: nextEnqueuedAt(),\n                attempts: 0,\n                payload,\n            };\n            await outbox.put(entry);\n            await refreshPending();\n            return entry.id;\n        },\n        flush(trigger: SyncTrigger = \"manual\") {\n            if (!inflight) {\n                setState({ phase: \"syncing\" });\n                inflight = runGuarded(trigger)\n                    .then(async (summary) => {\n                        await refreshPending();\n                        setState({\n                            phase: summary.skipped\n                                ? \"offline\"\n                                : summary.failed > 0\n                                  ? \"error\"\n                                  : \"idle\",\n                            lastSummary: summary,\n                            lastError: summary.lastError,\n                            lastSyncedAt: summary.skipped ? state.lastSyncedAt : Date.now(),\n                        });\n                        return summary;\n                    })\n                    .finally(() => {\n                        inflight = null;\n                    });\n            }\n            return inflight;\n        },\n        pendingCount() {\n            return outbox.count();\n        },\n        listPending() {\n            return outbox.list(undefined, { orderBy: \"enqueuedAt\" });\n        },\n        async clearOutbox() {\n            await outbox.clear();\n            await refreshPending();\n        },\n        resetWatermark() {\n            watermark.clear();\n        },\n        getState() {\n            return state;\n        },\n        subscribe(listener) {\n            listeners.add(listener);\n            return () => {\n                listeners.delete(listener);\n            };\n        },\n        dispose() {\n            closed = true;\n            channel?.close();\n        },\n    };\n}\n"],"mappings":"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"}