@markwasfy/loko 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/index.cjs +853 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +367 -0
- package/dist/index.d.ts +367 -0
- package/dist/index.js +840 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/background.ts","../src/collection.ts","../src/conflict/lww.ts","../src/emitter.ts","../src/tabs.ts","../src/types.ts","../src/sync.ts","../src/storage/indexeddb.ts","../src/storage/memory.ts","../src/adapters/rest.ts","../src/adapters/memory.ts"],"names":["key","database"],"mappings":"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","file":"index.cjs","sourcesContent":["export interface BackgroundSyncOptions {\n /** Polling interval in ms. `0` disables periodic sync. */\n intervalMs: number;\n /** Fired when the network comes back online. */\n onOnline: () => void;\n /** Fired when the network goes offline. */\n onOffline: () => void;\n /** Periodic tick (only fires when {@link shouldTick} returns true). */\n tick: () => void;\n /** Gate for periodic ticks — used to restrict polling to the leader tab. */\n shouldTick: () => boolean;\n}\n\n/**\n * Wires offline/online detection and optional interval polling. Safe to\n * construct in non-browser environments (it simply does nothing there).\n */\nexport class BackgroundSync {\n private timer: ReturnType<typeof setInterval> | undefined;\n private readonly onlineHandler = () => this.opts.onOnline();\n private readonly offlineHandler = () => this.opts.onOffline();\n\n constructor(private readonly opts: BackgroundSyncOptions) {}\n\n start(): void {\n if (typeof window !== \"undefined\" && typeof window.addEventListener === \"function\") {\n window.addEventListener(\"online\", this.onlineHandler);\n window.addEventListener(\"offline\", this.offlineHandler);\n }\n if (this.opts.intervalMs > 0) {\n this.timer = setInterval(() => {\n if (this.opts.shouldTick()) this.opts.tick();\n }, this.opts.intervalMs);\n // Don't keep a Node process alive purely for polling.\n (this.timer as { unref?: () => void }).unref?.();\n }\n }\n\n /** Current network status, defaulting to online where unknown. */\n isOnline(): boolean {\n const nav = globalThis.navigator as Navigator | undefined;\n return nav ? nav.onLine : true;\n }\n\n stop(): void {\n if (typeof window !== \"undefined\" && typeof window.removeEventListener === \"function\") {\n window.removeEventListener(\"online\", this.onlineHandler);\n window.removeEventListener(\"offline\", this.offlineHandler);\n }\n if (this.timer !== undefined) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n }\n}\n\n/**\n * Opt-in helper to register a Service Worker Background Sync trigger, for true\n * background sync when the page is closed. The core works fully without this;\n * your service worker must listen for the `sync` event with the same tag and\n * message clients to call `sync.sync()`.\n */\nexport async function registerBackgroundSync(tag = \"loko-sync\"): Promise<boolean> {\n try {\n const nav = globalThis.navigator as (Navigator & { serviceWorker?: ServiceWorkerContainer }) | undefined;\n if (!nav?.serviceWorker) return false;\n const reg = (await nav.serviceWorker.ready) as ServiceWorkerRegistration & {\n sync?: { register(tag: string): Promise<void> };\n };\n if (!reg.sync) return false;\n await reg.sync.register(tag);\n return true;\n } catch {\n return false;\n }\n}\n","import type { CollectionOptions, StorageAdapter, StoredDoc } from \"./types.js\";\n\n/** Internal wiring handed to each {@link Collection} by the owning Sync instance. */\nexport interface CollectionContext {\n storage: StorageAdapter;\n primaryKey: string;\n indexes: string[];\n /** Resolves once storage is initialized; awaited before every storage access. */\n ready: Promise<void>;\n /**\n * Called after a local write. The Sync engine emits a `change` event,\n * broadcasts to other tabs, and schedules autoSync.\n */\n onLocalWrite(doc: StoredDoc): void;\n}\n\n/**\n * A typed view over one collection of records. Reads/writes the user's payload\n * `T` and tracks sync metadata under the hood. `T` must carry the primary key\n * field (default `\"id\"`).\n */\nexport class Collection<T extends Record<string, unknown> = Record<string, unknown>> {\n readonly name: string;\n private readonly ctx: CollectionContext;\n private readonly subscribers = new Set<(items: T[]) => void>();\n private readonly oneSubscribers = new Map<string, Set<(item: T | undefined) => void>>();\n private refreshScheduled = false;\n\n constructor(name: string, ctx: CollectionContext) {\n this.name = name;\n this.ctx = ctx;\n }\n\n private idOf(record: T): string {\n const id = record[this.ctx.primaryKey];\n if (id === undefined || id === null || id === \"\") {\n throw new Error(\n `Collection \"${this.name}\": record is missing primary key \"${this.ctx.primaryKey}\".`,\n );\n }\n return String(id);\n }\n\n /** Read a single live record by id (tombstones return `undefined`). */\n async get(id: string): Promise<T | undefined> {\n await this.ctx.ready;\n const doc = await this.ctx.storage.get(this.name, id);\n if (!doc || doc.deletedAt !== null) return undefined;\n return doc.data as T;\n }\n\n /** All live records in the collection (tombstones excluded). */\n async all(): Promise<T[]> {\n await this.ctx.ready;\n const docs = await this.ctx.storage.getAll(this.name);\n return docs.filter((d) => d.deletedAt === null).map((d) => d.data as T);\n }\n\n /** Insert or update a record. Returns the stored record. */\n async put(record: T): Promise<T> {\n await this.ctx.ready;\n const id = this.idOf(record);\n const existing = await this.ctx.storage.get(this.name, id);\n const doc = this.nextDoc(id, record, false, existing);\n await this.ctx.storage.put(doc);\n this.ctx.onLocalWrite(doc);\n return record;\n }\n\n /** Insert or update many records atomically (one transaction). */\n async bulkPut(records: T[]): Promise<void> {\n if (records.length === 0) return;\n await this.ctx.ready;\n // Read existing metadata first, then stage all writes in one transaction.\n const docs = await Promise.all(\n records.map(async (record) => {\n const id = this.idOf(record);\n const existing = await this.ctx.storage.get(this.name, id);\n return this.nextDoc(id, record, false, existing);\n }),\n );\n await this.ctx.storage.transact((tx) => {\n for (const doc of docs) tx.put(doc);\n });\n for (const doc of docs) this.ctx.onLocalWrite(doc);\n }\n\n /** Soft-delete a record (writes a tombstone so the deletion syncs). */\n async delete(id: string): Promise<void> {\n await this.ctx.ready;\n const existing = await this.ctx.storage.get(this.name, id);\n if (!existing || existing.deletedAt !== null) return;\n const doc: StoredDoc = {\n ...existing,\n data: existing.data,\n deletedAt: Date.now(),\n updatedAt: Date.now(),\n localRev: existing.localRev + 1,\n };\n await this.ctx.storage.put(doc);\n this.ctx.onLocalWrite(doc);\n }\n\n /** Build the next StoredDoc for a local write, bumping `localRev`. */\n private nextDoc(id: string, record: T, deleted: boolean, existing?: StoredDoc): StoredDoc<T> {\n const now = Date.now();\n return {\n id,\n collection: this.name,\n data: record,\n updatedAt: now,\n deletedAt: deleted ? now : null,\n localRev: (existing?.localRev ?? 0) + 1,\n lastPushedLocalRev: existing?.lastPushedLocalRev ?? 0,\n remoteVersion: existing?.remoteVersion ?? 0,\n syncedAt: existing?.syncedAt ?? null,\n };\n }\n\n /**\n * Subscribe to the whole collection. The callback fires immediately with the\n * current records, then on every change (local, sync, or other tab).\n * Returns an unsubscribe function.\n */\n subscribe(cb: (items: T[]) => void): () => void {\n this.subscribers.add(cb);\n void this.all().then(cb);\n return () => {\n this.subscribers.delete(cb);\n };\n }\n\n /** Subscribe to a single record by id. */\n subscribeOne(id: string, cb: (item: T | undefined) => void): () => void {\n let set = this.oneSubscribers.get(id);\n if (!set) {\n set = new Set();\n this.oneSubscribers.set(id, set);\n }\n set.add(cb);\n void this.get(id).then(cb);\n return () => {\n const s = this.oneSubscribers.get(id);\n s?.delete(cb);\n if (s && s.size === 0) this.oneSubscribers.delete(id);\n };\n }\n\n /**\n * Notify subscribers that this collection changed. Coalesces bursts (e.g.\n * bulkPut, a sync batch) into a single refresh per microtask. Called by the\n * Sync engine for local writes, pulled changes, and cross-tab updates.\n */\n notify(): void {\n if (this.refreshScheduled) return;\n this.refreshScheduled = true;\n queueMicrotask(() => {\n this.refreshScheduled = false;\n if (this.subscribers.size > 0) {\n void this.all().then((items) => {\n for (const cb of [...this.subscribers]) cb(items);\n });\n }\n for (const [id, set] of this.oneSubscribers) {\n void this.get(id).then((item) => {\n for (const cb of [...set]) cb(item);\n });\n }\n });\n }\n}\n","import type { Change, ConflictResolver, StoredDoc } from \"../types.js\";\n\n/**\n * Build a clean local record by fully adopting a remote change. Used for\n * fast-forward (non-conflicting) updates and when the remote wins a conflict.\n * The resulting record is NOT dirty (`localRev === lastPushedLocalRev`).\n */\nexport function docFromRemote<T>(remote: Change<T>, now = Date.now()): StoredDoc<T> {\n return {\n id: remote.id,\n collection: remote.collection,\n // For a deletion the payload is null; the tombstone (`deletedAt`) carries meaning.\n data: remote.data as T,\n updatedAt: remote.updatedAt,\n deletedAt: remote.deleted ? now : null,\n localRev: 0,\n lastPushedLocalRev: 0,\n remoteVersion: remote.version,\n syncedAt: now,\n };\n}\n\n/**\n * Default conflict strategy: **last write wins** by wall-clock `updatedAt`, with\n * the already-committed remote winning ties. Conflict resolution is per-record:\n * the winning record replaces the loser wholesale (no field merge).\n *\n * - Remote wins -> adopt remote (record becomes clean).\n * - Local wins -> keep local data, rebased onto the remote's version, and left\n * dirty so it re-pushes and overwrites the server on the next sync.\n */\nexport function lastWriteWins(): ConflictResolver {\n return <T>(local: StoredDoc<T> | undefined, remote: Change<T>): StoredDoc<T> => {\n const now = Date.now();\n if (!local) return docFromRemote(remote, now);\n\n const remoteWins = remote.updatedAt >= local.updatedAt;\n if (remoteWins) return docFromRemote(remote, now);\n\n // Local wins: keep local data/edits, rebase onto the remote version so the\n // next push isn't rejected as stale. Stays dirty (localRev unchanged).\n return { ...local, remoteVersion: remote.version };\n };\n}\n","/** Minimal typed event emitter. Zero dependencies. */\nexport class Emitter<Events> {\n private listeners: { [K in keyof Events]?: Set<(payload: Events[K]) => void> } = {};\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof Events>(event: K, fn: (payload: Events[K]) => void): () => void {\n let set = this.listeners[event];\n if (!set) {\n set = new Set();\n this.listeners[event] = set;\n }\n set.add(fn);\n return () => {\n set?.delete(fn);\n };\n }\n\n /** Subscribe to an event for a single emission. */\n once<K extends keyof Events>(event: K, fn: (payload: Events[K]) => void): () => void {\n const off = this.on(event, (payload) => {\n off();\n fn(payload);\n });\n return off;\n }\n\n /** Emit an event to all current listeners. Listener errors are isolated. */\n emit<K extends keyof Events>(event: K, payload: Events[K]): void {\n const set = this.listeners[event];\n if (!set) return;\n // Copy so listeners can unsubscribe during emission without skipping others.\n for (const fn of [...set]) {\n try {\n fn(payload);\n } catch {\n // A misbehaving listener must not break the emit loop.\n }\n }\n }\n\n /** Remove all listeners (optionally for a single event). */\n clear<K extends keyof Events>(event?: K): void {\n if (event === undefined) {\n this.listeners = {};\n } else {\n delete this.listeners[event];\n }\n }\n}\n","/** Cross-tab messages relayed over BroadcastChannel. */\nexport type TabMessage =\n | { type: \"write\"; collection: string; id: string }\n | { type: \"sync\" };\n\n/**\n * Coordinates multiple tabs of the same origin:\n * - relays local writes / sync completions over a {@link BroadcastChannel} so\n * every tab refreshes its subscribers from the shared IndexedDB;\n * - elects a single leader (via the Web Locks API) so only one tab runs\n * background/interval sync, avoiding redundant network calls.\n *\n * Degrades gracefully where the APIs are unavailable (Node/SSR/old browsers):\n * messaging becomes a no-op and every tab is treated as a leader.\n */\nexport class TabCoordinator {\n private channel: BroadcastChannel | undefined;\n private leader = false;\n\n constructor(\n private readonly name: string,\n onMessage: (msg: TabMessage) => void,\n ) {\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(\"loko:\" + name);\n this.channel.onmessage = (e: MessageEvent<TabMessage>) => onMessage(e.data);\n }\n }\n\n broadcast(msg: TabMessage): void {\n this.channel?.postMessage(msg);\n }\n\n /** `true` once this tab has won leadership (or when no Web Locks API exists). */\n get isLeader(): boolean {\n return this.leader;\n }\n\n /**\n * Acquire leadership. The exclusive lock is held until the tab closes; when it\n * does, another tab automatically takes over. Resolves once leadership is won.\n */\n electLeader(onBecomeLeader?: () => void): void {\n const locks = (globalThis.navigator as Navigator | undefined)?.locks;\n if (!locks) {\n // No Web Locks: treat this tab as a leader so background sync still runs.\n this.leader = true;\n onBecomeLeader?.();\n return;\n }\n void locks.request(\"loko-leader:\" + this.name, () => {\n this.leader = true;\n onBecomeLeader?.();\n // Hold the lock for the lifetime of the tab.\n return new Promise<void>(() => {});\n });\n }\n\n close(): void {\n this.channel?.close();\n this.channel = undefined;\n }\n}\n","/**\n * Core type definitions for loko.\n *\n * Design notes:\n * - Server-assigned `version` is the authority for conflict resolution and delta\n * sync, NOT client wall-clock time. Device clocks drift, so `updatedAt` is kept\n * purely for display and as a hint for the default resolver.\n * - `dirty` is never stored: it is derived as `localRev > lastPushedLocalRev`.\n */\n\n/** A record stored locally, wrapping the user's data `T` with sync metadata. */\nexport interface StoredDoc<T = unknown> {\n /** Primary key within the collection. */\n id: string;\n /** Collection name this record belongs to. */\n collection: string;\n /** The user's payload. `null` only ever paired with a tombstone (`deletedAt`). */\n data: T;\n /** Wall-clock ms of the last local edit. Display/hint only — never the sync authority. */\n updatedAt: number;\n /** Tombstone marker (soft delete) so deletions propagate. `null` = live record. */\n deletedAt: number | null;\n /** Bumped on every local edit. */\n localRev: number;\n /** Value of `localRev` at the last successful push ack. */\n lastPushedLocalRev: number;\n /** Server-assigned version this record was last synced to/from (0 = never synced). */\n remoteVersion: number;\n /** Wall-clock ms of the last successful sync touching this record. */\n syncedAt: number | null;\n}\n\n/** `true` when a record has local edits not yet acknowledged by the server. */\nexport function isDirty(doc: Pick<StoredDoc, \"localRev\" | \"lastPushedLocalRev\">): boolean {\n return doc.localRev > doc.lastPushedLocalRev;\n}\n\n/** A change transmitted over the wire (push/pull). */\nexport interface Change<T = unknown> {\n collection: string;\n id: string;\n /** Payload, or `null` for a deletion. */\n data: T | null;\n /** Server-assigned version. For outgoing (push) changes this is the client's\n * last-known `remoteVersion` (the base the edit was made against). */\n version: number;\n /** Display value / resolver hint only. */\n updatedAt: number;\n /** Whether this change is a deletion. */\n deleted: boolean;\n}\n\n/** Opaque, monotonically increasing server sequence used as a pull cursor. */\nexport type Cursor = string | number;\n\n/** Result of pushing local changes to the server. */\nexport interface PushResult {\n /** Records the server accepted, with their newly committed versions. */\n acked: Array<{ id: string; collection: string; version: number }>;\n /**\n * Records the server rejected because it was already ahead (e.g. client sent\n * v5 but server has v6). Surfaced at push time so conflicts aren't only\n * discovered on the next pull. May be omitted/empty.\n */\n conflicts?: Array<Change>;\n /** Server sequence after this push; lets the client advance its pull cursor. */\n cursor: Cursor;\n}\n\n/** Result of pulling remote changes since a cursor. */\nexport interface PullResult {\n changes: Array<Change>;\n cursor: Cursor;\n}\n\n/**\n * Transport between the local store and a backend. Bring your own — implement\n * this against REST, Supabase, Firebase, WebSockets, anything.\n */\nexport interface SyncAdapter {\n push(changes: Change[]): Promise<PushResult>;\n pull(since: Cursor): Promise<PullResult>;\n}\n\n/** A handle for atomic multi-record writes within {@link StorageAdapter.transact}. */\nexport interface StorageTx {\n put(doc: StoredDoc): void;\n delete(collection: string, id: string): void;\n setMeta(key: string, value: unknown): void;\n}\n\n/**\n * Pluggable persistence layer. Ships with `indexedDBStorage()` (browser default)\n * and `memoryStorage()` (tests/Node/SSR).\n */\nexport interface StorageAdapter {\n /** Open/prepare the store for the given database name. */\n init(name: string): Promise<void>;\n get(collection: string, id: string): Promise<StoredDoc | undefined>;\n /** All records in a collection, including tombstones. */\n getAll(collection: string): Promise<StoredDoc[]>;\n put(doc: StoredDoc): Promise<void>;\n bulkPut(docs: StoredDoc[]): Promise<void>;\n /**\n * Run `fn` as an atomic, all-or-nothing transaction. Underpins `bulkPut`\n * today and `sync.transaction()` (reserved) tomorrow. A throw rolls back.\n */\n transact(fn: (tx: StorageTx) => void | Promise<void>): Promise<void>;\n /** All records with unsynced local edits, across every collection. */\n getDirty(): Promise<StoredDoc[]>;\n getMeta<V = unknown>(key: string): Promise<V | undefined>;\n setMeta(key: string, value: unknown): Promise<void>;\n /** Release resources (close DB, channels). */\n close(): Promise<void>;\n}\n\n/**\n * Decides the winner when a local edit and a remote change collide. The default\n * is {@link lastWriteWins}. Conflict resolution is per-record, not per-field:\n * to merge fields, return a record built from both sides.\n */\nexport type ConflictResolver = <T = unknown>(\n local: StoredDoc<T> | undefined,\n remote: Change<T>,\n) => StoredDoc<T>;\n\n/** Options declared once per collection via {@link Sync.defineCollection}. */\nexport interface CollectionOptions {\n /** Field on the record used as the id. Defaults to `\"id\"`. */\n primaryKey?: string;\n /**\n * Indexed fields. Accepted and validated in v1 but not yet used for querying\n * (reserved for indexed reads on the roadmap).\n */\n indexes?: string[];\n}\n\nexport type SyncStatus = \"idle\" | \"syncing\" | \"offline\" | \"error\";\n\n/** Map of event name -> payload for the typed emitter. */\nexport interface SyncEvents {\n /** A record changed locally or via sync. */\n change: { collection: string; id: string; doc: StoredDoc | undefined };\n /** A full sync cycle completed. */\n sync: { pushed: number; pulled: number };\n /** Sync status transitioned. */\n status: { status: SyncStatus };\n /** Something went wrong (sync, storage, transport). */\n error: { error: unknown };\n}\n\nexport interface SyncConfig {\n /** Logical database name; also namespaces the multi-tab BroadcastChannel. */\n name: string;\n /** Where records live. Defaults to `indexedDBStorage()` in the browser. */\n storage: StorageAdapter;\n /** How to talk to the backend. Optional — omit for a purely local store. */\n adapter?: SyncAdapter;\n /** Conflict strategy. Defaults to {@link lastWriteWins}. */\n conflict?: ConflictResolver;\n /**\n * When `true` (default), sync on reconnect and on an interval (leader tab only).\n * Pass a number to override the polling interval in ms (0 disables polling).\n */\n autoSync?: boolean | number;\n}\n","import { BackgroundSync, registerBackgroundSync } from \"./background.js\";\nimport { Collection, type CollectionContext } from \"./collection.js\";\nimport { docFromRemote, lastWriteWins } from \"./conflict/lww.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { TabCoordinator, type TabMessage } from \"./tabs.js\";\nimport {\n type Change,\n type CollectionOptions,\n type ConflictResolver,\n isDirty,\n type StoredDoc,\n type SyncConfig,\n type SyncEvents,\n type SyncStatus,\n} from \"./types.js\";\n\nconst CURSOR_KEY = \"__cursor\";\nconst CLIENT_ID_KEY = \"__clientId\";\n/** Reserved collection backing the `store()` / `get()` key-value sugar. */\nconst KV_COLLECTION = \"_kv\";\n\nfunction docToChange(doc: StoredDoc): Change {\n const deleted = doc.deletedAt !== null;\n return {\n collection: doc.collection,\n id: doc.id,\n data: deleted ? null : doc.data,\n version: doc.remoteVersion,\n updatedAt: doc.updatedAt,\n deleted,\n };\n}\n\nexport class Sync {\n private readonly storage: SyncConfig[\"storage\"];\n private readonly adapter: SyncConfig[\"adapter\"];\n private readonly resolver: ConflictResolver;\n private readonly emitter = new Emitter<SyncEvents>();\n private readonly collections = new Map<string, Collection<never>>();\n private readonly options = new Map<string, Required<CollectionOptions>>();\n private readonly tabs: TabCoordinator;\n private readonly background: BackgroundSync;\n private readonly autoSync: boolean;\n private readonly ready: Promise<void>;\n\n private _status: SyncStatus = \"idle\";\n private inFlight: Promise<{ pushed: number; pulled: number }> | undefined;\n private clientId = \"\";\n\n constructor(config: SyncConfig) {\n this.storage = config.storage;\n this.adapter = config.adapter;\n this.resolver = config.conflict ?? lastWriteWins();\n this.autoSync = config.autoSync !== false && config.adapter !== undefined;\n\n const intervalMs =\n typeof config.autoSync === \"number\"\n ? config.autoSync\n : this.autoSync\n ? 30_000\n : 0;\n\n this.tabs = new TabCoordinator(config.name, (msg) => this.onTabMessage(msg));\n this.background = new BackgroundSync({\n intervalMs,\n onOnline: () => {\n this.setStatus(\"idle\");\n if (this.autoSync) void this.sync().catch(() => {});\n },\n onOffline: () => this.setStatus(\"offline\"),\n tick: () => void this.sync().catch(() => {}),\n // Only the leader tab polls.\n shouldTick: () => this.tabs.isLeader && this.background.isOnline(),\n });\n\n this.ready = this.init(config.name);\n }\n\n private async init(name: string): Promise<void> {\n await this.storage.init(name);\n let id = await this.storage.getMeta<string>(CLIENT_ID_KEY);\n if (!id) {\n id =\n globalThis.crypto?.randomUUID?.() ??\n Math.random().toString(36).slice(2) + Date.now().toString(36);\n await this.storage.setMeta(CLIENT_ID_KEY, id);\n }\n this.clientId = id;\n if (!this.background.isOnline()) this.setStatus(\"offline\");\n this.tabs.electLeader();\n this.background.start();\n }\n\n // ── Collections ────────────────────────────────────────────────────────────\n\n /** Declare a collection's options once (at init). Returns the collection. */\n defineCollection<T extends Record<string, unknown>>(\n name: string,\n options: CollectionOptions = {},\n ): Collection<T> {\n const resolved: Required<CollectionOptions> = {\n primaryKey: options.primaryKey ?? \"id\",\n indexes: options.indexes ?? [],\n };\n this.options.set(name, resolved);\n return this.getOrCreate<T>(name, resolved);\n }\n\n /** Access a collection. Auto-defines with defaults if not yet declared. */\n collection<T extends Record<string, unknown>>(name: string): Collection<T> {\n const opts = this.options.get(name);\n return this.getOrCreate<T>(\n name,\n opts ?? { primaryKey: \"id\", indexes: [] },\n );\n }\n\n private getOrCreate<T extends Record<string, unknown>>(\n name: string,\n opts: Required<CollectionOptions>,\n ): Collection<T> {\n const existing = this.collections.get(name);\n if (existing) return existing as unknown as Collection<T>;\n const ctx: CollectionContext = {\n storage: this.storage,\n primaryKey: opts.primaryKey,\n indexes: opts.indexes,\n ready: this.ready,\n onLocalWrite: (doc) => this.handleLocalWrite(doc),\n };\n const collection = new Collection<T>(name, ctx);\n this.collections.set(name, collection as unknown as Collection<never>);\n return collection;\n }\n\n // ── Key/value sugar (over the reserved `_kv` collection) ─────────────────────\n\n /**\n * Convenience for simple key -> value cases. Stored as a SINGLE record in the\n * reserved `_kv` collection, so it syncs as one coarse record. Not smart\n * per-item sync — use real collections (`collection().put`) for that.\n */\n async store<V>(key: string, value: V): Promise<void> {\n await this.ready;\n await this.collection<{ id: string; value: V }>(KV_COLLECTION).put({ id: key, value });\n }\n\n /** Read a value previously written with {@link store}. */\n async get<V>(key: string): Promise<V | undefined> {\n await this.ready;\n const rec = await this.collection<{ id: string; value: V }>(KV_COLLECTION).get(key);\n return rec?.value;\n }\n\n // ── Sync engine ──────────────────────────────────────────────────────────────\n\n get status(): SyncStatus {\n return this._status;\n }\n\n /** Resolves once storage is initialized (clientId loaded, leader elected). */\n whenReady(): Promise<void> {\n return this.ready;\n }\n\n /** Subscribe to a lifecycle event. Returns an unsubscribe function. */\n on<K extends keyof SyncEvents>(event: K, fn: (payload: SyncEvents[K]) => void): () => void {\n return this.emitter.on(event, fn);\n }\n\n /**\n * Run a full sync cycle: push local changes, then pull remote ones. Concurrent\n * calls share the same in-flight run. No-op when no adapter is configured.\n */\n async sync(): Promise<{ pushed: number; pulled: number }> {\n await this.ready;\n if (!this.adapter) return { pushed: 0, pulled: 0 };\n if (this.inFlight) return this.inFlight;\n\n this.inFlight = (async () => {\n this.setStatus(\"syncing\");\n try {\n const pushed = await this.push();\n const pulled = await this.pull();\n this.setStatus(this.background.isOnline() ? \"idle\" : \"offline\");\n this.emitter.emit(\"sync\", { pushed, pulled });\n return { pushed, pulled };\n } catch (error) {\n this.setStatus(\"error\");\n this.emitter.emit(\"error\", { error });\n throw error;\n } finally {\n this.inFlight = undefined;\n }\n })();\n\n return this.inFlight;\n }\n\n private async push(): Promise<number> {\n const dirty = await this.storage.getDirty();\n if (dirty.length === 0) return 0;\n\n const pushedRev = new Map<string, number>();\n for (const doc of dirty) pushedRev.set(doc.collection + \" \" + doc.id, doc.localRev);\n\n const result = await this.adapter!.push(dirty.map(docToChange));\n\n const affected = new Set<string>();\n // Mark acked records clean (up to the localRev we actually pushed).\n for (const ack of result.acked) {\n const current = await this.storage.get(ack.collection, ack.id);\n if (!current) continue;\n const rev = pushedRev.get(ack.collection + \" \" + ack.id) ?? current.localRev;\n await this.storage.put({\n ...current,\n remoteVersion: ack.version,\n // If the record was edited again mid-push it stays dirty.\n lastPushedLocalRev: Math.max(current.lastPushedLocalRev, rev),\n syncedAt: Date.now(),\n });\n affected.add(ack.collection);\n }\n // Server was already ahead: resolve like any incoming change.\n for (const conflict of result.conflicts ?? []) {\n await this.applyRemote(conflict);\n affected.add(conflict.collection);\n }\n\n this.notifyAndBroadcast(affected, true);\n return result.acked.length;\n }\n\n private async pull(): Promise<number> {\n const cursor = (await this.storage.getMeta<string | number>(CURSOR_KEY)) ?? 0;\n const result = await this.adapter!.pull(cursor);\n\n const affected = new Set<string>();\n // Resolve all changes (reads happen here), then commit writes + cursor atomically.\n const writes: StoredDoc[] = [];\n for (const change of result.changes) {\n const resolved = await this.resolveRemote(change);\n if (resolved) {\n writes.push(resolved);\n affected.add(change.collection);\n }\n }\n await this.storage.transact((tx) => {\n for (const doc of writes) tx.put(doc);\n tx.setMeta(CURSOR_KEY, result.cursor);\n });\n\n this.notifyAndBroadcast(affected, true);\n // Count records actually applied (skipping our own echoes / already-seen versions).\n return writes.length;\n }\n\n /** Apply one remote change, returning the StoredDoc to write, or `undefined` to skip. */\n private async resolveRemote(remote: Change): Promise<StoredDoc | undefined> {\n const local = await this.storage.get(remote.collection, remote.id);\n\n // Already have this version (or older / our own echo) → skip.\n if (local && remote.version <= local.remoteVersion) return undefined;\n\n let resolved: StoredDoc;\n if (local && isDirty(local)) {\n // True concurrent edit: local has unpushed changes AND the server moved on.\n resolved = this.resolver(local, remote);\n } else {\n // Fast-forward: adopt the remote wholesale.\n resolved = docFromRemote(remote);\n }\n // Engine enforces sync bookkeeping regardless of resolver behavior.\n return { ...resolved, remoteVersion: remote.version, syncedAt: Date.now() };\n }\n\n private async applyRemote(remote: Change): Promise<void> {\n const resolved = await this.resolveRemote(remote);\n if (resolved) await this.storage.put(resolved);\n }\n\n // ── Internal plumbing ────────────────────────────────────────────────────────\n\n private handleLocalWrite(doc: StoredDoc): void {\n this.emitter.emit(\"change\", { collection: doc.collection, id: doc.id, doc });\n this.collections.get(doc.collection)?.notify();\n this.tabs.broadcast({ type: \"write\", collection: doc.collection, id: doc.id });\n if (this.autoSync && this.background.isOnline()) {\n void this.sync().catch(() => {});\n }\n }\n\n private notifyAndBroadcast(collections: Set<string>, broadcast: boolean): void {\n for (const name of collections) {\n this.collections.get(name)?.notify();\n this.emitter.emit(\"change\", { collection: name, id: \"*\", doc: undefined });\n }\n if (broadcast && collections.size > 0) this.tabs.broadcast({ type: \"sync\" });\n }\n\n private onTabMessage(msg: TabMessage): void {\n if (msg.type === \"write\") {\n // Another tab wrote to shared storage — refresh local subscribers.\n this.collections.get(msg.collection)?.notify();\n this.emitter.emit(\"change\", { collection: msg.collection, id: msg.id, doc: undefined });\n } else {\n for (const collection of this.collections.values()) collection.notify();\n }\n }\n\n private setStatus(status: SyncStatus): void {\n if (this._status === status) return;\n this._status = status;\n this.emitter.emit(\"status\", { status });\n }\n\n /** Opt into Service Worker Background Sync (no-op if unsupported). */\n registerBackgroundSync(tag?: string): Promise<boolean> {\n return registerBackgroundSync(tag);\n }\n\n /** Tear down listeners, timers, and channels, and close storage. */\n async close(): Promise<void> {\n this.background.stop();\n this.tabs.close();\n this.emitter.clear();\n await this.storage.close();\n }\n}\n\n/** Create a Sync instance. See {@link SyncConfig}. */\nexport function createSync(config: SyncConfig): Sync {\n return new Sync(config);\n}\n","import type { StorageAdapter, StorageTx, StoredDoc } from \"../types.js\";\nimport { isDirty } from \"../types.js\";\n\nconst DOCS = \"docs\";\nconst META = \"meta\";\n\n/** Promisify an IDBRequest. */\nfunction req<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\n/** Resolve when a transaction commits; reject on error/abort. */\nfunction txDone(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error ?? new Error(\"IndexedDB transaction aborted\"));\n });\n}\n\nexport interface IndexedDBStorageOptions {\n /** Override the IDBFactory (e.g. inject fake-indexeddb in tests). */\n indexedDB?: IDBFactory;\n}\n\n/**\n * Default browser {@link StorageAdapter}, backed by IndexedDB. Zero dependencies.\n *\n * Records live in a `docs` store keyed by `[collection, id]` with a `collection`\n * index for range scans; key/value meta lives in a `meta` store.\n *\n * `transact` buffers writes synchronously inside the callback, then applies them\n * in a single IndexedDB transaction — so a throw rolls everything back, and we\n * never hold an IDB transaction open across an `await` (which would auto-close it).\n */\nexport function indexedDBStorage(options: IndexedDBStorageOptions = {}): StorageAdapter {\n const idb =\n options.indexedDB ??\n (typeof indexedDB !== \"undefined\" ? indexedDB : undefined);\n if (!idb) {\n throw new Error(\n \"indexedDBStorage(): no IndexedDB available. Pass `{ indexedDB }` (e.g. fake-indexeddb) or use memoryStorage().\",\n );\n }\n\n let db: IDBDatabase | undefined;\n\n function open(name: string): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = idb!.open(name, 1);\n request.onupgradeneeded = () => {\n const database = request.result;\n if (!database.objectStoreNames.contains(DOCS)) {\n const store = database.createObjectStore(DOCS, { keyPath: [\"collection\", \"id\"] });\n store.createIndex(\"collection\", \"collection\", { unique: false });\n }\n if (!database.objectStoreNames.contains(META)) {\n database.createObjectStore(META);\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n function database(): IDBDatabase {\n if (!db) throw new Error(\"indexedDBStorage(): init() was not called.\");\n return db;\n }\n\n return {\n async init(name) {\n if (!db) db = await open(name);\n },\n\n async get(collection, id) {\n const tx = database().transaction(DOCS, \"readonly\");\n const result = await req<StoredDoc | undefined>(\n tx.objectStore(DOCS).get([collection, id]),\n );\n return result;\n },\n\n async getAll(collection) {\n const tx = database().transaction(DOCS, \"readonly\");\n const index = tx.objectStore(DOCS).index(\"collection\");\n // Passing the key directly selects exactly that key (no IDBKeyRange global needed).\n return req<StoredDoc[]>(index.getAll(collection));\n },\n\n async put(doc) {\n const tx = database().transaction(DOCS, \"readwrite\");\n tx.objectStore(DOCS).put(doc);\n await txDone(tx);\n },\n\n async bulkPut(docs) {\n await this.transact((tx) => {\n for (const doc of docs) tx.put(doc);\n });\n },\n\n async transact(fn) {\n // Buffer operations synchronously so we don't keep an IDB transaction\n // open across awaits inside `fn`.\n const puts: StoredDoc[] = [];\n const deletes: Array<[string, string]> = [];\n const metaWrites: Array<[string, unknown]> = [];\n const tx: StorageTx = {\n put: (doc) => void puts.push(doc),\n delete: (collection, id) => void deletes.push([collection, id]),\n setMeta: (key, value) => void metaWrites.push([key, value]),\n };\n\n await fn(tx);\n\n const stores = metaWrites.length > 0 ? [DOCS, META] : [DOCS];\n const idbTx = database().transaction(stores, \"readwrite\");\n const docStore = idbTx.objectStore(DOCS);\n for (const doc of puts) docStore.put(doc);\n for (const [collection, id] of deletes) docStore.delete([collection, id]);\n if (metaWrites.length > 0) {\n const metaStore = idbTx.objectStore(META);\n for (const [key, value] of metaWrites) metaStore.put(value, key);\n }\n await txDone(idbTx);\n },\n\n async getDirty() {\n const tx = database().transaction(DOCS, \"readonly\");\n const all = await req<StoredDoc[]>(tx.objectStore(DOCS).getAll());\n return all.filter(isDirty);\n },\n\n async getMeta<V = unknown>(key: string) {\n const tx = database().transaction(META, \"readonly\");\n return req<V | undefined>(tx.objectStore(META).get(key));\n },\n\n async setMeta(key, value) {\n const tx = database().transaction(META, \"readwrite\");\n tx.objectStore(META).put(value, key);\n await txDone(tx);\n },\n\n async close() {\n db?.close();\n db = undefined;\n },\n };\n}\n","import type { StorageAdapter, StorageTx, StoredDoc } from \"../types.js\";\nimport { isDirty } from \"../types.js\";\n\n/** `collection` + `id` -> stable map key. */\nfunction key(collection: string, id: string): string {\n return collection + \"\u0000\" + id;\n}\n\n/** Deep-clone a record so callers can't mutate stored state by reference. */\nfunction clone<T>(doc: StoredDoc<T>): StoredDoc<T> {\n // structuredClone is available in Node 17+ and all modern browsers.\n return structuredClone(doc);\n}\n\n/**\n * In-memory {@link StorageAdapter} for tests, Node, and SSR. Not persistent.\n *\n * `transact` buffers writes and applies them atomically: if `fn` throws, no\n * change is committed (the rollback guarantee `bulkPut` and `transaction` rely on).\n */\nexport function memoryStorage(): StorageAdapter {\n const docs = new Map<string, StoredDoc>();\n const meta = new Map<string, unknown>();\n\n function commit(staged: {\n puts: Map<string, StoredDoc>;\n deletes: Set<string>;\n meta: Map<string, unknown>;\n }): void {\n for (const k of staged.deletes) docs.delete(k);\n for (const [k, v] of staged.puts) docs.set(k, v);\n for (const [k, v] of staged.meta) meta.set(k, v);\n }\n\n return {\n async init() {\n /* no-op */\n },\n\n async get(collection, id) {\n const doc = docs.get(key(collection, id));\n return doc ? clone(doc) : undefined;\n },\n\n async getAll(collection) {\n const out: StoredDoc[] = [];\n for (const doc of docs.values()) {\n if (doc.collection === collection) out.push(clone(doc));\n }\n return out;\n },\n\n async put(doc) {\n docs.set(key(doc.collection, doc.id), clone(doc));\n },\n\n async bulkPut(input) {\n await this.transact((tx) => {\n for (const doc of input) tx.put(doc);\n });\n },\n\n async transact(fn) {\n const staged = {\n puts: new Map<string, StoredDoc>(),\n deletes: new Set<string>(),\n meta: new Map<string, unknown>(),\n };\n const tx: StorageTx = {\n put(doc) {\n const k = key(doc.collection, doc.id);\n staged.puts.set(k, clone(doc));\n staged.deletes.delete(k);\n },\n delete(collection, id) {\n const k = key(collection, id);\n staged.deletes.add(k);\n staged.puts.delete(k);\n },\n setMeta(metaKey, value) {\n staged.meta.set(metaKey, value);\n },\n };\n // If fn throws, `staged` is discarded and nothing is committed.\n await fn(tx);\n commit(staged);\n },\n\n async getDirty() {\n const out: StoredDoc[] = [];\n for (const doc of docs.values()) {\n if (isDirty(doc)) out.push(clone(doc));\n }\n return out;\n },\n\n async getMeta<V = unknown>(metaKey: string) {\n return meta.has(metaKey) ? (structuredClone(meta.get(metaKey)) as V) : undefined;\n },\n\n async setMeta(metaKey, value) {\n meta.set(metaKey, structuredClone(value));\n },\n\n async close() {\n docs.clear();\n meta.clear();\n },\n };\n}\n","import type { Cursor, PullResult, PushResult, SyncAdapter } from \"../types.js\";\n\nexport interface RestAdapterOptions {\n /**\n * Base URL of the sync endpoints. The adapter calls:\n * - `POST {url}/push` body `{ changes }` -> `{ acked, conflicts?, cursor }`\n * - `GET {url}/pull?since={cursor}` -> `{ changes, cursor }`\n */\n url: string;\n /** Extra headers (e.g. auth). Called per request so tokens can refresh. */\n headers?: () => Record<string, string> | Promise<Record<string, string>>;\n /** Override `fetch` (e.g. for tests or custom retry/agent behavior). */\n fetch?: typeof fetch;\n}\n\n/**\n * Default HTTP {@link SyncAdapter}. Implements a tiny, copy-pasteable protocol;\n * see `examples/server` for a reference backend.\n */\nexport function restAdapter(options: RestAdapterOptions): SyncAdapter {\n const base = options.url.replace(/\\/$/, \"\");\n const doFetch = options.fetch ?? globalThis.fetch;\n if (!doFetch) {\n throw new Error(\"restAdapter(): no global fetch available. Pass `{ fetch }`.\");\n }\n\n async function headers(): Promise<Record<string, string>> {\n const extra = options.headers ? await options.headers() : {};\n return { \"content-type\": \"application/json\", ...extra };\n }\n\n return {\n async push(changes): Promise<PushResult> {\n const res = await doFetch(`${base}/push`, {\n method: \"POST\",\n headers: await headers(),\n body: JSON.stringify({ changes }),\n });\n if (!res.ok) throw new Error(`loko push failed: ${res.status} ${res.statusText}`);\n return (await res.json()) as PushResult;\n },\n\n async pull(since: Cursor): Promise<PullResult> {\n const res = await doFetch(`${base}/pull?since=${encodeURIComponent(String(since))}`, {\n method: \"GET\",\n headers: await headers(),\n });\n if (!res.ok) throw new Error(`loko pull failed: ${res.status} ${res.statusText}`);\n return (await res.json()) as PullResult;\n },\n };\n}\n","import type { Change, Cursor, PushResult, SyncAdapter } from \"../types.js\";\n\ninterface ServerRecord {\n collection: string;\n id: string;\n data: unknown;\n deleted: boolean;\n updatedAt: number;\n version: number;\n}\n\n/**\n * A minimal in-memory \"server\". Assigns a monotonically increasing `version` to\n * every committed change (this `version` is the conflict-resolution authority,\n * not client clocks) and exposes it as the pull cursor. Share one server across\n * several {@link memoryAdapter} instances to simulate multiple clients in tests.\n */\nexport class MemoryServer {\n private records = new Map<string, ServerRecord>();\n private seq = 0;\n\n private key(collection: string, id: string): string {\n return collection + \" \" + id;\n }\n\n commit(changes: Change[]): PushResult {\n const acked: PushResult[\"acked\"] = [];\n const conflicts: Change[] = [];\n\n for (const change of changes) {\n const k = this.key(change.collection, change.id);\n const current = this.records.get(k);\n // Client based its edit on `change.version`; if the server has moved past\n // that, it's a conflict — return the server's current state instead.\n if (current && current.version > change.version) {\n conflicts.push(this.toChange(current));\n continue;\n }\n const version = ++this.seq;\n this.records.set(k, {\n collection: change.collection,\n id: change.id,\n data: change.data,\n deleted: change.deleted,\n updatedAt: change.updatedAt,\n version,\n });\n acked.push({ id: change.id, collection: change.collection, version });\n }\n\n return { acked, conflicts, cursor: this.seq };\n }\n\n changesSince(since: Cursor): { changes: Change[]; cursor: Cursor } {\n const sinceNum = Number(since) || 0;\n const changes = [...this.records.values()]\n .filter((r) => r.version > sinceNum)\n .sort((a, b) => a.version - b.version)\n .map((r) => this.toChange(r));\n return { changes, cursor: this.seq };\n }\n\n private toChange(r: ServerRecord): Change {\n return {\n collection: r.collection,\n id: r.id,\n data: r.deleted ? null : r.data,\n version: r.version,\n updatedAt: r.updatedAt,\n deleted: r.deleted,\n };\n }\n}\n\n/**\n * In-memory {@link SyncAdapter} for tests and demos. Pass a shared\n * {@link MemoryServer} to connect multiple simulated clients to one backend.\n */\nexport function memoryAdapter(server: MemoryServer = new MemoryServer()): SyncAdapter {\n return {\n async push(changes) {\n return server.commit(changes);\n },\n async pull(since) {\n return server.changesSince(since);\n },\n };\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for loko.
|
|
3
|
+
*
|
|
4
|
+
* Design notes:
|
|
5
|
+
* - Server-assigned `version` is the authority for conflict resolution and delta
|
|
6
|
+
* sync, NOT client wall-clock time. Device clocks drift, so `updatedAt` is kept
|
|
7
|
+
* purely for display and as a hint for the default resolver.
|
|
8
|
+
* - `dirty` is never stored: it is derived as `localRev > lastPushedLocalRev`.
|
|
9
|
+
*/
|
|
10
|
+
/** A record stored locally, wrapping the user's data `T` with sync metadata. */
|
|
11
|
+
interface StoredDoc<T = unknown> {
|
|
12
|
+
/** Primary key within the collection. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Collection name this record belongs to. */
|
|
15
|
+
collection: string;
|
|
16
|
+
/** The user's payload. `null` only ever paired with a tombstone (`deletedAt`). */
|
|
17
|
+
data: T;
|
|
18
|
+
/** Wall-clock ms of the last local edit. Display/hint only — never the sync authority. */
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
/** Tombstone marker (soft delete) so deletions propagate. `null` = live record. */
|
|
21
|
+
deletedAt: number | null;
|
|
22
|
+
/** Bumped on every local edit. */
|
|
23
|
+
localRev: number;
|
|
24
|
+
/** Value of `localRev` at the last successful push ack. */
|
|
25
|
+
lastPushedLocalRev: number;
|
|
26
|
+
/** Server-assigned version this record was last synced to/from (0 = never synced). */
|
|
27
|
+
remoteVersion: number;
|
|
28
|
+
/** Wall-clock ms of the last successful sync touching this record. */
|
|
29
|
+
syncedAt: number | null;
|
|
30
|
+
}
|
|
31
|
+
/** `true` when a record has local edits not yet acknowledged by the server. */
|
|
32
|
+
declare function isDirty(doc: Pick<StoredDoc, "localRev" | "lastPushedLocalRev">): boolean;
|
|
33
|
+
/** A change transmitted over the wire (push/pull). */
|
|
34
|
+
interface Change<T = unknown> {
|
|
35
|
+
collection: string;
|
|
36
|
+
id: string;
|
|
37
|
+
/** Payload, or `null` for a deletion. */
|
|
38
|
+
data: T | null;
|
|
39
|
+
/** Server-assigned version. For outgoing (push) changes this is the client's
|
|
40
|
+
* last-known `remoteVersion` (the base the edit was made against). */
|
|
41
|
+
version: number;
|
|
42
|
+
/** Display value / resolver hint only. */
|
|
43
|
+
updatedAt: number;
|
|
44
|
+
/** Whether this change is a deletion. */
|
|
45
|
+
deleted: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Opaque, monotonically increasing server sequence used as a pull cursor. */
|
|
48
|
+
type Cursor = string | number;
|
|
49
|
+
/** Result of pushing local changes to the server. */
|
|
50
|
+
interface PushResult {
|
|
51
|
+
/** Records the server accepted, with their newly committed versions. */
|
|
52
|
+
acked: Array<{
|
|
53
|
+
id: string;
|
|
54
|
+
collection: string;
|
|
55
|
+
version: number;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Records the server rejected because it was already ahead (e.g. client sent
|
|
59
|
+
* v5 but server has v6). Surfaced at push time so conflicts aren't only
|
|
60
|
+
* discovered on the next pull. May be omitted/empty.
|
|
61
|
+
*/
|
|
62
|
+
conflicts?: Array<Change>;
|
|
63
|
+
/** Server sequence after this push; lets the client advance its pull cursor. */
|
|
64
|
+
cursor: Cursor;
|
|
65
|
+
}
|
|
66
|
+
/** Result of pulling remote changes since a cursor. */
|
|
67
|
+
interface PullResult {
|
|
68
|
+
changes: Array<Change>;
|
|
69
|
+
cursor: Cursor;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Transport between the local store and a backend. Bring your own — implement
|
|
73
|
+
* this against REST, Supabase, Firebase, WebSockets, anything.
|
|
74
|
+
*/
|
|
75
|
+
interface SyncAdapter {
|
|
76
|
+
push(changes: Change[]): Promise<PushResult>;
|
|
77
|
+
pull(since: Cursor): Promise<PullResult>;
|
|
78
|
+
}
|
|
79
|
+
/** A handle for atomic multi-record writes within {@link StorageAdapter.transact}. */
|
|
80
|
+
interface StorageTx {
|
|
81
|
+
put(doc: StoredDoc): void;
|
|
82
|
+
delete(collection: string, id: string): void;
|
|
83
|
+
setMeta(key: string, value: unknown): void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Pluggable persistence layer. Ships with `indexedDBStorage()` (browser default)
|
|
87
|
+
* and `memoryStorage()` (tests/Node/SSR).
|
|
88
|
+
*/
|
|
89
|
+
interface StorageAdapter {
|
|
90
|
+
/** Open/prepare the store for the given database name. */
|
|
91
|
+
init(name: string): Promise<void>;
|
|
92
|
+
get(collection: string, id: string): Promise<StoredDoc | undefined>;
|
|
93
|
+
/** All records in a collection, including tombstones. */
|
|
94
|
+
getAll(collection: string): Promise<StoredDoc[]>;
|
|
95
|
+
put(doc: StoredDoc): Promise<void>;
|
|
96
|
+
bulkPut(docs: StoredDoc[]): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Run `fn` as an atomic, all-or-nothing transaction. Underpins `bulkPut`
|
|
99
|
+
* today and `sync.transaction()` (reserved) tomorrow. A throw rolls back.
|
|
100
|
+
*/
|
|
101
|
+
transact(fn: (tx: StorageTx) => void | Promise<void>): Promise<void>;
|
|
102
|
+
/** All records with unsynced local edits, across every collection. */
|
|
103
|
+
getDirty(): Promise<StoredDoc[]>;
|
|
104
|
+
getMeta<V = unknown>(key: string): Promise<V | undefined>;
|
|
105
|
+
setMeta(key: string, value: unknown): Promise<void>;
|
|
106
|
+
/** Release resources (close DB, channels). */
|
|
107
|
+
close(): Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Decides the winner when a local edit and a remote change collide. The default
|
|
111
|
+
* is {@link lastWriteWins}. Conflict resolution is per-record, not per-field:
|
|
112
|
+
* to merge fields, return a record built from both sides.
|
|
113
|
+
*/
|
|
114
|
+
type ConflictResolver = <T = unknown>(local: StoredDoc<T> | undefined, remote: Change<T>) => StoredDoc<T>;
|
|
115
|
+
/** Options declared once per collection via {@link Sync.defineCollection}. */
|
|
116
|
+
interface CollectionOptions {
|
|
117
|
+
/** Field on the record used as the id. Defaults to `"id"`. */
|
|
118
|
+
primaryKey?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Indexed fields. Accepted and validated in v1 but not yet used for querying
|
|
121
|
+
* (reserved for indexed reads on the roadmap).
|
|
122
|
+
*/
|
|
123
|
+
indexes?: string[];
|
|
124
|
+
}
|
|
125
|
+
type SyncStatus = "idle" | "syncing" | "offline" | "error";
|
|
126
|
+
/** Map of event name -> payload for the typed emitter. */
|
|
127
|
+
interface SyncEvents {
|
|
128
|
+
/** A record changed locally or via sync. */
|
|
129
|
+
change: {
|
|
130
|
+
collection: string;
|
|
131
|
+
id: string;
|
|
132
|
+
doc: StoredDoc | undefined;
|
|
133
|
+
};
|
|
134
|
+
/** A full sync cycle completed. */
|
|
135
|
+
sync: {
|
|
136
|
+
pushed: number;
|
|
137
|
+
pulled: number;
|
|
138
|
+
};
|
|
139
|
+
/** Sync status transitioned. */
|
|
140
|
+
status: {
|
|
141
|
+
status: SyncStatus;
|
|
142
|
+
};
|
|
143
|
+
/** Something went wrong (sync, storage, transport). */
|
|
144
|
+
error: {
|
|
145
|
+
error: unknown;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
interface SyncConfig {
|
|
149
|
+
/** Logical database name; also namespaces the multi-tab BroadcastChannel. */
|
|
150
|
+
name: string;
|
|
151
|
+
/** Where records live. Defaults to `indexedDBStorage()` in the browser. */
|
|
152
|
+
storage: StorageAdapter;
|
|
153
|
+
/** How to talk to the backend. Optional — omit for a purely local store. */
|
|
154
|
+
adapter?: SyncAdapter;
|
|
155
|
+
/** Conflict strategy. Defaults to {@link lastWriteWins}. */
|
|
156
|
+
conflict?: ConflictResolver;
|
|
157
|
+
/**
|
|
158
|
+
* When `true` (default), sync on reconnect and on an interval (leader tab only).
|
|
159
|
+
* Pass a number to override the polling interval in ms (0 disables polling).
|
|
160
|
+
*/
|
|
161
|
+
autoSync?: boolean | number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Internal wiring handed to each {@link Collection} by the owning Sync instance. */
|
|
165
|
+
interface CollectionContext {
|
|
166
|
+
storage: StorageAdapter;
|
|
167
|
+
primaryKey: string;
|
|
168
|
+
indexes: string[];
|
|
169
|
+
/** Resolves once storage is initialized; awaited before every storage access. */
|
|
170
|
+
ready: Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Called after a local write. The Sync engine emits a `change` event,
|
|
173
|
+
* broadcasts to other tabs, and schedules autoSync.
|
|
174
|
+
*/
|
|
175
|
+
onLocalWrite(doc: StoredDoc): void;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A typed view over one collection of records. Reads/writes the user's payload
|
|
179
|
+
* `T` and tracks sync metadata under the hood. `T` must carry the primary key
|
|
180
|
+
* field (default `"id"`).
|
|
181
|
+
*/
|
|
182
|
+
declare class Collection<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
183
|
+
readonly name: string;
|
|
184
|
+
private readonly ctx;
|
|
185
|
+
private readonly subscribers;
|
|
186
|
+
private readonly oneSubscribers;
|
|
187
|
+
private refreshScheduled;
|
|
188
|
+
constructor(name: string, ctx: CollectionContext);
|
|
189
|
+
private idOf;
|
|
190
|
+
/** Read a single live record by id (tombstones return `undefined`). */
|
|
191
|
+
get(id: string): Promise<T | undefined>;
|
|
192
|
+
/** All live records in the collection (tombstones excluded). */
|
|
193
|
+
all(): Promise<T[]>;
|
|
194
|
+
/** Insert or update a record. Returns the stored record. */
|
|
195
|
+
put(record: T): Promise<T>;
|
|
196
|
+
/** Insert or update many records atomically (one transaction). */
|
|
197
|
+
bulkPut(records: T[]): Promise<void>;
|
|
198
|
+
/** Soft-delete a record (writes a tombstone so the deletion syncs). */
|
|
199
|
+
delete(id: string): Promise<void>;
|
|
200
|
+
/** Build the next StoredDoc for a local write, bumping `localRev`. */
|
|
201
|
+
private nextDoc;
|
|
202
|
+
/**
|
|
203
|
+
* Subscribe to the whole collection. The callback fires immediately with the
|
|
204
|
+
* current records, then on every change (local, sync, or other tab).
|
|
205
|
+
* Returns an unsubscribe function.
|
|
206
|
+
*/
|
|
207
|
+
subscribe(cb: (items: T[]) => void): () => void;
|
|
208
|
+
/** Subscribe to a single record by id. */
|
|
209
|
+
subscribeOne(id: string, cb: (item: T | undefined) => void): () => void;
|
|
210
|
+
/**
|
|
211
|
+
* Notify subscribers that this collection changed. Coalesces bursts (e.g.
|
|
212
|
+
* bulkPut, a sync batch) into a single refresh per microtask. Called by the
|
|
213
|
+
* Sync engine for local writes, pulled changes, and cross-tab updates.
|
|
214
|
+
*/
|
|
215
|
+
notify(): void;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
declare class Sync {
|
|
219
|
+
private readonly storage;
|
|
220
|
+
private readonly adapter;
|
|
221
|
+
private readonly resolver;
|
|
222
|
+
private readonly emitter;
|
|
223
|
+
private readonly collections;
|
|
224
|
+
private readonly options;
|
|
225
|
+
private readonly tabs;
|
|
226
|
+
private readonly background;
|
|
227
|
+
private readonly autoSync;
|
|
228
|
+
private readonly ready;
|
|
229
|
+
private _status;
|
|
230
|
+
private inFlight;
|
|
231
|
+
private clientId;
|
|
232
|
+
constructor(config: SyncConfig);
|
|
233
|
+
private init;
|
|
234
|
+
/** Declare a collection's options once (at init). Returns the collection. */
|
|
235
|
+
defineCollection<T extends Record<string, unknown>>(name: string, options?: CollectionOptions): Collection<T>;
|
|
236
|
+
/** Access a collection. Auto-defines with defaults if not yet declared. */
|
|
237
|
+
collection<T extends Record<string, unknown>>(name: string): Collection<T>;
|
|
238
|
+
private getOrCreate;
|
|
239
|
+
/**
|
|
240
|
+
* Convenience for simple key -> value cases. Stored as a SINGLE record in the
|
|
241
|
+
* reserved `_kv` collection, so it syncs as one coarse record. Not smart
|
|
242
|
+
* per-item sync — use real collections (`collection().put`) for that.
|
|
243
|
+
*/
|
|
244
|
+
store<V>(key: string, value: V): Promise<void>;
|
|
245
|
+
/** Read a value previously written with {@link store}. */
|
|
246
|
+
get<V>(key: string): Promise<V | undefined>;
|
|
247
|
+
get status(): SyncStatus;
|
|
248
|
+
/** Resolves once storage is initialized (clientId loaded, leader elected). */
|
|
249
|
+
whenReady(): Promise<void>;
|
|
250
|
+
/** Subscribe to a lifecycle event. Returns an unsubscribe function. */
|
|
251
|
+
on<K extends keyof SyncEvents>(event: K, fn: (payload: SyncEvents[K]) => void): () => void;
|
|
252
|
+
/**
|
|
253
|
+
* Run a full sync cycle: push local changes, then pull remote ones. Concurrent
|
|
254
|
+
* calls share the same in-flight run. No-op when no adapter is configured.
|
|
255
|
+
*/
|
|
256
|
+
sync(): Promise<{
|
|
257
|
+
pushed: number;
|
|
258
|
+
pulled: number;
|
|
259
|
+
}>;
|
|
260
|
+
private push;
|
|
261
|
+
private pull;
|
|
262
|
+
/** Apply one remote change, returning the StoredDoc to write, or `undefined` to skip. */
|
|
263
|
+
private resolveRemote;
|
|
264
|
+
private applyRemote;
|
|
265
|
+
private handleLocalWrite;
|
|
266
|
+
private notifyAndBroadcast;
|
|
267
|
+
private onTabMessage;
|
|
268
|
+
private setStatus;
|
|
269
|
+
/** Opt into Service Worker Background Sync (no-op if unsupported). */
|
|
270
|
+
registerBackgroundSync(tag?: string): Promise<boolean>;
|
|
271
|
+
/** Tear down listeners, timers, and channels, and close storage. */
|
|
272
|
+
close(): Promise<void>;
|
|
273
|
+
}
|
|
274
|
+
/** Create a Sync instance. See {@link SyncConfig}. */
|
|
275
|
+
declare function createSync(config: SyncConfig): Sync;
|
|
276
|
+
|
|
277
|
+
interface IndexedDBStorageOptions {
|
|
278
|
+
/** Override the IDBFactory (e.g. inject fake-indexeddb in tests). */
|
|
279
|
+
indexedDB?: IDBFactory;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Default browser {@link StorageAdapter}, backed by IndexedDB. Zero dependencies.
|
|
283
|
+
*
|
|
284
|
+
* Records live in a `docs` store keyed by `[collection, id]` with a `collection`
|
|
285
|
+
* index for range scans; key/value meta lives in a `meta` store.
|
|
286
|
+
*
|
|
287
|
+
* `transact` buffers writes synchronously inside the callback, then applies them
|
|
288
|
+
* in a single IndexedDB transaction — so a throw rolls everything back, and we
|
|
289
|
+
* never hold an IDB transaction open across an `await` (which would auto-close it).
|
|
290
|
+
*/
|
|
291
|
+
declare function indexedDBStorage(options?: IndexedDBStorageOptions): StorageAdapter;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* In-memory {@link StorageAdapter} for tests, Node, and SSR. Not persistent.
|
|
295
|
+
*
|
|
296
|
+
* `transact` buffers writes and applies them atomically: if `fn` throws, no
|
|
297
|
+
* change is committed (the rollback guarantee `bulkPut` and `transaction` rely on).
|
|
298
|
+
*/
|
|
299
|
+
declare function memoryStorage(): StorageAdapter;
|
|
300
|
+
|
|
301
|
+
interface RestAdapterOptions {
|
|
302
|
+
/**
|
|
303
|
+
* Base URL of the sync endpoints. The adapter calls:
|
|
304
|
+
* - `POST {url}/push` body `{ changes }` -> `{ acked, conflicts?, cursor }`
|
|
305
|
+
* - `GET {url}/pull?since={cursor}` -> `{ changes, cursor }`
|
|
306
|
+
*/
|
|
307
|
+
url: string;
|
|
308
|
+
/** Extra headers (e.g. auth). Called per request so tokens can refresh. */
|
|
309
|
+
headers?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
310
|
+
/** Override `fetch` (e.g. for tests or custom retry/agent behavior). */
|
|
311
|
+
fetch?: typeof fetch;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Default HTTP {@link SyncAdapter}. Implements a tiny, copy-pasteable protocol;
|
|
315
|
+
* see `examples/server` for a reference backend.
|
|
316
|
+
*/
|
|
317
|
+
declare function restAdapter(options: RestAdapterOptions): SyncAdapter;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* A minimal in-memory "server". Assigns a monotonically increasing `version` to
|
|
321
|
+
* every committed change (this `version` is the conflict-resolution authority,
|
|
322
|
+
* not client clocks) and exposes it as the pull cursor. Share one server across
|
|
323
|
+
* several {@link memoryAdapter} instances to simulate multiple clients in tests.
|
|
324
|
+
*/
|
|
325
|
+
declare class MemoryServer {
|
|
326
|
+
private records;
|
|
327
|
+
private seq;
|
|
328
|
+
private key;
|
|
329
|
+
commit(changes: Change[]): PushResult;
|
|
330
|
+
changesSince(since: Cursor): {
|
|
331
|
+
changes: Change[];
|
|
332
|
+
cursor: Cursor;
|
|
333
|
+
};
|
|
334
|
+
private toChange;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* In-memory {@link SyncAdapter} for tests and demos. Pass a shared
|
|
338
|
+
* {@link MemoryServer} to connect multiple simulated clients to one backend.
|
|
339
|
+
*/
|
|
340
|
+
declare function memoryAdapter(server?: MemoryServer): SyncAdapter;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Build a clean local record by fully adopting a remote change. Used for
|
|
344
|
+
* fast-forward (non-conflicting) updates and when the remote wins a conflict.
|
|
345
|
+
* The resulting record is NOT dirty (`localRev === lastPushedLocalRev`).
|
|
346
|
+
*/
|
|
347
|
+
declare function docFromRemote<T>(remote: Change<T>, now?: number): StoredDoc<T>;
|
|
348
|
+
/**
|
|
349
|
+
* Default conflict strategy: **last write wins** by wall-clock `updatedAt`, with
|
|
350
|
+
* the already-committed remote winning ties. Conflict resolution is per-record:
|
|
351
|
+
* the winning record replaces the loser wholesale (no field merge).
|
|
352
|
+
*
|
|
353
|
+
* - Remote wins -> adopt remote (record becomes clean).
|
|
354
|
+
* - Local wins -> keep local data, rebased onto the remote's version, and left
|
|
355
|
+
* dirty so it re-pushes and overwrites the server on the next sync.
|
|
356
|
+
*/
|
|
357
|
+
declare function lastWriteWins(): ConflictResolver;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Opt-in helper to register a Service Worker Background Sync trigger, for true
|
|
361
|
+
* background sync when the page is closed. The core works fully without this;
|
|
362
|
+
* your service worker must listen for the `sync` event with the same tag and
|
|
363
|
+
* message clients to call `sync.sync()`.
|
|
364
|
+
*/
|
|
365
|
+
declare function registerBackgroundSync(tag?: string): Promise<boolean>;
|
|
366
|
+
|
|
367
|
+
export { type Change, Collection, type CollectionOptions, type ConflictResolver, type Cursor, type IndexedDBStorageOptions, MemoryServer, type PullResult, type PushResult, type RestAdapterOptions, type StorageAdapter, type StorageTx, type StoredDoc, Sync, type SyncAdapter, type SyncConfig, type SyncEvents, type SyncStatus, createSync, docFromRemote, indexedDBStorage, isDirty, lastWriteWins, memoryAdapter, memoryStorage, registerBackgroundSync, restAdapter };
|