@byearlybird/starling 0.13.0 → 0.13.1

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { atom, batched, computed, map } from "nanostores";
1
+ import { atom, batched, map } from "nanostores";
2
2
 
3
3
  //#region lib/store/schema.ts
4
4
  function validate(schema, input) {
@@ -297,7 +297,7 @@ function createStore(config) {
297
297
  ...collections,
298
298
  $snapshot,
299
299
  query: (collectionNames, callback) => {
300
- return computed(getCollectionDataStores(collectionNames), (...values) => {
300
+ return batched(getCollectionDataStores(collectionNames), (...values) => {
301
301
  const entries = collectionNames.map((name, i) => [name, values[i]]);
302
302
  return callback(Object.fromEntries(entries));
303
303
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["result: Record<string, R>","result: Record<string, unknown>","current: any","result: Document","result: Tombstones","mergeCollections","mergedDocuments: Record<DocumentId, Document>","mergeCollections","$state: ClockAtom","collectionSnapshotAtoms: ReadableAtom<Collection>[]","collectionsSnapshot: Record<string, Collection>"],"sources":["../lib/store/schema.ts","../lib/core/hex.ts","../lib/core/clock.ts","../lib/core/flatten.ts","../lib/core/document.ts","../lib/core/tombstone.ts","../lib/core/collection.ts","../lib/store/collection.ts","../lib/store/clock.ts","../lib/store/store.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport function validate<T extends StandardSchemaV1>(\n schema: T,\n input: StandardSchemaV1.InferInput<T>,\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 SchemaWithId<T extends AnyObject> =\n StandardSchemaV1.InferOutput<T> extends {\n id: any;\n }\n ? T\n : never;\n\nexport type Output<T extends AnyObject> = StandardSchemaV1.InferOutput<T>;\n\nexport type Input<T extends AnyObject> = StandardSchemaV1.InferInput<T>;\n","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;\n\nexport type Clock = {\n ms: number;\n seq: number;\n};\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): string {\n return `${toHex(ms, MS_LENGTH)}${toHex(seq, SEQ_LENGTH)}${nonce(NONCE_LENGTH)}`;\n}\n\nexport function parseStamp(stamp: string): { ms: number; seq: number } {\n return {\n ms: parseInt(stamp.slice(0, MS_LENGTH), 16),\n seq: parseInt(stamp.slice(MS_LENGTH, MS_LENGTH + SEQ_LENGTH), 16),\n };\n}\n","/**\n * Flattens a nested object into a flat object with dot-notation keys\n * @param obj - The object to flatten\n * @param mapper - Optional callback to transform leaf values\n * @returns A flattened object with dot-notation keys\n */\nexport function flatten<T, R = unknown>(\n obj: T,\n mapper?: (value: unknown, path: string) => R,\n): Record<string, R> {\n const result: Record<string, R> = {};\n\n const addLeaf = (value: unknown, path: string) => {\n if (path) {\n result[path] = mapper ? mapper(value, path) : (value as R);\n }\n };\n\n function traverse(current: unknown, prefix: string = \"\"): void {\n if (!shouldTraverse(current)) {\n addLeaf(current, prefix);\n return;\n }\n\n for (const [key, value] of Object.entries(current)) {\n const newPath = prefix ? `${prefix}.${key}` : key;\n traverse(value, newPath);\n }\n }\n\n traverse(obj);\n return result;\n}\n\n/**\n * Unflattens a flat object with dot-notation keys into a nested object\n * @param obj - The flattened object to unflatten\n * @param mapper - Optional callback to transform leaf values before placing them\n * @returns A nested object\n */\nexport function unflatten<T = unknown, R = unknown>(\n obj: Record<string, T>,\n mapper?: (value: T, path: string) => R,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [path, value] of Object.entries(obj)) {\n const keys = path.split(\".\");\n const mappedValue = mapper ? mapper(value, path) : value;\n\n let current: any = result;\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]!;\n if (!(key in current)) {\n current[key] = {};\n }\n current = current[key];\n }\n\n const finalKey = keys[keys.length - 1]!;\n current[finalKey] = mappedValue;\n }\n\n return result;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (value.constructor === Object || Object.getPrototypeOf(value) === null)\n );\n}\n\nfunction shouldTraverse(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && Object.keys(value).length > 0;\n}\n","import { flatten, unflatten } from \"./flatten\";\n\ntype Field<T = unknown> = {\n \"~value\": T;\n \"~stamp\": string;\n};\n\nexport type Document = Record<string, Field>;\n\nexport function makeDocument(\n fields: Record<string, any>,\n stamp: string,\n): Document {\n return flatten(fields, (value) => ({ \"~value\": value, \"~stamp\": stamp }));\n}\n\nexport function parseDocument(document: Document): Record<string, any> {\n return unflatten(document, (field) => field[\"~value\"]);\n}\n\nexport function mergeDocuments(target: Document, source: Document): Document {\n const result: Document = {};\n const keys = new Set([...Object.keys(target), ...Object.keys(source)]);\n\n for (const key of keys) {\n const targetValue = target[key];\n const sourceValue = source[key];\n\n if (targetValue && sourceValue) {\n result[key] =\n targetValue[\"~stamp\"] > sourceValue[\"~stamp\"]\n ? targetValue\n : sourceValue;\n } else if (targetValue) {\n result[key] = targetValue;\n } else if (sourceValue) {\n result[key] = sourceValue;\n } else {\n throw new Error(`Key ${key} not found in either document`);\n }\n }\n\n return result;\n}\n","export type Tombstones = Record<string, string>;\n\nexport function mergeTombstones(\n target: Tombstones,\n source: Tombstones,\n): 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 { Document } from \"./document\";\nimport type { Tombstones } from \"./tombstone\";\nimport { mergeDocuments } from \"./document\";\nimport { mergeTombstones } from \"./tombstone\";\n\nexport type DocumentId = string;\n\nexport type Collection = {\n documents: Record<DocumentId, Document>;\n tombstones: Tombstones;\n};\n\nexport function mergeCollections(\n target: Collection,\n source: Collection,\n): Collection {\n const mergedTombstones = mergeTombstones(\n target.tombstones,\n source.tombstones,\n );\n\n const mergedDocuments: Record<DocumentId, Document> = {};\n const allDocumentIds = new Set([\n ...Object.keys(target.documents),\n ...Object.keys(source.documents),\n ]);\n\n for (const id of allDocumentIds) {\n const targetDoc = target.documents[id];\n const sourceDoc = source.documents[id];\n\n if (mergedTombstones[id]) {\n continue;\n }\n\n if (targetDoc && sourceDoc) {\n mergedDocuments[id] = mergeDocuments(targetDoc, sourceDoc);\n } else if (targetDoc) {\n mergedDocuments[id] = targetDoc;\n } else if (sourceDoc) {\n mergedDocuments[id] = sourceDoc;\n }\n }\n\n return {\n documents: mergedDocuments,\n tombstones: mergedTombstones,\n };\n}\nexport function mergeCollectionRecords(\n target: Record<string, Collection>,\n source: Record<string, Collection>,\n): Record<string, Collection> {\n const result: Record<string, Collection> = { ...target };\n\n for (const [collectionName, sourceCollection] of Object.entries(source)) {\n const targetCollection = result[collectionName];\n if (targetCollection) {\n result[collectionName] = mergeCollections(\n targetCollection,\n sourceCollection,\n );\n } else {\n result[collectionName] = sourceCollection;\n }\n }\n\n return result;\n}\n","import { batched, map, type ReadableAtom } from \"nanostores\";\nimport { validate } from \"./schema\";\nimport {\n makeDocument,\n parseDocument,\n mergeDocuments,\n mergeCollections,\n type Collection,\n type DocumentId,\n} from \"../core\";\nimport type { AnyObject, SchemaWithId, Output, Input } from \"./schema\";\nimport type { ClockAPI } from \"./clock\";\n\nexport type CollectionConfig<T extends AnyObject> =\n | {\n schema: T;\n getId: (data: Output<T>) => DocumentId;\n }\n | {\n schema: SchemaWithId<T>;\n };\n\nexport type CollectionApi<T extends AnyObject> = {\n $data: ReadableAtom<ReadonlyMap<DocumentId, Output<T>>>;\n $snapshot: ReadableAtom<Collection>;\n add(data: Input<T>): void;\n remove(id: DocumentId): void;\n update(id: DocumentId, document: Partial<Input<T>>): void;\n merge(snapshot: Collection): void;\n} & Pick<\n ReadonlyMap<DocumentId, Output<T>>,\n \"get\" | \"has\" | \"keys\" | \"values\" | \"entries\" | \"forEach\" | \"size\"\n>;\n\ntype TickFunction = () => string;\n\nexport function addDocument<T extends AnyObject>(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n config: CollectionConfig<T>,\n tick: TickFunction,\n data: Input<T>,\n): void {\n const getId = defineGetId(config);\n const valid = validate(config.schema, data);\n const doc = makeDocument(valid, tick());\n const id = getId(valid);\n $documents.setKey(id, doc);\n}\n\nexport function removeDocument(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n $tombstones: ReturnType<typeof map<Collection[\"tombstones\"]>>,\n tick: TickFunction,\n id: DocumentId,\n): void {\n $tombstones.setKey(id, tick());\n $documents.setKey(id, undefined);\n}\n\nexport function updateDocument<T extends AnyObject>(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n config: CollectionConfig<T>,\n tick: TickFunction,\n id: DocumentId,\n document: Partial<Input<T>>,\n): void {\n const current = $documents.get()[id];\n if (!current) return;\n\n const newAttrs = makeDocument(document, tick());\n const doc = mergeDocuments(current, newAttrs);\n\n validate(config.schema, parseDocument(doc));\n\n $documents.setKey(id, doc);\n}\n\nexport function mergeCollectionSnapshot(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n $tombstones: ReturnType<typeof map<Collection[\"tombstones\"]>>,\n currentSnapshot: Collection,\n incomingSnapshot: Collection,\n): void {\n const merged = mergeCollections(currentSnapshot, incomingSnapshot);\n $documents.set(merged.documents);\n $tombstones.set(merged.tombstones);\n}\n\nexport function createCollection<T extends AnyObject>(\n config: CollectionConfig<T>,\n clock: ClockAPI,\n): CollectionApi<T> {\n const { $data, $snapshot, $documents, $tombstones } =\n createCollectionState<T>();\n\n return {\n $data,\n $snapshot,\n get(key: DocumentId) {\n return $data.get().get(key);\n },\n has(key: DocumentId) {\n return $data.get().has(key);\n },\n keys() {\n return $data.get().keys();\n },\n values() {\n return $data.get().values();\n },\n entries() {\n return $data.get().entries();\n },\n forEach(\n callbackfn: (\n value: Output<T>,\n key: DocumentId,\n map: ReadonlyMap<DocumentId, Output<T>>,\n ) => void,\n thisArg?: any,\n ) {\n return $data.get().forEach(callbackfn, thisArg);\n },\n get size() {\n return $data.get().size;\n },\n add(data: Input<T>) {\n addDocument($documents, config, clock.tick, data);\n },\n remove(id: DocumentId) {\n removeDocument($documents, $tombstones, clock.tick, id);\n },\n update(id: DocumentId, document: Partial<Input<T>>) {\n updateDocument($documents, config, clock.tick, id, document);\n },\n merge(snapshot: Collection) {\n const currentSnapshot = $snapshot.get();\n mergeCollectionSnapshot(\n $documents,\n $tombstones,\n currentSnapshot,\n snapshot,\n );\n },\n };\n}\n\nfunction createCollectionState<T extends AnyObject>(): {\n $data: ReadableAtom<ReadonlyMap<DocumentId, Output<T>>>;\n $snapshot: ReadableAtom<Collection>;\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>;\n $tombstones: ReturnType<typeof map<Collection[\"tombstones\"]>>;\n} {\n const $documents = map<Collection[\"documents\"]>({});\n const $tombstones = map<Collection[\"tombstones\"]>({});\n const $snapshot = batched([$documents, $tombstones], parseSnapshot);\n const $data = batched([$documents, $tombstones], parseCollection<T>);\n\n return {\n $data,\n $snapshot,\n $documents,\n $tombstones,\n };\n}\n\nfunction hasIdProperty<T extends AnyObject>(\n data: Output<T>,\n): data is { id: DocumentId } {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"id\" in data &&\n typeof (data as any).id === \"string\"\n );\n}\n\nfunction parseCollection<T extends AnyObject>(\n documents: Collection[\"documents\"],\n tombstones: Collection[\"tombstones\"],\n): ReadonlyMap<DocumentId, Output<T>> {\n const result = new Map<DocumentId, Output<T>>();\n for (const [id, doc] of Object.entries(documents)) {\n if (!tombstones[id] && doc) {\n result.set(id, parseDocument(doc));\n }\n }\n return result;\n}\n\nfunction parseSnapshot(\n documents: Collection[\"documents\"],\n tombstones: Collection[\"tombstones\"],\n): Collection {\n return {\n documents,\n tombstones,\n };\n}\n\nfunction hasGetId<T extends AnyObject>(\n config: CollectionConfig<T>,\n): config is {\n schema: T;\n getId: (data: Output<T>) => DocumentId;\n} {\n return \"getId\" in config && typeof config.getId === \"function\";\n}\n\nfunction defineGetId<T extends AnyObject>(\n config: CollectionConfig<T>,\n): (data: Output<T>) => DocumentId {\n return hasGetId(config) ? config.getId : defaultGetId;\n}\n\nfunction defaultGetId<T extends AnyObject>(data: Output<T>): DocumentId {\n if (hasIdProperty(data)) {\n return data.id;\n }\n throw new Error(\n \"Schema must have an 'id' property when getId is not provided\",\n );\n}\n","import { atom } from \"nanostores\";\nimport type { Clock } from \"../core/clock\";\nimport { advanceClock, makeStamp } from \"../core/clock\";\n\ntype ClockAtom = ReturnType<typeof atom<Clock>>;\n\nexport type ClockAPI = {\n $state: ClockAtom;\n tick: () => string;\n advance: (ms: number, seq: number) => void;\n};\n\nexport function createClock(): ClockAPI {\n const $state: ClockAtom = atom<Clock>(nowClock());\n\n const tick = () => {\n const next = advanceClock($state.get(), nowClock());\n $state.set(next);\n return makeStamp(next.ms, next.seq);\n };\n\n const advance = (ms: number, seq: number) => {\n const next = advanceClock($state.get(), { ms, seq });\n $state.set(next);\n };\n\n return {\n $state,\n tick,\n advance,\n };\n}\n\nfunction nowClock(): Clock {\n return { ms: Date.now(), seq: 0 };\n}\n","import { batched, computed, type ReadableAtom } from \"nanostores\";\nimport { createCollection } from \"./collection\";\nimport { createClock, type ClockAPI } from \"./clock\";\nimport type { CollectionConfig, CollectionApi } from \"./collection\";\nimport type { Clock } from \"../core/clock\";\nimport type { Collection } from \"../core/collection\";\n\nexport type StoreSnapshot = {\n clock: Clock;\n collections: Record<string, Collection>;\n};\n\nexport type StoreCollections<T extends Record<string, CollectionConfig<any>>> =\n {\n [K in keyof T]: T[K] extends CollectionConfig<infer S>\n ? CollectionApi<S>\n : never;\n };\n\nexport type QueryCollections<\n TCollections extends StoreCollections<any>,\n TKeys extends readonly (keyof TCollections)[],\n> = {\n [K in TKeys[number]]: TCollections[K] extends { $data: ReadableAtom<infer D> }\n ? D\n : never;\n};\n\nexport type StoreAPI<T extends Record<string, CollectionConfig<any>>> =\n StoreCollections<T> & {\n $snapshot: ReadableAtom<StoreSnapshot>;\n query<TKeys extends readonly (keyof StoreCollections<T>)[], TResult>(\n collections: TKeys,\n callback: (\n collections: QueryCollections<StoreCollections<T>, TKeys>,\n ) => TResult,\n ): ReadableAtom<TResult>;\n merge(snapshot: StoreSnapshot): void;\n };\n\nexport function createStore<\n T extends Record<string, CollectionConfig<any>>,\n>(config: { collections: T }): StoreAPI<T> {\n const clock = createClock();\n const collections = initCollections(config.collections, clock);\n const $snapshot = parseCollections(collections, clock.$state);\n\n function getCollectionDataStores(\n collectionNames: readonly (keyof StoreCollections<T>)[],\n ): ReadableAtom<any>[] {\n return collectionNames.map((name) => collections[name]!.$data);\n }\n\n return {\n ...collections,\n $snapshot,\n query: <TKeys extends readonly (keyof StoreCollections<T>)[], TResult>(\n collectionNames: TKeys,\n callback: (\n collections: QueryCollections<StoreCollections<T>, TKeys>,\n ) => TResult,\n ) => {\n const atoms = getCollectionDataStores(collectionNames);\n\n return computed(atoms, (...values) => {\n const entries = collectionNames.map((name, i) => [name, values[i]]);\n return callback(\n Object.fromEntries(entries) as QueryCollections<\n StoreCollections<T>,\n TKeys\n >,\n );\n });\n },\n merge: (snapshot) => {\n clock.advance(snapshot.clock.ms, snapshot.clock.seq);\n mergeCollections(collections, snapshot.collections);\n },\n };\n}\n\nfunction initCollections<T extends Record<string, CollectionConfig<any>>>(\n collectionsConfig: T,\n clock: ClockAPI,\n): StoreCollections<T> {\n return Object.fromEntries(\n Object.entries(collectionsConfig).map(([name, config]) => [\n name,\n createCollection(config, clock),\n ]),\n ) as StoreCollections<T>;\n}\n\nfunction parseCollections<T extends Record<string, CollectionConfig<any>>>(\n collections: StoreCollections<T>,\n clockState: ReadableAtom<Clock>,\n): ReadableAtom<StoreSnapshot> {\n const collectionNames = Object.keys(collections);\n const collectionSnapshotAtoms: ReadableAtom<Collection>[] = [];\n\n for (const name of collectionNames) {\n const collection = collections[name];\n if (collection) {\n collectionSnapshotAtoms.push(collection.$snapshot);\n }\n }\n\n return batched(\n [clockState, ...collectionSnapshotAtoms],\n (clock, ...snapshots) => {\n const collectionsSnapshot: Record<string, Collection> = {};\n for (let i = 0; i < collectionNames.length; i++) {\n const name = collectionNames[i];\n const snapshot = snapshots[i];\n if (name && snapshot !== undefined) {\n collectionsSnapshot[name] = snapshot;\n }\n }\n\n return {\n clock,\n collections: collectionsSnapshot,\n };\n },\n );\n}\n\nfunction mergeCollections(\n target: Record<string, CollectionApi<any>>,\n source: Record<string, Collection>,\n) {\n for (const [collectionName, collectionSnapshot] of Object.entries(source)) {\n const collection = target[collectionName];\n if (collection) {\n collection.merge(collectionSnapshot);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["result: Record<string, R>","result: Record<string, unknown>","current: any","result: Document","result: Tombstones","mergeCollections","mergedDocuments: Record<DocumentId, Document>","mergeCollections","$state: ClockAtom","collectionSnapshotAtoms: ReadableAtom<Collection>[]","collectionsSnapshot: Record<string, Collection>"],"sources":["../lib/store/schema.ts","../lib/core/hex.ts","../lib/core/clock.ts","../lib/core/flatten.ts","../lib/core/document.ts","../lib/core/tombstone.ts","../lib/core/collection.ts","../lib/store/collection.ts","../lib/store/clock.ts","../lib/store/store.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport function validate<T extends StandardSchemaV1>(\n schema: T,\n input: StandardSchemaV1.InferInput<T>,\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 SchemaWithId<T extends AnyObject> =\n StandardSchemaV1.InferOutput<T> extends {\n id: any;\n }\n ? T\n : never;\n\nexport type Output<T extends AnyObject> = StandardSchemaV1.InferOutput<T>;\n\nexport type Input<T extends AnyObject> = StandardSchemaV1.InferInput<T>;\n","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;\n\nexport type Clock = {\n ms: number;\n seq: number;\n};\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): string {\n return `${toHex(ms, MS_LENGTH)}${toHex(seq, SEQ_LENGTH)}${nonce(NONCE_LENGTH)}`;\n}\n\nexport function parseStamp(stamp: string): { ms: number; seq: number } {\n return {\n ms: parseInt(stamp.slice(0, MS_LENGTH), 16),\n seq: parseInt(stamp.slice(MS_LENGTH, MS_LENGTH + SEQ_LENGTH), 16),\n };\n}\n","/**\n * Flattens a nested object into a flat object with dot-notation keys\n * @param obj - The object to flatten\n * @param mapper - Optional callback to transform leaf values\n * @returns A flattened object with dot-notation keys\n */\nexport function flatten<T, R = unknown>(\n obj: T,\n mapper?: (value: unknown, path: string) => R,\n): Record<string, R> {\n const result: Record<string, R> = {};\n\n const addLeaf = (value: unknown, path: string) => {\n if (path) {\n result[path] = mapper ? mapper(value, path) : (value as R);\n }\n };\n\n function traverse(current: unknown, prefix: string = \"\"): void {\n if (!shouldTraverse(current)) {\n addLeaf(current, prefix);\n return;\n }\n\n for (const [key, value] of Object.entries(current)) {\n const newPath = prefix ? `${prefix}.${key}` : key;\n traverse(value, newPath);\n }\n }\n\n traverse(obj);\n return result;\n}\n\n/**\n * Unflattens a flat object with dot-notation keys into a nested object\n * @param obj - The flattened object to unflatten\n * @param mapper - Optional callback to transform leaf values before placing them\n * @returns A nested object\n */\nexport function unflatten<T = unknown, R = unknown>(\n obj: Record<string, T>,\n mapper?: (value: T, path: string) => R,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [path, value] of Object.entries(obj)) {\n const keys = path.split(\".\");\n const mappedValue = mapper ? mapper(value, path) : value;\n\n let current: any = result;\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]!;\n if (!(key in current)) {\n current[key] = {};\n }\n current = current[key];\n }\n\n const finalKey = keys[keys.length - 1]!;\n current[finalKey] = mappedValue;\n }\n\n return result;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (value.constructor === Object || Object.getPrototypeOf(value) === null)\n );\n}\n\nfunction shouldTraverse(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && Object.keys(value).length > 0;\n}\n","import { flatten, unflatten } from \"./flatten\";\n\ntype Field<T = unknown> = {\n \"~value\": T;\n \"~stamp\": string;\n};\n\nexport type Document = Record<string, Field>;\n\nexport function makeDocument(\n fields: Record<string, any>,\n stamp: string,\n): Document {\n return flatten(fields, (value) => ({ \"~value\": value, \"~stamp\": stamp }));\n}\n\nexport function parseDocument(document: Document): Record<string, any> {\n return unflatten(document, (field) => field[\"~value\"]);\n}\n\nexport function mergeDocuments(target: Document, source: Document): Document {\n const result: Document = {};\n const keys = new Set([...Object.keys(target), ...Object.keys(source)]);\n\n for (const key of keys) {\n const targetValue = target[key];\n const sourceValue = source[key];\n\n if (targetValue && sourceValue) {\n result[key] =\n targetValue[\"~stamp\"] > sourceValue[\"~stamp\"]\n ? targetValue\n : sourceValue;\n } else if (targetValue) {\n result[key] = targetValue;\n } else if (sourceValue) {\n result[key] = sourceValue;\n } else {\n throw new Error(`Key ${key} not found in either document`);\n }\n }\n\n return result;\n}\n","export type Tombstones = Record<string, string>;\n\nexport function mergeTombstones(\n target: Tombstones,\n source: Tombstones,\n): 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 { Document } from \"./document\";\nimport type { Tombstones } from \"./tombstone\";\nimport { mergeDocuments } from \"./document\";\nimport { mergeTombstones } from \"./tombstone\";\n\nexport type DocumentId = string;\n\nexport type Collection = {\n documents: Record<DocumentId, Document>;\n tombstones: Tombstones;\n};\n\nexport function mergeCollections(\n target: Collection,\n source: Collection,\n): Collection {\n const mergedTombstones = mergeTombstones(\n target.tombstones,\n source.tombstones,\n );\n\n const mergedDocuments: Record<DocumentId, Document> = {};\n const allDocumentIds = new Set([\n ...Object.keys(target.documents),\n ...Object.keys(source.documents),\n ]);\n\n for (const id of allDocumentIds) {\n const targetDoc = target.documents[id];\n const sourceDoc = source.documents[id];\n\n if (mergedTombstones[id]) {\n continue;\n }\n\n if (targetDoc && sourceDoc) {\n mergedDocuments[id] = mergeDocuments(targetDoc, sourceDoc);\n } else if (targetDoc) {\n mergedDocuments[id] = targetDoc;\n } else if (sourceDoc) {\n mergedDocuments[id] = sourceDoc;\n }\n }\n\n return {\n documents: mergedDocuments,\n tombstones: mergedTombstones,\n };\n}\nexport function mergeCollectionRecords(\n target: Record<string, Collection>,\n source: Record<string, Collection>,\n): Record<string, Collection> {\n const result: Record<string, Collection> = { ...target };\n\n for (const [collectionName, sourceCollection] of Object.entries(source)) {\n const targetCollection = result[collectionName];\n if (targetCollection) {\n result[collectionName] = mergeCollections(\n targetCollection,\n sourceCollection,\n );\n } else {\n result[collectionName] = sourceCollection;\n }\n }\n\n return result;\n}\n","import { batched, map, type ReadableAtom } from \"nanostores\";\nimport { validate } from \"./schema\";\nimport {\n makeDocument,\n parseDocument,\n mergeDocuments,\n mergeCollections,\n type Collection,\n type DocumentId,\n} from \"../core\";\nimport type { AnyObject, SchemaWithId, Output, Input } from \"./schema\";\nimport type { ClockAPI } from \"./clock\";\n\nexport type CollectionConfig<T extends AnyObject> =\n | {\n schema: T;\n getId: (data: Output<T>) => DocumentId;\n }\n | {\n schema: SchemaWithId<T>;\n };\n\nexport type CollectionApi<T extends AnyObject> = {\n $data: ReadableAtom<ReadonlyMap<DocumentId, Output<T>>>;\n $snapshot: ReadableAtom<Collection>;\n add(data: Input<T>): void;\n remove(id: DocumentId): void;\n update(id: DocumentId, document: Partial<Input<T>>): void;\n merge(snapshot: Collection): void;\n} & Pick<\n ReadonlyMap<DocumentId, Output<T>>,\n \"get\" | \"has\" | \"keys\" | \"values\" | \"entries\" | \"forEach\" | \"size\"\n>;\n\ntype TickFunction = () => string;\n\nexport function addDocument<T extends AnyObject>(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n config: CollectionConfig<T>,\n tick: TickFunction,\n data: Input<T>,\n): void {\n const getId = defineGetId(config);\n const valid = validate(config.schema, data);\n const doc = makeDocument(valid, tick());\n const id = getId(valid);\n $documents.setKey(id, doc);\n}\n\nexport function removeDocument(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n $tombstones: ReturnType<typeof map<Collection[\"tombstones\"]>>,\n tick: TickFunction,\n id: DocumentId,\n): void {\n $tombstones.setKey(id, tick());\n $documents.setKey(id, undefined);\n}\n\nexport function updateDocument<T extends AnyObject>(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n config: CollectionConfig<T>,\n tick: TickFunction,\n id: DocumentId,\n document: Partial<Input<T>>,\n): void {\n const current = $documents.get()[id];\n if (!current) return;\n\n const newAttrs = makeDocument(document, tick());\n const doc = mergeDocuments(current, newAttrs);\n\n validate(config.schema, parseDocument(doc));\n\n $documents.setKey(id, doc);\n}\n\nexport function mergeCollectionSnapshot(\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>,\n $tombstones: ReturnType<typeof map<Collection[\"tombstones\"]>>,\n currentSnapshot: Collection,\n incomingSnapshot: Collection,\n): void {\n const merged = mergeCollections(currentSnapshot, incomingSnapshot);\n $documents.set(merged.documents);\n $tombstones.set(merged.tombstones);\n}\n\nexport function createCollection<T extends AnyObject>(\n config: CollectionConfig<T>,\n clock: ClockAPI,\n): CollectionApi<T> {\n const { $data, $snapshot, $documents, $tombstones } =\n createCollectionState<T>();\n\n return {\n $data,\n $snapshot,\n get(key: DocumentId) {\n return $data.get().get(key);\n },\n has(key: DocumentId) {\n return $data.get().has(key);\n },\n keys() {\n return $data.get().keys();\n },\n values() {\n return $data.get().values();\n },\n entries() {\n return $data.get().entries();\n },\n forEach(\n callbackfn: (\n value: Output<T>,\n key: DocumentId,\n map: ReadonlyMap<DocumentId, Output<T>>,\n ) => void,\n thisArg?: any,\n ) {\n return $data.get().forEach(callbackfn, thisArg);\n },\n get size() {\n return $data.get().size;\n },\n add(data: Input<T>) {\n addDocument($documents, config, clock.tick, data);\n },\n remove(id: DocumentId) {\n removeDocument($documents, $tombstones, clock.tick, id);\n },\n update(id: DocumentId, document: Partial<Input<T>>) {\n updateDocument($documents, config, clock.tick, id, document);\n },\n merge(snapshot: Collection) {\n const currentSnapshot = $snapshot.get();\n mergeCollectionSnapshot(\n $documents,\n $tombstones,\n currentSnapshot,\n snapshot,\n );\n },\n };\n}\n\nfunction createCollectionState<T extends AnyObject>(): {\n $data: ReadableAtom<ReadonlyMap<DocumentId, Output<T>>>;\n $snapshot: ReadableAtom<Collection>;\n $documents: ReturnType<typeof map<Collection[\"documents\"]>>;\n $tombstones: ReturnType<typeof map<Collection[\"tombstones\"]>>;\n} {\n const $documents = map<Collection[\"documents\"]>({});\n const $tombstones = map<Collection[\"tombstones\"]>({});\n const $snapshot = batched([$documents, $tombstones], parseSnapshot);\n const $data = batched([$documents, $tombstones], parseCollection<T>);\n\n return {\n $data,\n $snapshot,\n $documents,\n $tombstones,\n };\n}\n\nfunction hasIdProperty<T extends AnyObject>(\n data: Output<T>,\n): data is { id: DocumentId } {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"id\" in data &&\n typeof (data as any).id === \"string\"\n );\n}\n\nfunction parseCollection<T extends AnyObject>(\n documents: Collection[\"documents\"],\n tombstones: Collection[\"tombstones\"],\n): ReadonlyMap<DocumentId, Output<T>> {\n const result = new Map<DocumentId, Output<T>>();\n for (const [id, doc] of Object.entries(documents)) {\n if (!tombstones[id] && doc) {\n result.set(id, parseDocument(doc));\n }\n }\n return result;\n}\n\nfunction parseSnapshot(\n documents: Collection[\"documents\"],\n tombstones: Collection[\"tombstones\"],\n): Collection {\n return {\n documents,\n tombstones,\n };\n}\n\nfunction hasGetId<T extends AnyObject>(\n config: CollectionConfig<T>,\n): config is {\n schema: T;\n getId: (data: Output<T>) => DocumentId;\n} {\n return \"getId\" in config && typeof config.getId === \"function\";\n}\n\nfunction defineGetId<T extends AnyObject>(\n config: CollectionConfig<T>,\n): (data: Output<T>) => DocumentId {\n return hasGetId(config) ? config.getId : defaultGetId;\n}\n\nfunction defaultGetId<T extends AnyObject>(data: Output<T>): DocumentId {\n if (hasIdProperty(data)) {\n return data.id;\n }\n throw new Error(\n \"Schema must have an 'id' property when getId is not provided\",\n );\n}\n","import { atom } from \"nanostores\";\nimport type { Clock } from \"../core/clock\";\nimport { advanceClock, makeStamp } from \"../core/clock\";\n\ntype ClockAtom = ReturnType<typeof atom<Clock>>;\n\nexport type ClockAPI = {\n $state: ClockAtom;\n tick: () => string;\n advance: (ms: number, seq: number) => void;\n};\n\nexport function createClock(): ClockAPI {\n const $state: ClockAtom = atom<Clock>(nowClock());\n\n const tick = () => {\n const next = advanceClock($state.get(), nowClock());\n $state.set(next);\n return makeStamp(next.ms, next.seq);\n };\n\n const advance = (ms: number, seq: number) => {\n const next = advanceClock($state.get(), { ms, seq });\n $state.set(next);\n };\n\n return {\n $state,\n tick,\n advance,\n };\n}\n\nfunction nowClock(): Clock {\n return { ms: Date.now(), seq: 0 };\n}\n","import { batched, type ReadableAtom } from \"nanostores\";\nimport { createCollection } from \"./collection\";\nimport { createClock, type ClockAPI } from \"./clock\";\nimport type { CollectionConfig, CollectionApi } from \"./collection\";\nimport type { Clock } from \"../core/clock\";\nimport type { Collection } from \"../core/collection\";\n\nexport type StoreSnapshot = {\n clock: Clock;\n collections: Record<string, Collection>;\n};\n\nexport type StoreCollections<T extends Record<string, CollectionConfig<any>>> =\n {\n [K in keyof T]: T[K] extends CollectionConfig<infer S>\n ? CollectionApi<S>\n : never;\n };\n\nexport type QueryCollections<\n TCollections extends StoreCollections<any>,\n TKeys extends readonly (keyof TCollections)[],\n> = {\n [K in TKeys[number]]: TCollections[K] extends { $data: ReadableAtom<infer D> }\n ? D\n : never;\n};\n\nexport type StoreAPI<T extends Record<string, CollectionConfig<any>>> =\n StoreCollections<T> & {\n $snapshot: ReadableAtom<StoreSnapshot>;\n query<TKeys extends readonly (keyof StoreCollections<T>)[], TResult>(\n collections: TKeys,\n callback: (\n collections: QueryCollections<StoreCollections<T>, TKeys>,\n ) => TResult,\n ): ReadableAtom<TResult>;\n merge(snapshot: StoreSnapshot): void;\n };\n\nexport function createStore<\n T extends Record<string, CollectionConfig<any>>,\n>(config: { collections: T }): StoreAPI<T> {\n const clock = createClock();\n const collections = initCollections(config.collections, clock);\n const $snapshot = parseCollections(collections, clock.$state);\n\n function getCollectionDataStores(\n collectionNames: readonly (keyof StoreCollections<T>)[],\n ): ReadableAtom<any>[] {\n return collectionNames.map((name) => collections[name]!.$data);\n }\n\n return {\n ...collections,\n $snapshot,\n query: <TKeys extends readonly (keyof StoreCollections<T>)[], TResult>(\n collectionNames: TKeys,\n callback: (\n collections: QueryCollections<StoreCollections<T>, TKeys>,\n ) => TResult,\n ) => {\n const atoms = getCollectionDataStores(collectionNames);\n\n return batched(atoms, (...values) => {\n const entries = collectionNames.map((name, i) => [name, values[i]]);\n return callback(\n Object.fromEntries(entries) as QueryCollections<\n StoreCollections<T>,\n TKeys\n >,\n );\n });\n },\n merge: (snapshot) => {\n clock.advance(snapshot.clock.ms, snapshot.clock.seq);\n mergeCollections(collections, snapshot.collections);\n },\n };\n}\n\nfunction initCollections<T extends Record<string, CollectionConfig<any>>>(\n collectionsConfig: T,\n clock: ClockAPI,\n): StoreCollections<T> {\n return Object.fromEntries(\n Object.entries(collectionsConfig).map(([name, config]) => [\n name,\n createCollection(config, clock),\n ]),\n ) as StoreCollections<T>;\n}\n\nfunction parseCollections<T extends Record<string, CollectionConfig<any>>>(\n collections: StoreCollections<T>,\n clockState: ReadableAtom<Clock>,\n): ReadableAtom<StoreSnapshot> {\n const collectionNames = Object.keys(collections);\n const collectionSnapshotAtoms: ReadableAtom<Collection>[] = [];\n\n for (const name of collectionNames) {\n const collection = collections[name];\n if (collection) {\n collectionSnapshotAtoms.push(collection.$snapshot);\n }\n }\n\n return batched(\n [clockState, ...collectionSnapshotAtoms],\n (clock, ...snapshots) => {\n const collectionsSnapshot: Record<string, Collection> = {};\n for (let i = 0; i < collectionNames.length; i++) {\n const name = collectionNames[i];\n const snapshot = snapshots[i];\n if (name && snapshot !== undefined) {\n collectionsSnapshot[name] = snapshot;\n }\n }\n\n return {\n clock,\n collections: collectionsSnapshot,\n };\n },\n );\n}\n\nfunction mergeCollections(\n target: Record<string, CollectionApi<any>>,\n source: Record<string, Collection>,\n) {\n for (const [collectionName, collectionSnapshot] of Object.entries(source)) {\n const collection = target[collectionName];\n if (collection) {\n collection.merge(collectionSnapshot);\n }\n }\n}\n"],"mappings":";;;AAEA,SAAgB,SACd,QACA,OACiC;CACjC,MAAM,SAAS,OAAO,aAAa,SAAS,MAAM;AAClD,KAAI,kBAAkB,QACpB,OAAM,IAAI,UAAU,wCAAwC;AAG9D,KAAI,OAAO,OACT,OAAM,IAAI,MAAM,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE,CAAC;AAGzD,QAAO,OAAO;;;;;ACfhB,SAAgB,MAAM,OAAe,WAA2B;AAC9D,QAAO,MAAM,SAAS,GAAG,CAAC,SAAS,WAAW,IAAI;;AAGpD,SAAgB,MAAM,QAAwB;CAC5C,MAAM,QAAQ,IAAI,WAAW,SAAS,EAAE;AACxC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,MAAM,KAAK,MAAM,CACrB,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CACvB,KAAK,GAAG;;;;;ACPb,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,eAAe;AAOrB,SAAgB,aAAa,SAAgB,MAAoB;AAC/D,KAAI,KAAK,KAAK,QAAQ,GACpB,QAAO;EAAE,IAAI,KAAK;EAAI,KAAK,KAAK;EAAK;UAC5B,KAAK,OAAO,QAAQ,GAC7B,QAAO;EAAE,IAAI,QAAQ;EAAI,KAAK,KAAK,IAAI,QAAQ,KAAK,KAAK,IAAI,GAAG;EAAG;KAEnE,QAAO;EAAE,IAAI,QAAQ;EAAI,KAAK,QAAQ,MAAM;EAAG;;AAInD,SAAgB,UAAU,IAAY,KAAqB;AACzD,QAAO,GAAG,MAAM,IAAI,UAAU,GAAG,MAAM,KAAK,WAAW,GAAG,MAAM,aAAa;;;;;;;;;;;AChB/E,SAAgB,QACd,KACA,QACmB;CACnB,MAAMA,SAA4B,EAAE;CAEpC,MAAM,WAAW,OAAgB,SAAiB;AAChD,MAAI,KACF,QAAO,QAAQ,SAAS,OAAO,OAAO,KAAK,GAAI;;CAInD,SAAS,SAAS,SAAkB,SAAiB,IAAU;AAC7D,MAAI,CAAC,eAAe,QAAQ,EAAE;AAC5B,WAAQ,SAAS,OAAO;AACxB;;AAGF,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,CAEhD,UAAS,OADO,SAAS,GAAG,OAAO,GAAG,QAAQ,IACtB;;AAI5B,UAAS,IAAI;AACb,QAAO;;;;;;;;AAST,SAAgB,UACd,KACA,QACyB;CACzB,MAAMC,SAAkC,EAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,IAAI,EAAE;EAC/C,MAAM,OAAO,KAAK,MAAM,IAAI;EAC5B,MAAM,cAAc,SAAS,OAAO,OAAO,KAAK,GAAG;EAEnD,IAAIC,UAAe;AACnB,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;GACxC,MAAM,MAAM,KAAK;AACjB,OAAI,EAAE,OAAO,SACX,SAAQ,OAAO,EAAE;AAEnB,aAAU,QAAQ;;EAGpB,MAAM,WAAW,KAAK,KAAK,SAAS;AACpC,UAAQ,YAAY;;AAGtB,QAAO;;AAGT,SAAS,cAAc,OAAkD;AACvE,QACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,MAAM,KACpB,MAAM,gBAAgB,UAAU,OAAO,eAAe,MAAM,KAAK;;AAItE,SAAS,eAAe,OAAkD;AACxE,QAAO,cAAc,MAAM,IAAI,OAAO,KAAK,MAAM,CAAC,SAAS;;;;;ACnE7D,SAAgB,aACd,QACA,OACU;AACV,QAAO,QAAQ,SAAS,WAAW;EAAE,UAAU;EAAO,UAAU;EAAO,EAAE;;AAG3E,SAAgB,cAAc,UAAyC;AACrE,QAAO,UAAU,WAAW,UAAU,MAAM,UAAU;;AAGxD,SAAgB,eAAe,QAAkB,QAA4B;CAC3E,MAAMC,SAAmB,EAAE;CAC3B,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAEtE,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,cAAc,OAAO;EAC3B,MAAM,cAAc,OAAO;AAE3B,MAAI,eAAe,YACjB,QAAO,OACL,YAAY,YAAY,YAAY,YAChC,cACA;WACG,YACT,QAAO,OAAO;WACL,YACT,QAAO,OAAO;MAEd,OAAM,IAAI,MAAM,OAAO,IAAI,+BAA+B;;AAI9D,QAAO;;;;;ACxCT,SAAgB,gBACd,QACA,QACY;CACZ,MAAMC,SAAqB,EAAE;CAC7B,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAEtE,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,cAAc,OAAO;EAC3B,MAAM,cAAc,OAAO;AAE3B,MAAI,eAAe,YACjB,QAAO,OAAO,cAAc,cAAc,cAAc;WAC/C,YACT,QAAO,OAAO;WACL,YACT,QAAO,OAAO;;AAIlB,QAAO;;;;;ACVT,SAAgBC,mBACd,QACA,QACY;CACZ,MAAM,mBAAmB,gBACvB,OAAO,YACP,OAAO,WACR;CAED,MAAMC,kBAAgD,EAAE;CACxD,MAAM,iBAAiB,IAAI,IAAI,CAC7B,GAAG,OAAO,KAAK,OAAO,UAAU,EAChC,GAAG,OAAO,KAAK,OAAO,UAAU,CACjC,CAAC;AAEF,MAAK,MAAM,MAAM,gBAAgB;EAC/B,MAAM,YAAY,OAAO,UAAU;EACnC,MAAM,YAAY,OAAO,UAAU;AAEnC,MAAI,iBAAiB,IACnB;AAGF,MAAI,aAAa,UACf,iBAAgB,MAAM,eAAe,WAAW,UAAU;WACjD,UACT,iBAAgB,MAAM;WACb,UACT,iBAAgB,MAAM;;AAI1B,QAAO;EACL,WAAW;EACX,YAAY;EACb;;;;;ACXH,SAAgB,YACd,YACA,QACA,MACA,MACM;CACN,MAAM,QAAQ,YAAY,OAAO;CACjC,MAAM,QAAQ,SAAS,OAAO,QAAQ,KAAK;CAC3C,MAAM,MAAM,aAAa,OAAO,MAAM,CAAC;CACvC,MAAM,KAAK,MAAM,MAAM;AACvB,YAAW,OAAO,IAAI,IAAI;;AAG5B,SAAgB,eACd,YACA,aACA,MACA,IACM;AACN,aAAY,OAAO,IAAI,MAAM,CAAC;AAC9B,YAAW,OAAO,IAAI,OAAU;;AAGlC,SAAgB,eACd,YACA,QACA,MACA,IACA,UACM;CACN,MAAM,UAAU,WAAW,KAAK,CAAC;AACjC,KAAI,CAAC,QAAS;CAGd,MAAM,MAAM,eAAe,SADV,aAAa,UAAU,MAAM,CAAC,CACF;AAE7C,UAAS,OAAO,QAAQ,cAAc,IAAI,CAAC;AAE3C,YAAW,OAAO,IAAI,IAAI;;AAG5B,SAAgB,wBACd,YACA,aACA,iBACA,kBACM;CACN,MAAM,SAASC,mBAAiB,iBAAiB,iBAAiB;AAClE,YAAW,IAAI,OAAO,UAAU;AAChC,aAAY,IAAI,OAAO,WAAW;;AAGpC,SAAgB,iBACd,QACA,OACkB;CAClB,MAAM,EAAE,OAAO,WAAW,YAAY,gBACpC,uBAA0B;AAE5B,QAAO;EACL;EACA;EACA,IAAI,KAAiB;AACnB,UAAO,MAAM,KAAK,CAAC,IAAI,IAAI;;EAE7B,IAAI,KAAiB;AACnB,UAAO,MAAM,KAAK,CAAC,IAAI,IAAI;;EAE7B,OAAO;AACL,UAAO,MAAM,KAAK,CAAC,MAAM;;EAE3B,SAAS;AACP,UAAO,MAAM,KAAK,CAAC,QAAQ;;EAE7B,UAAU;AACR,UAAO,MAAM,KAAK,CAAC,SAAS;;EAE9B,QACE,YAKA,SACA;AACA,UAAO,MAAM,KAAK,CAAC,QAAQ,YAAY,QAAQ;;EAEjD,IAAI,OAAO;AACT,UAAO,MAAM,KAAK,CAAC;;EAErB,IAAI,MAAgB;AAClB,eAAY,YAAY,QAAQ,MAAM,MAAM,KAAK;;EAEnD,OAAO,IAAgB;AACrB,kBAAe,YAAY,aAAa,MAAM,MAAM,GAAG;;EAEzD,OAAO,IAAgB,UAA6B;AAClD,kBAAe,YAAY,QAAQ,MAAM,MAAM,IAAI,SAAS;;EAE9D,MAAM,UAAsB;AAE1B,2BACE,YACA,aAHsB,UAAU,KAAK,EAKrC,SACD;;EAEJ;;AAGH,SAAS,wBAKP;CACA,MAAM,aAAa,IAA6B,EAAE,CAAC;CACnD,MAAM,cAAc,IAA8B,EAAE,CAAC;CACrD,MAAM,YAAY,QAAQ,CAAC,YAAY,YAAY,EAAE,cAAc;AAGnE,QAAO;EACL,OAHY,QAAQ,CAAC,YAAY,YAAY,EAAE,gBAAmB;EAIlE;EACA;EACA;EACD;;AAGH,SAAS,cACP,MAC4B;AAC5B,QACE,OAAO,SAAS,YAChB,SAAS,QACT,QAAQ,QACR,OAAQ,KAAa,OAAO;;AAIhC,SAAS,gBACP,WACA,YACoC;CACpC,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,CAAC,IAAI,QAAQ,OAAO,QAAQ,UAAU,CAC/C,KAAI,CAAC,WAAW,OAAO,IACrB,QAAO,IAAI,IAAI,cAAc,IAAI,CAAC;AAGtC,QAAO;;AAGT,SAAS,cACP,WACA,YACY;AACZ,QAAO;EACL;EACA;EACD;;AAGH,SAAS,SACP,QAIA;AACA,QAAO,WAAW,UAAU,OAAO,OAAO,UAAU;;AAGtD,SAAS,YACP,QACiC;AACjC,QAAO,SAAS,OAAO,GAAG,OAAO,QAAQ;;AAG3C,SAAS,aAAkC,MAA6B;AACtE,KAAI,cAAc,KAAK,CACrB,QAAO,KAAK;AAEd,OAAM,IAAI,MACR,+DACD;;;;;ACjNH,SAAgB,cAAwB;CACtC,MAAMC,SAAoB,KAAY,UAAU,CAAC;CAEjD,MAAM,aAAa;EACjB,MAAM,OAAO,aAAa,OAAO,KAAK,EAAE,UAAU,CAAC;AACnD,SAAO,IAAI,KAAK;AAChB,SAAO,UAAU,KAAK,IAAI,KAAK,IAAI;;CAGrC,MAAM,WAAW,IAAY,QAAgB;EAC3C,MAAM,OAAO,aAAa,OAAO,KAAK,EAAE;GAAE;GAAI;GAAK,CAAC;AACpD,SAAO,IAAI,KAAK;;AAGlB,QAAO;EACL;EACA;EACA;EACD;;AAGH,SAAS,WAAkB;AACzB,QAAO;EAAE,IAAI,KAAK,KAAK;EAAE,KAAK;EAAG;;;;;ACMnC,SAAgB,YAEd,QAAyC;CACzC,MAAM,QAAQ,aAAa;CAC3B,MAAM,cAAc,gBAAgB,OAAO,aAAa,MAAM;CAC9D,MAAM,YAAY,iBAAiB,aAAa,MAAM,OAAO;CAE7D,SAAS,wBACP,iBACqB;AACrB,SAAO,gBAAgB,KAAK,SAAS,YAAY,MAAO,MAAM;;AAGhE,QAAO;EACL,GAAG;EACH;EACA,QACE,iBACA,aAGG;AAGH,UAAO,QAFO,wBAAwB,gBAAgB,GAE/B,GAAG,WAAW;IACnC,MAAM,UAAU,gBAAgB,KAAK,MAAM,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC;AACnE,WAAO,SACL,OAAO,YAAY,QAAQ,CAI5B;KACD;;EAEJ,QAAQ,aAAa;AACnB,SAAM,QAAQ,SAAS,MAAM,IAAI,SAAS,MAAM,IAAI;AACpD,oBAAiB,aAAa,SAAS,YAAY;;EAEtD;;AAGH,SAAS,gBACP,mBACA,OACqB;AACrB,QAAO,OAAO,YACZ,OAAO,QAAQ,kBAAkB,CAAC,KAAK,CAAC,MAAM,YAAY,CACxD,MACA,iBAAiB,QAAQ,MAAM,CAChC,CAAC,CACH;;AAGH,SAAS,iBACP,aACA,YAC6B;CAC7B,MAAM,kBAAkB,OAAO,KAAK,YAAY;CAChD,MAAMC,0BAAsD,EAAE;AAE9D,MAAK,MAAM,QAAQ,iBAAiB;EAClC,MAAM,aAAa,YAAY;AAC/B,MAAI,WACF,yBAAwB,KAAK,WAAW,UAAU;;AAItD,QAAO,QACL,CAAC,YAAY,GAAG,wBAAwB,GACvC,OAAO,GAAG,cAAc;EACvB,MAAMC,sBAAkD,EAAE;AAC1D,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,OAAO,gBAAgB;GAC7B,MAAM,WAAW,UAAU;AAC3B,OAAI,QAAQ,aAAa,OACvB,qBAAoB,QAAQ;;AAIhC,SAAO;GACL;GACA,aAAa;GACd;GAEJ;;AAGH,SAAS,iBACP,QACA,QACA;AACA,MAAK,MAAM,CAAC,gBAAgB,uBAAuB,OAAO,QAAQ,OAAO,EAAE;EACzE,MAAM,aAAa,OAAO;AAC1B,MAAI,WACF,YAAW,MAAM,mBAAmB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byearlybird/starling",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",