@byearlybird/starling 0.18.0 → 0.19.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/README.md +58 -92
- package/dist/index.d.ts +63 -181
- package/dist/index.js +336 -569
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["result: Tombstones","merged: Record<string, Atom<unknown>>","mergedCollection: Record<string, AtomizedDocument<T>>","#listeners","results: T[]","#config","#emitter","#state","#getCollection","#getNextStamp","modifications: Record<string, CollectionModifications>","results: DocType<T[typeof collection]>[]","#onMessage","#channel","#store","#config","#emitter","#sync","#load","#db","#save","#persist","#openDB","#unsubscribe","#debounceTimer"],"sources":["../lib/core/hex.ts","../lib/core/clock.ts","../lib/core/tombstone.ts","../lib/core/atomizer.ts","../lib/core/types.ts","../lib/core/lens.ts","../lib/core/merge.ts","../lib/emitter/emitter.ts","../lib/store/schema.ts","../lib/store/operations.ts","../lib/store/store.ts","../lib/persisters/broadcast-sync.ts","../lib/persisters/idb-persister.ts"],"sourcesContent":["export function toHex(value: number, padLength: number): string {\n return value.toString(16).padStart(padLength, \"0\");\n}\n\nexport function nonce(length: number): string {\n const bytes = new Uint8Array(length / 2);\n crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map((b) => toHex(b, 2))\n .join(\"\");\n}\n","import { nonce, toHex } from \"./hex\";\n\nconst MS_LENGTH = 12;\nconst SEQ_LENGTH = 6;\nconst NONCE_LENGTH = 6;\nconst STAMP_LENGTH = MS_LENGTH + SEQ_LENGTH + NONCE_LENGTH;\nconst HEX_PATTERN = /^[0-9a-f]+$/i;\n\nexport type Clock = {\n ms: number;\n seq: number;\n};\n\n/**\n * Branded type for HLC timestamps.\n * Stamps are lexicographically sortable hex strings encoding (ms, seq, nonce).\n */\nexport type Stamp = string & { readonly __brand: \"Stamp\" };\n\nexport function advanceClock(current: Clock, next: Clock): Clock {\n if (next.ms > current.ms) {\n return { ms: next.ms, seq: next.seq };\n } else if (next.ms === current.ms) {\n return { ms: current.ms, seq: Math.max(current.seq, next.seq) + 1 };\n } else {\n return { ms: current.ms, seq: current.seq + 1 };\n }\n}\n\nexport function makeStamp(ms: number, seq: number): Stamp {\n return `${toHex(ms, MS_LENGTH)}${toHex(seq, SEQ_LENGTH)}${nonce(NONCE_LENGTH)}` as Stamp;\n}\n\n/**\n * Parse and validate a string as a Stamp. Use when deserializing stored state.\n * Throws if the value is not a valid 24-character hex string.\n */\nexport function asStamp(value: string): Stamp {\n if (value.length !== STAMP_LENGTH || !HEX_PATTERN.test(value)) {\n throw new Error(`Invalid stamp: expected ${STAMP_LENGTH} hex characters, got \"${value}\"`);\n }\n return value as Stamp;\n}\n","import type { Stamp } from \"./clock\";\n\nexport type Tombstones = Record<string, Stamp>;\n\nexport function isDeleted(id: string, tombstones: Tombstones): boolean {\n return tombstones[id] !== undefined;\n}\n\nexport function mergeTombstones(target: Tombstones, source: Tombstones): Tombstones {\n const result: Tombstones = {};\n const keys = new Set([...Object.keys(target), ...Object.keys(source)]);\n\n for (const key of keys) {\n const targetStamp = target[key];\n const sourceStamp = source[key];\n\n if (targetStamp && sourceStamp) {\n result[key] = targetStamp > sourceStamp ? targetStamp : sourceStamp;\n } else if (targetStamp) {\n result[key] = targetStamp;\n } else if (sourceStamp) {\n result[key] = sourceStamp;\n }\n }\n\n return result;\n}\n","import type { Stamp } from \"./clock\";\nimport type { Atom, AtomizedDocument, Document } from \"./types\";\nimport { KEYS } from \"./types\";\n\nexport function pack<T>(value: T, timestamp: Stamp): Atom<T> {\n return {\n [KEYS.VAL]: value,\n [KEYS.TS]: timestamp,\n };\n}\n\nexport function unpack<T>(node: unknown): T | undefined {\n return isAtom(node) ? (node[KEYS.VAL] as T) : undefined;\n}\n\nexport function isAtom(node: unknown): node is Atom<unknown> {\n return node !== null && typeof node === \"object\" && KEYS.VAL in node;\n}\n\nexport function atomize<T extends Document>(data: T, timestamp: Stamp): AtomizedDocument<T> {\n const document = {} as AtomizedDocument<T>;\n for (const key of Object.keys(data) as (keyof T)[]) {\n document[key] = pack(data[key], timestamp);\n }\n return document;\n}\n","import { isAtom } from \"./atomizer\";\nimport type { Clock, Stamp } from \"./clock\";\nimport type { Tombstones } from \"./tombstone\";\n\n// CONSTANTS\nexport const KEYS = { VAL: \"~val\", TS: \"~ts\" } as const;\n\n/**\n * Base constraint for plain document shapes - any object with string keys.\n */\nexport type Document = Record<string, unknown>;\n\nexport type Atom<T> = {\n [KEYS.VAL]: T;\n [KEYS.TS]: Stamp;\n};\n\n/**\n * Atomized document with plain shape T. Each key K is stored as Atom<T[K]> (per-field).\n */\nexport type AtomizedDocument<T extends Document> = {\n [K in keyof T]: Atom<T[K]>;\n};\n\n/**\n * Collection of atomized documents with shape T. Map from document ID to AtomizedDocument<T>.\n */\nexport type Collection<T extends Document> = Record<string, AtomizedDocument<T>>;\n\n/**\n * Collection state containing documents and tombstones.\n */\nexport type CollectionState<T extends Document> = {\n documents: Collection<T>;\n tombstones: Tombstones;\n};\n\nexport type StoreState = {\n clock: Clock;\n collections: Record<string, CollectionState<Document>>;\n};\n\n/**\n * Type guard to check if a value is an AtomizedDocument.\n * An AtomizedDocument is an object where all values are Atoms.\n */\nexport function isAtomizedDocument<T extends Document>(\n value: unknown,\n): value is AtomizedDocument<T> {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n const obj = value as Record<string, unknown>;\n // Check that all values are atoms\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n if (!isAtom(obj[key])) {\n return false;\n }\n }\n }\n return true;\n}\n\n/**\n * Type guard to check if a value is a Collection.\n * A Collection is a Record where all values are AtomizedDocuments.\n */\nexport function isCollection<T extends Document>(value: unknown): value is Collection<T> {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n const obj = value as Record<string, unknown>;\n // Check that all values are documents\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n if (!isAtomizedDocument<T>(obj[key])) {\n return false;\n }\n }\n }\n return true;\n}\n\n/**\n * Type guard to check if a value is a CollectionState.\n * A CollectionState has documents (Collection) and tombstones (Record<string, string>).\n */\nexport function isCollectionState<T extends Document>(value: unknown): value is CollectionState<T> {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n const obj = value as Record<string, unknown>;\n if (!(\"documents\" in obj) || !(\"tombstones\" in obj)) {\n return false;\n }\n return (\n isCollection<T>(obj[\"documents\"]) &&\n obj[\"tombstones\"] !== null &&\n typeof obj[\"tombstones\"] === \"object\" &&\n !Array.isArray(obj[\"tombstones\"])\n );\n}\n","import { isAtom, unpack } from \"./atomizer\";\nimport type { AtomizedDocument, Document } from \"./types\";\n\nexport function createReadLens<T extends Document>(doc: AtomizedDocument<T>): T {\n const handler: ProxyHandler<AtomizedDocument<T>> = {\n get(target, prop, receiver) {\n // Ignore prototype properties and symbols\n if (typeof prop === \"symbol\" || !Object.prototype.hasOwnProperty.call(target, prop)) {\n return undefined;\n }\n\n const value = Reflect.get(target, prop, receiver);\n\n // 1. Missing Key\n if (value === undefined) return undefined;\n\n // 2. Atom Detected (The Field) -> Unpack and return\n // We STOP here. We do not proxy deeper.\n if (isAtom(value)) {\n return unpack(value);\n }\n\n // 3. Non-atom (invalid); fail fast\n throw new Error(\n `createReadLens: field \"${String(prop)}\" is not an atom. Expected AtomizedDocument<T> with atomized fields only.`,\n );\n },\n\n // Block Writes\n set() {\n console.warn(\"Mutations must use the update API.\");\n return false;\n },\n };\n\n return new Proxy(doc, handler) as T;\n}\n","import { KEYS } from \"./types\";\nimport type { Atom, AtomizedDocument, Document, CollectionState } from \"./types\";\nimport { mergeTombstones } from \"./tombstone\";\nimport { isAtom } from \"./atomizer\";\n\n/** Merges incoming doc fields into local. Adds new keys from incoming; LWW on conflicts. */\nexport function mergeDocs<T extends Document>(\n local: AtomizedDocument<T>,\n incoming: Partial<AtomizedDocument<T>>,\n): AtomizedDocument<T> {\n const merged: Record<string, Atom<unknown>> = { ...local };\n let hasChanges = false;\n\n for (const key of Object.keys(incoming)) {\n const localAtom = local[key as keyof T];\n const incomingAtom = incoming[key as keyof T];\n if (incomingAtom === undefined) continue;\n\n if (!localAtom) {\n if (isAtom(incomingAtom)) {\n merged[key] = incomingAtom;\n hasChanges = true;\n }\n continue;\n }\n\n if (isAtom(localAtom) && isAtom(incomingAtom)) {\n if (incomingAtom[KEYS.TS] > localAtom[KEYS.TS]) {\n merged[key] = incomingAtom;\n hasChanges = true;\n }\n }\n }\n\n // Type assertion is safe: merged contains only keys from T with Atom<T[K]> values\n // We start with local (AtomizedDocument<T>) and only add/update valid atoms from incoming\n return (hasChanges ? merged : local) as AtomizedDocument<T>;\n}\n\n/**\n * Merges two collection states, respecting tombstones.\n * Documents that are tombstoned are excluded from the result.\n * For documents that exist in both collections, fields are merged using LWW semantics.\n */\nexport function mergeCollections<T extends Document>(\n local: CollectionState<T>,\n incoming: CollectionState<T>,\n): CollectionState<T> {\n const mergedTombstones = mergeTombstones(local.tombstones, incoming.tombstones);\n const mergedCollection: Record<string, AtomizedDocument<T>> = {};\n const allDocumentIds = new Set([\n ...Object.keys(local.documents),\n ...Object.keys(incoming.documents),\n ]);\n\n for (const id of allDocumentIds) {\n // Skip tombstoned documents\n if (mergedTombstones[id]) {\n continue;\n }\n\n const localDoc = local.documents[id];\n const incomingDoc = incoming.documents[id];\n\n if (localDoc && incomingDoc) {\n // Both exist: merge documents\n mergedCollection[id] = mergeDocs(localDoc, incomingDoc);\n } else if (localDoc) {\n // Only in local: keep it\n mergedCollection[id] = localDoc;\n } else if (incomingDoc) {\n // Only in incoming: keep it\n mergedCollection[id] = incomingDoc;\n }\n }\n\n return {\n documents: mergedCollection,\n tombstones: mergedTombstones,\n };\n}\n","export class Emitter<T> {\n #listeners = new Set<(event: T) => void>();\n\n subscribe(listener: (event: T) => void) {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n emit(event: T) {\n // Copy the listeners to avoid calling listeners that are added during the emit.\n const listeners = Array.from(this.#listeners);\n for (const listener of listeners) {\n listener(event);\n }\n }\n}\n","import type { Document } from \"../core\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Validates input data against a schema.\n * Accepts `unknown` input to allow safe validation of untyped data.\n * The schema's runtime validation will ensure type safety.\n */\nexport function validate<T extends StandardSchemaV1>(\n schema: T,\n input: unknown,\n): StandardSchemaV1.InferOutput<T> {\n const result = schema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n throw new TypeError(\"Schema validation must be synchronous\");\n }\n\n if (result.issues) {\n throw new Error(JSON.stringify(result.issues, null, 2));\n }\n\n return result.value;\n}\n\n/**\n * Base type constraint for any standard schema object\n */\nexport type AnyObject = StandardSchemaV1<Record<string, any>>;\n\nexport type Output<T extends AnyObject> = StandardSchemaV1.InferOutput<T>;\n\nexport type Input<T extends AnyObject> = StandardSchemaV1.InferInput<T>;\n\nexport type CollectionDef<\n T extends Document = Document,\n Id extends string = string,\n S extends AnyObject = AnyObject,\n> = {\n readonly \"~docType\": T; // Phantom - document shape\n readonly \"~idType\": Id; // Phantom - ID type\n readonly \"~schemaType\": S; // Phantom - schema type for input inference\n schema: AnyObject; // Runtime only - used for validation\n getId: (data: T) => Id; // Properly typed, no contravariance issue\n};\n\n/**\n * Helper function to create a collection definition with proper type inference.\n * This captures the schema output type directly as the document type.\n *\n * @example\n * ```ts\n * const store = createStore({\n * users: define(userSchema, (data) => data.id),\n * });\n * ```\n */\nexport function define<S extends AnyObject, Id extends string = string>(\n schema: S,\n getId: (data: Output<S>) => Id,\n): CollectionDef<Output<S>, Id, S> {\n return {\n \"~docType\": undefined as unknown as Output<S>, // Phantom\n \"~idType\": undefined as unknown as Id, // Phantom\n \"~schemaType\": undefined as unknown as S, // Phantom\n schema,\n getId,\n };\n}\n\n/**\n * Extract the document type from a CollectionDef\n */\nexport type DocType<C extends CollectionDef> = C[\"~docType\"];\n\n/**\n * Extract the ID type from a CollectionDef\n */\nexport type IdType<C extends CollectionDef> = C[\"~idType\"];\n\n/**\n * Extract the input type from a CollectionDef's schema\n * This allows put() to accept partial data when schemas have defaults\n */\nexport type InputType<C extends CollectionDef> =\n C extends CollectionDef<any, any, infer S> ? Input<S> : never;\n\n/**\n * Configuration for all collections in a store.\n * All collection definitions must be created using the `collection()` helper.\n *\n * Uses `any` for type parameters to allow any CollectionDef to be stored,\n * while still preserving specific types through inference in createStore.\n */\nexport type StoreConfig = Record<string, CollectionDef<any, any, any>>;\n\n/**\n * Valid collection name from a store config\n */\nexport type CollectionName<T extends StoreConfig> = keyof T & string;\n","import {\n createReadLens,\n isDeleted,\n mergeDocs,\n type AtomizedDocument,\n type Document,\n type Collection,\n type Stamp,\n type Tombstones,\n atomize,\n advanceClock,\n type StoreState,\n mergeCollections,\n} from \"../core\";\nimport type { StoreConfig } from \"./schema\";\nimport type { StoreChangeEvent } from \"./types\";\n\nexport function doGet<T extends Document>(\n docs: Collection<T>,\n tombstones: Tombstones,\n id: string,\n): T | undefined {\n if (isDeleted(id, tombstones)) return undefined;\n const current = docs[id];\n if (!current) return undefined;\n return createReadLens<T>(current);\n}\n\nexport function doList<T extends Document>(\n docs: Collection<T>,\n tombstones: Tombstones,\n where?: (doc: T) => boolean,\n): T[] {\n const results: T[] = [];\n for (const [id, document] of Object.entries(docs)) {\n if (!isDeleted(id, tombstones)) {\n const doc = createReadLens<T>(document);\n if (!where || where(doc)) {\n results.push(doc);\n }\n }\n }\n return results;\n}\n\nexport function doPut<T extends Document>(\n docs: Collection<T>,\n tombstones: Tombstones,\n data: T,\n stamp: Stamp,\n validateFn: (data: unknown) => T,\n getId: (data: T) => string,\n): T {\n const validated = validateFn(data);\n const id = getId(validated);\n if (tombstones[id]) {\n delete tombstones[id]; // Revive\n }\n docs[id] = atomize<T>(validated, stamp);\n return createReadLens<T>(docs[id]!);\n}\n\nexport function doPatch<T extends Document>(\n docs: Collection<T>,\n id: string,\n data: Partial<T>,\n stamp: Stamp,\n validateFn: (data: unknown) => T,\n): T {\n const current = docs[id];\n if (!current) {\n throw new Error(`Cannot patch non-existent document \"${id}\"`);\n }\n\n const changes = atomize<Partial<T>>(data, stamp) as Partial<AtomizedDocument<T>>;\n const merged = mergeDocs(current, changes);\n const plain = createReadLens<T>(merged);\n validateFn(plain);\n docs[id] = merged;\n return plain;\n}\n\nexport function doRemove(\n docs: Collection<Document>,\n tombstones: Tombstones,\n id: string,\n stamp: Stamp,\n): void {\n tombstones[id] = stamp;\n delete docs[id];\n}\n\nexport function mergeState<T extends StoreConfig>(\n currentState: StoreState,\n snapshot: StoreState,\n config: T,\n): StoreChangeEvent<T> {\n const diff = {} as StoreChangeEvent<T>;\n currentState.clock = advanceClock(currentState.clock, snapshot.clock);\n\n for (const [name, incomingCollectionState] of Object.entries(snapshot.collections)) {\n const localCollectionState = currentState.collections[name] ?? {\n documents: {},\n tombstones: {},\n };\n currentState.collections[name] = mergeCollections(\n localCollectionState,\n incomingCollectionState,\n );\n // Only mark collections that exist in the config\n if (name in config) {\n (diff as Record<string, true>)[name] = true;\n }\n }\n\n return diff;\n}\n","import {\n advanceClock,\n makeStamp,\n atomize,\n createReadLens,\n mergeDocs,\n type CollectionState,\n type StoreState,\n type Document,\n type Stamp,\n type AtomizedDocument,\n} from \"../core\";\nimport { Emitter } from \"../emitter\";\nimport type { CollectionName, DocType, IdType, InputType, StoreConfig } from \"./schema\";\nimport { validate } from \"./schema\";\nimport type { StoreChangeEvent } from \"./types\";\nimport { doGet, doList, doPatch, doPut, doRemove, mergeState } from \"./operations\";\n\nexport type ListOptions<T> = {\n where?: (doc: T) => boolean;\n};\n\nexport type TransactionAPI<T extends StoreConfig> = {\n get<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): DocType<T[N]> | undefined;\n list<N extends CollectionName<T>>(\n collection: N,\n options?: ListOptions<DocType<T[N]>>,\n ): DocType<T[N]>[];\n put<N extends CollectionName<T>>(collection: N, data: InputType<T[N]>): DocType<T[N]>;\n patch<N extends CollectionName<T>>(\n collection: N,\n id: IdType<T[N]>,\n data: Partial<DocType<T[N]>>,\n ): DocType<T[N]>;\n remove<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): void;\n};\n\nexport class Store<T extends StoreConfig> {\n #config: T;\n #emitter: Emitter<StoreChangeEvent<T>>;\n #state: StoreState;\n\n constructor(config: T) {\n this.#config = config;\n this.#emitter = new Emitter<StoreChangeEvent<T>>();\n this.#state = {\n clock: { ms: Date.now(), seq: 0 },\n collections: {},\n };\n\n // Initialize collections\n for (const collectionName of Object.keys(config) as CollectionName<T>[]) {\n this.#state.collections[collectionName] = {\n documents: {},\n tombstones: {},\n };\n }\n }\n\n #getNextStamp(): Stamp {\n this.#state.clock = advanceClock(this.#state.clock, { ms: Date.now(), seq: 0 });\n return makeStamp(this.#state.clock.ms, this.#state.clock.seq);\n }\n\n #getCollection<N extends CollectionName<T>>(collection: N): CollectionState<Document> {\n if (!(collection in this.#config)) {\n throw new Error(`Collection \"${collection}\" not found`);\n }\n return this.#state.collections[collection]!;\n }\n\n get<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): DocType<T[N]> | undefined {\n const col = this.#getCollection(collection);\n return doGet(col.documents, col.tombstones, id);\n }\n\n list<N extends CollectionName<T>>(\n collection: N,\n options?: ListOptions<DocType<T[N]>>,\n ): DocType<T[N]>[] {\n const col = this.#getCollection(collection);\n return doList(col.documents, col.tombstones, options?.where);\n }\n\n put<N extends CollectionName<T>>(collection: N, data: InputType<T[N]>): DocType<T[N]> {\n const col = this.#getCollection(collection);\n const collectionConfig = this.#config[collection]!;\n const result = doPut(\n col.documents,\n col.tombstones,\n data as Document,\n this.#getNextStamp(),\n (d: unknown) => validate(collectionConfig.schema, d),\n (d: DocType<T[typeof collection]>) => collectionConfig.getId(d),\n );\n this.#emitter.emit({ [collection]: true } as StoreChangeEvent<T>);\n return result;\n }\n\n patch<N extends CollectionName<T>>(\n collection: N,\n id: IdType<T[N]>,\n data: Partial<DocType<T[N]>>,\n ): DocType<T[N]> {\n const col = this.#getCollection(collection);\n const collectionConfig = this.#config[collection]!;\n const result = doPatch(col.documents, id, data, this.#getNextStamp(), (d: unknown) =>\n validate(collectionConfig.schema, d),\n );\n this.#emitter.emit({ [collection]: true } as StoreChangeEvent<T>);\n return result;\n }\n\n remove<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): void {\n const col = this.#getCollection(collection);\n doRemove(col.documents, col.tombstones, id, this.#getNextStamp());\n this.#emitter.emit({ [collection]: true } as StoreChangeEvent<T>);\n }\n\n subscribe(callback: (event: StoreChangeEvent<T>) => void): () => void {\n return this.#emitter.subscribe(callback);\n }\n\n getState(): StoreState {\n return { ...this.#state };\n }\n\n merge(snapshot: StoreState): StoreChangeEvent<T> {\n const diff = mergeState(this.#state, snapshot, this.#config);\n this.#emitter.emit(diff);\n return diff;\n }\n\n transact<R>(callback: (tx: TransactionAPI<T>) => R): R {\n // Track document-level modifications per collection\n type CollectionModifications = {\n modifiedDocs: Record<string, AtomizedDocument<Document>>;\n modifiedTombstones: Set<string>;\n revivedDocs: Set<string>;\n };\n\n const modifications: Record<string, CollectionModifications> = {};\n const event = {} as StoreChangeEvent<T>;\n\n // Helper to initialize modification tracking for a collection\n const ensureModifications = (name: string): CollectionModifications => {\n if (!(name in this.#config)) {\n throw new Error(`Collection \"${name}\" not found`);\n }\n if (!modifications[name]) {\n modifications[name] = {\n modifiedDocs: {},\n modifiedTombstones: new Set(),\n revivedDocs: new Set(),\n };\n }\n return modifications[name]!;\n };\n\n // Create transaction API\n const tx: TransactionAPI<T> = {\n get: (collection, id) => {\n // Validate collection exists\n if (!(collection in this.#config)) {\n throw new Error(`Collection \"${collection}\" not found`);\n }\n\n // Check modifications first for uncommitted writes\n const mods = modifications[collection];\n if (mods) {\n if (mods.modifiedDocs[id]) {\n return createReadLens(mods.modifiedDocs[id]!);\n }\n if (mods.modifiedTombstones.has(id)) {\n return undefined; // Deleted in transaction\n }\n }\n // Fallback to main state\n const col = this.#state.collections[collection]!;\n return doGet(col.documents, col.tombstones, id);\n },\n\n list: (collection, options) => {\n // Validate collection exists\n if (!(collection in this.#config)) {\n throw new Error(`Collection \"${collection}\" not found`);\n }\n\n const col = this.#state.collections[collection]!;\n const mods = modifications[collection];\n const results: DocType<T[typeof collection]>[] = [];\n\n // Collect all IDs to process\n const allIds = new Set<string>();\n for (const id in col.documents) allIds.add(id);\n if (mods) {\n for (const id in mods.modifiedDocs) allIds.add(id);\n }\n\n // Build result list with filtering\n for (const id of allIds) {\n // Skip if tombstoned\n if (mods?.modifiedTombstones.has(id)) continue;\n if (!mods?.revivedDocs.has(id) && col.tombstones[id]) continue;\n\n // Use modified doc if available, otherwise main state\n const doc = mods?.modifiedDocs[id] ?? col.documents[id];\n if (doc) {\n const plainDoc = createReadLens(doc);\n if (!options?.where || options.where(plainDoc)) {\n results.push(plainDoc);\n }\n }\n }\n\n return results;\n },\n\n put: (collection, data) => {\n const mods = ensureModifications(collection);\n const collectionConfig = this.#config[collection]!;\n const col = this.#state.collections[collection]!;\n event[collection] = true;\n\n // Validate and extract ID\n const validated = validate(collectionConfig.schema, data);\n const id = collectionConfig.getId(validated);\n\n // Handle tombstone revival\n if (col.tombstones[id]) {\n mods.revivedDocs.add(id);\n mods.modifiedTombstones.delete(id);\n }\n\n // Create new atomized document\n mods.modifiedDocs[id] = atomize(validated, this.#getNextStamp());\n return createReadLens(mods.modifiedDocs[id]!);\n },\n\n patch: (collection, id, data) => {\n const mods = ensureModifications(collection);\n const collectionConfig = this.#config[collection]!;\n const col = this.#state.collections[collection]!;\n event[collection] = true;\n\n // Get current document (from modifications or main state)\n let current = mods.modifiedDocs[id];\n if (!current) {\n current = col.documents[id];\n if (!current) {\n throw new Error(`Cannot patch non-existent document \"${id}\"`);\n }\n // Clone document into modifications\n mods.modifiedDocs[id] = structuredClone(current);\n }\n\n // Apply patch using field-level merge\n const changes = atomize(data, this.#getNextStamp()) as Partial<\n AtomizedDocument<DocType<T[typeof collection]>>\n >;\n const merged = mergeDocs(mods.modifiedDocs[id]!, changes);\n\n // Validate merged result\n const plain = createReadLens(merged);\n validate(collectionConfig.schema, plain);\n\n mods.modifiedDocs[id] = merged;\n return plain;\n },\n\n remove: (collection, id) => {\n const mods = ensureModifications(collection);\n event[collection] = true;\n\n mods.modifiedTombstones.add(id);\n delete mods.modifiedDocs[id]; // Remove if was newly created\n },\n };\n\n // Execute callback\n const result = callback(tx);\n\n // Commit: merge modifications into main state\n for (const [name, mods] of Object.entries(modifications)) {\n const col = this.#state.collections[name]!;\n\n // Merge modified documents\n for (const [id, doc] of Object.entries(mods.modifiedDocs)) {\n col.documents[id] = doc;\n }\n\n // Apply tombstones\n for (const id of mods.modifiedTombstones) {\n col.tombstones[id] = this.#getNextStamp();\n delete col.documents[id];\n }\n\n // Revive documents (remove tombstones)\n for (const id of mods.revivedDocs) {\n delete col.tombstones[id];\n }\n }\n\n // Emit event (only for mutated collections)\n if (Object.keys(event).length > 0) {\n this.#emitter.emit(event);\n }\n\n return result;\n }\n}\n\nexport function createStore<T extends StoreConfig>(config: T): Store<T> {\n return new Store(config);\n}\n","import type { StoreState } from \"../core\";\n\nexport type BroadcastSyncOptions = {\n channelName: string;\n onMessage: (state: StoreState) => void;\n};\n\n/**\n * Cross-tab synchronization via BroadcastChannel.\n * Handles environments where BroadcastChannel is unavailable.\n */\nexport class BroadcastSync {\n #channel: globalThis.BroadcastChannel | null = null;\n #onMessage: (state: StoreState) => void;\n\n constructor(options: BroadcastSyncOptions) {\n this.#onMessage = options.onMessage;\n\n try {\n this.#channel = new globalThis.BroadcastChannel(options.channelName);\n this.#channel.onmessage = (event: MessageEvent) => {\n if (event.data?.type === \"state-update\" && event.data?.state) {\n try {\n const incomingState = event.data.state as StoreState;\n this.#onMessage(incomingState);\n } catch (error) {\n console.warn(\"[BroadcastSync] Failed to process message:\", error);\n }\n }\n };\n } catch (error) {\n // BroadcastChannel may not be available in some environments\n console.warn(\"[BroadcastSync] BroadcastChannel not available:\", error);\n }\n }\n\n /**\n * Broadcast state to other tabs. Does nothing if BroadcastChannel is unavailable.\n */\n broadcast(state: StoreState): void {\n if (!this.#channel) return;\n\n try {\n this.#channel.postMessage({\n type: \"state-update\",\n state,\n });\n } catch (error) {\n console.warn(\"[BroadcastSync] Failed to broadcast:\", error);\n }\n }\n\n /**\n * Close the BroadcastChannel and clean up resources.\n */\n close(): void {\n if (this.#channel) {\n this.#channel.close();\n this.#channel = null;\n }\n }\n\n /**\n * Check if BroadcastChannel is available and connected.\n */\n get available(): boolean {\n return this.#channel !== null;\n }\n}\n","import type { StoreConfig } from \"../store/schema\";\nimport type { Store } from \"../store\";\nimport type { StoreState } from \"../core\";\nimport { Emitter } from \"../emitter\";\nimport { BroadcastSync } from \"./broadcast-sync\";\n\nexport interface IPersister {\n init(): Promise<void>;\n dispose(): Promise<void>;\n}\n\nexport type PersistenceOptions = {\n key: string;\n storeName?: string;\n debounceMs?: number;\n channelName?: string;\n serialize?: (state: StoreState) => string;\n deserialize?: (serialized: string) => StoreState;\n};\n\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst DB_VERSION = 1;\nconst DEFAULT_STORE_NAME = \"state\";\nconst STORE_KEY = \"store\";\n\nexport class IdbPersister<T extends StoreConfig> implements IPersister {\n #store: Store<T>;\n #config: Required<PersistenceOptions>;\n #debounceTimer: ReturnType<typeof setTimeout> | null = null;\n #sync: BroadcastSync;\n #emitter: Emitter<string>;\n #unsubscribe: (() => void) | null = null;\n #db: IDBDatabase | null = null;\n\n constructor(store: Store<T>, options: PersistenceOptions) {\n this.#store = store;\n this.#config = {\n key: options.key,\n debounceMs: options.debounceMs ?? DEFAULT_DEBOUNCE_MS,\n storeName: options.storeName ?? DEFAULT_STORE_NAME,\n serialize: options.serialize ?? ((s) => JSON.stringify(s)),\n deserialize: options.deserialize ?? ((s) => JSON.parse(s) as StoreState),\n channelName: options.channelName ?? `starling:${options.key}`,\n };\n this.#emitter = new Emitter<string>();\n this.#sync = new BroadcastSync({\n channelName: this.#config.channelName,\n onMessage: (state) => this.#store.merge(state),\n });\n }\n\n async #load(): Promise<StoreState | null> {\n const db = this.#db;\n if (!db) {\n return null;\n }\n\n try {\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([this.#config.storeName], \"readonly\");\n const store = transaction.objectStore(this.#config.storeName);\n const request = store.get(STORE_KEY);\n\n request.onerror = () => {\n reject(new Error(`Failed to load state: ${request.error?.message ?? \"Unknown error\"}`));\n };\n\n request.onsuccess = () => {\n const result = request.result;\n if (result) {\n try {\n const state = this.#config.deserialize(result);\n resolve(state);\n } catch (error) {\n console.warn(\"[Starling Persistence] Failed to deserialize state:\", error);\n resolve(null);\n }\n } else {\n resolve(null);\n }\n };\n });\n } catch {\n // Handle errors gracefully - return null if load fails\n return null;\n }\n }\n\n async #save(serialized: string): Promise<void> {\n const db = this.#db;\n if (!db) {\n throw new Error(\"IndexedDB not initialized. Call init() first.\");\n }\n\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([this.#config.storeName], \"readwrite\");\n const store = transaction.objectStore(this.#config.storeName);\n const request = store.put(serialized, STORE_KEY);\n\n request.onerror = () => {\n reject(new Error(`Failed to save state: ${request.error?.message ?? \"Unknown error\"}`));\n };\n\n request.onsuccess = () => {\n resolve();\n };\n });\n }\n\n async #persist(): Promise<void> {\n try {\n const state = this.#store.getState();\n const serialized = this.#config.serialize(state);\n\n // Save to IndexedDB\n await this.#save(serialized);\n\n // Notify broadcast channel (other tabs)\n this.#sync.broadcast(state);\n\n // Notify listeners\n this.#emitter.emit(serialized);\n } catch (error) {\n console.warn(\"[Starling Persistence] Failed to save state:\", error);\n }\n }\n\n #openDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(this.#config.key, DB_VERSION);\n\n request.onerror = () => {\n reject(new Error(`Failed to open IndexedDB: ${request.error?.message ?? \"Unknown error\"}`));\n };\n\n request.onsuccess = () => {\n resolve(request.result);\n };\n\n request.onupgradeneeded = (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(this.#config.storeName)) {\n db.createObjectStore(this.#config.storeName);\n }\n };\n });\n }\n\n async init(): Promise<void> {\n // Open IndexedDB connection\n try {\n this.#db = await this.#openDB();\n } catch (error) {\n console.warn(\"[Starling Persistence] Failed to open IndexedDB:\", error);\n // Continue without DB - operations will fail gracefully\n }\n\n // Load state from storage\n try {\n const savedState = await this.#load();\n if (savedState) {\n this.#store.merge(savedState);\n }\n } catch (error) {\n console.warn(\"[Starling Persistence] Failed to load state:\", error);\n }\n\n // Subscribe to store changes after loading (so initial load doesn't trigger save)\n this.#unsubscribe = this.#store.subscribe(() => {\n if (this.#debounceTimer) {\n clearTimeout(this.#debounceTimer);\n }\n this.#debounceTimer = setTimeout(\n () => {\n this.#debounceTimer = null;\n void this.#persist();\n },\n this.#config.debounceMs,\n );\n });\n }\n\n async dispose(): Promise<void> {\n // Unsubscribe first to prevent new changes during cleanup\n if (this.#unsubscribe) {\n this.#unsubscribe();\n this.#unsubscribe = null;\n }\n\n // Cancel any pending debounced write\n if (this.#debounceTimer) {\n clearTimeout(this.#debounceTimer);\n this.#debounceTimer = null;\n }\n\n // Persist the latest state before closing (only if DB is initialized)\n if (this.#db) {\n await this.#persist();\n }\n\n // Close IndexedDB connection\n if (this.#db) {\n this.#db.close();\n this.#db = null;\n }\n\n // Close cross-tab sync\n this.#sync.close();\n }\n\n subscribe(listener: (serialized: string) => void): () => void {\n return this.#emitter.subscribe(listener);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["result: Record<string, T>","result: Record<string, unknown>","current: Record<string, unknown>","result: Record<string, string> | undefined","merged: Record<string, Atom<unknown>> | undefined","mergedDocuments: Record<string, AtomizedDocument> | undefined","puts: string[] | undefined","patches: string[] | undefined","removes: string[] | undefined","changes: CollectionChangeEvent","flat: Record<string, unknown>","state: CollectionState","results: Document[]","merged: AtomizedDocument","cols: Record<string, Collection>","pendingEvent: StoreChangeEvent<T> | null","txSnapshots: Map<string, CollectionState> | null","colStates: Record<string, CollectionState>"],"sources":["../lib/clock.ts","../lib/emitter.ts","../lib/query.ts","../lib/flat.ts","../lib/crdt.ts","../lib/collection.ts","../lib/store.ts"],"sourcesContent":["function toHex(value: number, padLength: number): string {\n return value.toString(16).padStart(padLength, \"0\");\n}\n\nconst MS_LENGTH = 12;\nconst SEQ_LENGTH = 6;\n\nexport type Clock = {\n ms: number;\n seq: number;\n};\n\n/**\n * HLC timestamp.\n * Lexicographically sortable string: `${ms}@${seq}@${deviceId}`.\n * ms and seq are zero-padded hex; deviceId is appended as-is.\n */\nexport type Stamp = string;\n\nexport function advanceClock(current: Clock, next: Clock): Clock {\n if (next.ms > current.ms) {\n return { ms: next.ms, seq: next.seq };\n } else if (next.ms === current.ms) {\n return { ms: current.ms, seq: Math.max(current.seq, next.seq) + 1 };\n } else {\n return { ms: current.ms, seq: current.seq + 1 };\n }\n}\n\nexport function makeStamp(ms: number, seq: number, deviceId: string): Stamp {\n return `${toHex(ms, MS_LENGTH)}@${toHex(seq, SEQ_LENGTH)}@${deviceId}`;\n}\n","export type Emitter<T> = {\n subscribe(listener: (event: T) => void): () => void;\n emit(event: T): void;\n};\n\nexport function createEmitter<T>(): Emitter<T> {\n const listeners = new Set<(event: T) => void>();\n\n return {\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n emit(event) {\n for (const listener of Array.from(listeners)) {\n listener(event);\n }\n },\n };\n}\n","import { createEmitter } from \"./emitter.ts\";\nimport type { Store, StoreConfig, CollectionName } from \"./store.ts\";\n\nexport type Query<R> = {\n readonly result: R;\n subscribe(cb: (result: R) => void): () => void;\n dispose(): void;\n};\n\nexport function createQuery<T extends StoreConfig, R>(\n store: Store<T>,\n predicate: (store: Store<T>) => R,\n dependencies: CollectionName<T>[],\n): Query<R> {\n const emitter = createEmitter<R>();\n const disposers = new Set<() => void>();\n let result = predicate(store);\n\n const unsub = store.subscribe((e) => {\n if (dependencies.some((dep) => !!e[dep])) {\n result = predicate(store);\n emitter.emit(result);\n }\n });\n\n return {\n get result() {\n return result;\n },\n subscribe(cb) {\n const disposer = emitter.subscribe(cb);\n disposers.add(disposer);\n return () => {\n disposer();\n disposers.delete(disposer);\n };\n },\n dispose() {\n for (const disposer of disposers) {\n disposer();\n }\n disposers.clear();\n unsub();\n },\n };\n}\n","function isPlainObject(value: unknown): value is Record<string, unknown> {\n return Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\nexport function flatten<T = unknown>(\n obj: Record<string, unknown>,\n transform?: (value: unknown) => T,\n): Record<string, T> {\n const result: Record<string, T> = {};\n\n function recurse(current: Record<string, unknown>, prefix: string): void {\n const keys = Object.keys(current);\n\n if (keys.length === 0) {\n result[prefix] = (transform ? transform(current) : current) as T;\n return;\n }\n\n for (const key of keys) {\n const path = prefix ? `${prefix}.${key}` : key;\n const value = current[key];\n\n if (isPlainObject(value) && !Array.isArray(value)) {\n recurse(value, path);\n } else {\n result[path] = (transform ? transform(value) : value) as T;\n }\n }\n }\n\n recurse(obj, \"\");\n return result;\n}\n\nexport function unflatten(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const key of Object.keys(obj)) {\n const parts = key.split(\".\");\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n\n if (part === \"__proto__\") break;\n\n if (i === parts.length - 1) {\n current[part] = obj[key];\n } else {\n const next = parts[i + 1]!;\n const useArray = /^\\d+$/.test(next);\n\n if (current[part] === undefined) {\n current[part] = useArray ? [] : {};\n }\n current = current[part] as Record<string, unknown>;\n }\n }\n }\n\n return result;\n}\n","import type { Clock } from \"./clock.ts\";\nimport { flatten, unflatten } from \"./flat.ts\";\n\nexport type Document = Record<string, unknown>;\n\nexport type Atom<T> = {\n \"~v\": T;\n \"~ts\": string;\n};\n\nexport type AtomizedDocument = Record<string, Atom<unknown>>;\n\nexport type Tombstones = Record<string, string>;\n\nexport type CollectionState = {\n documents: Record<string, AtomizedDocument>;\n tombstones: Tombstones;\n};\n\nexport type StoreState = {\n clock: Clock;\n collections: Record<string, CollectionState>;\n};\n\nexport type CollectionChangeEvent = {\n put: string[];\n patch: string[];\n remove: string[];\n};\n\nexport function atomize(data: Document, timestamp: string): AtomizedDocument {\n return flatten(data, (value) => ({ \"~v\": value, \"~ts\": timestamp }));\n}\n\nexport function mergeTombstones(target: Tombstones, source: Tombstones): Tombstones {\n let result: Record<string, string> | undefined;\n\n for (const [key, stamp] of Object.entries(source)) {\n const existing = target[key];\n if (!existing || stamp > existing) {\n if (!result) result = { ...target };\n result[key] = stamp;\n }\n }\n\n return result ?? target;\n}\n\nexport function mergeDocs(local: AtomizedDocument, incoming: AtomizedDocument): AtomizedDocument {\n let merged: Record<string, Atom<unknown>> | undefined;\n\n for (const key of Object.keys(local)) {\n const incomingAtom = incoming[key];\n if (!incomingAtom) continue;\n\n if (incomingAtom[\"~ts\"] > local[key]![\"~ts\"]) {\n if (!merged) merged = { ...local };\n merged[key] = incomingAtom;\n }\n }\n\n return merged ?? local;\n}\n\nexport function mergeCollections(\n local: CollectionState,\n incoming: CollectionState,\n): { state: CollectionState; changes: CollectionChangeEvent | undefined } {\n const mergedTombstones = mergeTombstones(local.tombstones, incoming.tombstones);\n let mergedDocuments: Record<string, AtomizedDocument> | undefined;\n let puts: string[] | undefined;\n let patches: string[] | undefined;\n let removes: string[] | undefined;\n\n const allDocumentIds = new Set([\n ...Object.keys(local.documents),\n ...Object.keys(incoming.documents),\n ]);\n\n for (const id of allDocumentIds) {\n if (mergedTombstones[id]) {\n if (local.documents[id]) {\n if (!mergedDocuments) mergedDocuments = { ...local.documents };\n delete mergedDocuments[id];\n (removes ??= []).push(id);\n }\n continue;\n }\n\n const localDoc = local.documents[id];\n const incomingDoc = incoming.documents[id];\n\n if (localDoc && incomingDoc) {\n const merged = mergeDocs(localDoc, incomingDoc);\n if (merged !== localDoc) {\n if (!mergedDocuments) mergedDocuments = { ...local.documents };\n mergedDocuments[id] = merged;\n (patches ??= []).push(id);\n }\n } else if (incomingDoc) {\n if (!mergedDocuments) mergedDocuments = { ...local.documents };\n mergedDocuments[id] = incomingDoc;\n (puts ??= []).push(id);\n }\n }\n\n if (mergedTombstones === local.tombstones && mergedDocuments === undefined) {\n return { state: local, changes: undefined };\n }\n\n const changes: CollectionChangeEvent = {\n put: puts ?? [],\n patch: patches ?? [],\n remove: removes ?? [],\n };\n\n return {\n state: {\n documents: mergedDocuments ?? local.documents,\n tombstones: mergedTombstones,\n },\n changes,\n };\n}\n\nexport function readDoc<T extends Document>(doc: AtomizedDocument): T {\n const flat: Record<string, unknown> = {};\n for (const key of Object.keys(doc)) {\n flat[key] = doc[key]![\"~v\"];\n }\n return unflatten(flat) as T;\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport {\n atomize,\n readDoc,\n type AtomizedDocument,\n type CollectionState,\n type Document,\n} from \"./crdt.ts\";\n\nexport type CollectionDef<\n Input = unknown,\n Output extends Document = Document,\n KeyPath extends keyof Output & string = string,\n> = {\n keyPath: KeyPath;\n schema?: StandardSchemaV1<Input, Output>;\n};\n\nexport function validateSchema(\n schema: StandardSchemaV1<unknown, Document>,\n data: unknown,\n): Document {\n const result = schema[\"~standard\"].validate(data);\n if (result instanceof Promise) {\n throw new TypeError(\"Schema validation must be synchronous\");\n }\n if (result.issues) {\n throw new Error(result.issues.map((i) => i.message).join(\"; \"));\n }\n return result.value;\n}\n\nexport type Collection = {\n get(id: string): Document | undefined;\n list(where?: (doc: Document) => boolean): Document[];\n put(data: Document): { id: string; doc: Document };\n patch(id: string, data: Partial<Document>): { id: string; doc: Document };\n remove(id: string): void;\n getState(): CollectionState;\n setState(state: CollectionState): void;\n snapshot(): CollectionState;\n restore(snapshot: CollectionState): void;\n};\n\nexport function createCollection(\n def: CollectionDef<any, any, any>,\n getStamp: () => string,\n): Collection {\n let state: CollectionState = {\n documents: {},\n tombstones: {},\n };\n\n return {\n get(id) {\n if (state.tombstones[id]) return undefined;\n const current = state.documents[id];\n if (!current) return undefined;\n return readDoc(current);\n },\n\n list(where) {\n const results: Document[] = [];\n for (const [id, document] of Object.entries(state.documents)) {\n if (!state.tombstones[id]) {\n const doc = readDoc(document);\n if (!where || where(doc)) {\n results.push(doc);\n }\n }\n }\n return results;\n },\n\n put(data) {\n const toPut = def.schema ? validateSchema(def.schema, data as unknown) : data;\n const id = toPut[def.keyPath] as string;\n if (state.tombstones[id]) {\n delete state.tombstones[id];\n }\n const stamp = getStamp();\n state.documents[id] = atomize(toPut, stamp);\n return { id, doc: readDoc(state.documents[id]!) };\n },\n\n patch(id, data) {\n const prev = state.documents[id];\n if (!prev) {\n throw new Error(`Cannot patch non-existent document \"${id}\"`);\n }\n const stamp = getStamp();\n const changes = atomize(data, stamp);\n const merged: AtomizedDocument = { ...prev, ...changes };\n state.documents[id] = merged;\n const result = readDoc(merged);\n if (def.schema) {\n try {\n validateSchema(def.schema, result as unknown);\n } catch (e) {\n state.documents[id] = prev;\n throw e;\n }\n }\n return { id, doc: result };\n },\n\n remove(id) {\n state.tombstones[id] = getStamp();\n delete state.documents[id];\n },\n\n getState() {\n return state;\n },\n\n setState(newState) {\n state = newState;\n },\n\n snapshot() {\n return structuredClone(state);\n },\n\n restore(snap) {\n state = snap;\n },\n };\n}\n","import { advanceClock, makeStamp } from \"./clock.ts\";\nimport {\n mergeCollections,\n type CollectionChangeEvent,\n type CollectionState,\n type Document,\n type StoreState,\n} from \"./crdt.ts\";\nimport { createCollection, type Collection } from \"./collection.ts\";\nimport { createEmitter } from \"./emitter.ts\";\n\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { type CollectionDef } from \"./collection.ts\";\n\nexport type { CollectionDef } from \"./collection.ts\";\n\nexport type DocType<C> = C extends CollectionDef<any, infer T, any> ? T : never;\n\nexport type InputType<C> = C extends CollectionDef<infer I, any, any> ? I : never;\n\nexport type StoreConfig = Record<string, CollectionDef<any, any, any>>;\n\nexport type CollectionName<T extends StoreConfig> = keyof T & string;\n\nexport type CollectionAPI<C extends CollectionDef> = {\n get(id: string): DocType<C> | undefined;\n list(options?: { where?: (doc: DocType<C>) => boolean }): DocType<C>[];\n put(data: InputType<C>): DocType<C>;\n patch(id: string, data: Partial<DocType<C>>): DocType<C>;\n remove(id: string): void;\n};\n\nexport type StoreChangeEvent<T extends StoreConfig> = {\n [K in keyof T]?: CollectionChangeEvent;\n};\n\nexport type ListOptions<T> = {\n where?: (doc: T) => boolean;\n};\n\nexport type TransactionAPI<T extends StoreConfig> = {\n [K in CollectionName<T>]: CollectionAPI<T[K]>;\n};\n\nexport type Store<T extends StoreConfig> = {\n [K in CollectionName<T>]: CollectionAPI<T[K]>;\n} & {\n subscribe(callback: (event: StoreChangeEvent<T>) => void): () => void;\n getState(): StoreState;\n merge(snapshot: StoreState): StoreChangeEvent<T>;\n transact<R>(callback: (tx: TransactionAPI<T>) => R): R;\n};\n\ntype StringKeyOf<T> = {\n [K in keyof T & string]: T[K] extends string ? K : never;\n}[keyof T & string];\n\nconst RESERVED_NAMES = new Set([\"subscribe\", \"getState\", \"merge\", \"transact\"]);\n\nexport function createStore<T extends StoreConfig>(config: {\n deviceId: string;\n collections: T & {\n [K in keyof T & string]: T[K] extends { schema: StandardSchemaV1<any, infer O> }\n ? { keyPath: StringKeyOf<O> }\n : T[K];\n };\n}): Store<T>;\n\nexport function createStore<T extends Record<string, Document & { keyPath?: never }>>(config: {\n deviceId: string;\n collections: {\n [K in keyof T & string]: { keyPath: StringKeyOf<T[K]> };\n };\n}): Store<{ [K in keyof T & string]: CollectionDef<T[K], T[K], string> }>;\n\nexport function createStore<T extends StoreConfig>(config: {\n deviceId: string;\n collections: T;\n}): Store<T> {\n const { deviceId, collections } = config;\n\n for (const name of Object.keys(collections)) {\n if (RESERVED_NAMES.has(name)) {\n throw new Error(`Collection name \"${name}\" conflicts with a store method`);\n }\n }\n\n const emitter = createEmitter<StoreChangeEvent<T>>();\n let clock = { ms: Date.now(), seq: 0 };\n\n function getNextStamp(): string {\n clock = advanceClock(clock, { ms: Date.now(), seq: 0 });\n return makeStamp(clock.ms, clock.seq, deviceId);\n }\n\n const cols: Record<string, Collection> = {};\n for (const name of Object.keys(collections) as CollectionName<T>[]) {\n cols[name] = createCollection(collections[name]!, getNextStamp);\n }\n\n let transacting = false;\n let pendingEvent: StoreChangeEvent<T> | null = null;\n let txSnapshots: Map<string, CollectionState> | null = null;\n\n function getPendingChanges(name: CollectionName<T>): CollectionChangeEvent {\n let entry = pendingEvent![name];\n if (!entry) {\n entry = { put: [], patch: [], remove: [] };\n pendingEvent![name] = entry;\n }\n return entry;\n }\n\n function recordChange(name: CollectionName<T>, type: \"put\" | \"patch\" | \"remove\", id: string) {\n if (transacting) {\n getPendingChanges(name)[type].push(id);\n } else {\n emitter.emit({\n [name]: { put: [], patch: [], remove: [], [type]: [id] },\n } as unknown as StoreChangeEvent<T>);\n }\n }\n\n function ensureSnapshot(name: string) {\n if (transacting && txSnapshots && !txSnapshots.has(name)) {\n txSnapshots.set(name, cols[name]!.snapshot());\n }\n }\n\n function buildCollectionAPI<N extends CollectionName<T>>(name: N): CollectionAPI<T[N]> {\n const col = cols[name]!;\n return {\n get(id) {\n return col.get(id) as any;\n },\n list(options) {\n return col.list(options?.where as ((doc: Document) => boolean) | undefined) as any;\n },\n put(data) {\n ensureSnapshot(name);\n const { id, doc } = col.put(data as Document);\n recordChange(name, \"put\", id);\n return doc as any;\n },\n patch(id, data) {\n ensureSnapshot(name);\n const { doc } = col.patch(id, data);\n recordChange(name, \"patch\", id);\n return doc as any;\n },\n remove(id) {\n ensureSnapshot(name);\n col.remove(id);\n recordChange(name, \"remove\", id);\n },\n };\n }\n\n const collectionAPIs = {} as { [K in CollectionName<T>]: CollectionAPI<T[K]> };\n\n for (const name of Object.keys(collections) as CollectionName<T>[]) {\n collectionAPIs[name] = buildCollectionAPI(name);\n }\n\n return Object.assign(collectionAPIs, {\n subscribe(callback: (event: StoreChangeEvent<T>) => void) {\n return emitter.subscribe(callback);\n },\n\n getState(): StoreState {\n const colStates: Record<string, CollectionState> = {};\n for (const name of Object.keys(cols)) {\n colStates[name] = cols[name]!.getState();\n }\n return structuredClone({ clock, collections: colStates });\n },\n\n merge(snapshot: StoreState) {\n const diff = {} as StoreChangeEvent<T>;\n clock = advanceClock(clock, snapshot.clock);\n\n for (const [name, incomingCollectionState] of Object.entries(snapshot.collections)) {\n const col = cols[name];\n const localCollectionState = col ? col.getState() : { documents: {}, tombstones: {} };\n const { state: mergedState, changes } = mergeCollections(\n localCollectionState,\n incomingCollectionState,\n );\n if (changes) {\n if (col) {\n col.setState(mergedState);\n }\n if (name in collections) {\n (diff as Record<string, CollectionChangeEvent>)[name] = changes;\n }\n }\n }\n\n if (Object.keys(diff).length > 0) {\n emitter.emit(diff);\n }\n\n return diff;\n },\n\n transact<R>(callback: (tx: TransactionAPI<T>) => R): R {\n txSnapshots = new Map();\n transacting = true;\n pendingEvent = {} as StoreChangeEvent<T>;\n\n try {\n const result = callback(collectionAPIs as TransactionAPI<T>);\n const event = pendingEvent;\n transacting = false;\n pendingEvent = null;\n txSnapshots = null;\n\n if (Object.keys(event).length > 0) {\n emitter.emit(event);\n }\n\n return result;\n } catch (e) {\n for (const [name, snap] of txSnapshots!) {\n cols[name]!.restore(snap);\n }\n transacting = false;\n pendingEvent = null;\n txSnapshots = null;\n throw e;\n }\n },\n }) as Store<T>;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byearlybird/starling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -14,35 +14,35 @@
|
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsdown",
|
|
19
|
+
"dev": "tsdown --watch",
|
|
20
|
+
"test": "bun test",
|
|
21
|
+
"test:watch": "bun test --watch",
|
|
22
|
+
"bench": "bun run bench:crdt && bun run bench:store",
|
|
23
|
+
"bench:crdt": "bun run lib/crdt.bench.ts",
|
|
24
|
+
"bench:store": "bun run lib/store.bench.ts",
|
|
25
|
+
"fmt": "oxfmt",
|
|
26
|
+
"fmt:check": "oxfmt --check",
|
|
27
|
+
"lint": "oxlint",
|
|
28
|
+
"lint:fix": "oxlint --fix",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "bun run build"
|
|
31
|
+
},
|
|
17
32
|
"dependencies": {
|
|
18
33
|
"@standard-schema/spec": "^1.1.0"
|
|
19
34
|
},
|
|
20
35
|
"devDependencies": {
|
|
21
36
|
"@types/bun": "^1.3.7",
|
|
22
37
|
"@types/node": "^22.10.5",
|
|
23
|
-
"fake-indexeddb": "^6.2.5",
|
|
24
38
|
"mitata": "^1.0.34",
|
|
25
39
|
"oxfmt": "^0.24.0",
|
|
26
40
|
"oxlint": "^1.39.0",
|
|
27
41
|
"tsdown": "^0.18.3",
|
|
28
42
|
"typescript": "^5.7.3",
|
|
29
|
-
"zod": "^4.
|
|
43
|
+
"zod": "^4.3.6"
|
|
30
44
|
},
|
|
31
45
|
"peerDependencies": {
|
|
32
46
|
"typescript": "^5"
|
|
33
|
-
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsdown",
|
|
36
|
-
"dev": "tsdown --watch",
|
|
37
|
-
"test": "bun test",
|
|
38
|
-
"test:watch": "bun test --watch",
|
|
39
|
-
"bench": "bun run bench:core && bun run bench:store",
|
|
40
|
-
"bench:core": "bun run lib/core/core.bench.ts",
|
|
41
|
-
"bench:store": "bun run lib/store/store.bench.ts",
|
|
42
|
-
"fmt": "oxfmt",
|
|
43
|
-
"fmt:check": "oxfmt --check",
|
|
44
|
-
"lint": "oxlint",
|
|
45
|
-
"lint:fix": "oxlint --fix",
|
|
46
|
-
"typecheck": "tsc --noEmit"
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|