@byearlybird/starling 0.12.0 → 0.13.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/dist/index.js CHANGED
@@ -1,35 +1,7 @@
1
- import { c as makeResource, i as mapToDocument, l as mergeResources, o as mergeDocuments, s as deleteResource, u as createClock } from "./core-UUzgRHaU.js";
1
+ import { atom, batched, computed, map } from "nanostores";
2
2
 
3
- //#region src/database/emitter.ts
4
- function createEmitter() {
5
- const handlers = /* @__PURE__ */ new Map();
6
- return {
7
- on(type, handler) {
8
- let set = handlers.get(type);
9
- if (!set) {
10
- set = /* @__PURE__ */ new Set();
11
- handlers.set(type, set);
12
- }
13
- set.add(handler);
14
- return () => {
15
- set?.delete(handler);
16
- if (!set?.size) handlers.delete(type);
17
- };
18
- },
19
- emit(type, payload) {
20
- const set = handlers.get(type);
21
- if (!set) return;
22
- for (const handler of Array.from(set)) handler(payload);
23
- },
24
- clear() {
25
- handlers.clear();
26
- }
27
- };
28
- }
29
-
30
- //#endregion
31
- //#region src/database/standard-schema.ts
32
- function standardValidate(schema, input) {
3
+ //#region lib/store/schema.ts
4
+ function validate(schema, input) {
33
5
  const result = schema["~standard"].validate(input);
34
6
  if (result instanceof Promise) throw new TypeError("Schema validation must be synchronous");
35
7
  if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
@@ -37,426 +9,335 @@ function standardValidate(schema, input) {
37
9
  }
38
10
 
39
11
  //#endregion
40
- //#region src/database/collection.ts
41
- /**
42
- * Symbols for internal collection methods used by transactions.
43
- * These are not part of the public Collection type.
44
- */
45
- const CollectionInternals = {
46
- getPendingMutations: Symbol("getPendingMutations"),
47
- emitMutations: Symbol("emitMutations"),
48
- replaceData: Symbol("replaceData"),
49
- data: Symbol("data")
50
- };
51
- function createCollection(name, schema, getId, getEventstamp, initialData, options) {
52
- const autoFlush = options?.autoFlush ?? true;
53
- const data = initialData ?? /* @__PURE__ */ new Map();
54
- const emitter = createEmitter();
55
- const pendingMutations = {
56
- added: [],
57
- updated: [],
58
- removed: []
12
+ //#region lib/core/hex.ts
13
+ function toHex(value, padLength) {
14
+ return value.toString(16).padStart(padLength, "0");
15
+ }
16
+ function nonce(length) {
17
+ const bytes = new Uint8Array(length / 2);
18
+ crypto.getRandomValues(bytes);
19
+ return Array.from(bytes).map((b) => toHex(b, 2)).join("");
20
+ }
21
+
22
+ //#endregion
23
+ //#region lib/core/clock.ts
24
+ const MS_LENGTH = 12;
25
+ const SEQ_LENGTH = 6;
26
+ const NONCE_LENGTH = 6;
27
+ function advanceClock(current, next) {
28
+ if (next.ms > current.ms) return {
29
+ ms: next.ms,
30
+ seq: next.seq
59
31
  };
60
- const flushMutations = () => {
61
- if (pendingMutations.added.length > 0 || pendingMutations.updated.length > 0 || pendingMutations.removed.length > 0) {
62
- emitter.emit("mutation", {
63
- added: [...pendingMutations.added],
64
- updated: [...pendingMutations.updated],
65
- removed: [...pendingMutations.removed]
66
- });
67
- pendingMutations.added = [];
68
- pendingMutations.updated = [];
69
- pendingMutations.removed = [];
70
- }
32
+ else if (next.ms === current.ms) return {
33
+ ms: current.ms,
34
+ seq: Math.max(current.seq, next.seq) + 1
71
35
  };
72
- return {
73
- get(id, opts = {}) {
74
- const resource = data.get(id);
75
- if (!resource) return null;
76
- if (!opts.includeDeleted && resource.meta.deletedAt) return null;
77
- return resource.attributes;
78
- },
79
- getAll(opts = {}) {
80
- const resources = Array.from(data.values());
81
- if (opts.includeDeleted) return resources.map((resource) => resource.attributes);
82
- else return resources.filter((resource) => !resource.meta.deletedAt).map((resource) => resource.attributes);
83
- },
84
- find(filter, opts) {
85
- const results = [];
86
- for (const [, resource] of data.entries()) {
87
- if (resource.meta.deletedAt) continue;
88
- const attributes = resource.attributes;
89
- if (filter(attributes)) {
90
- const value = opts?.map ? opts.map(attributes) : attributes;
91
- results.push(value);
92
- }
93
- }
94
- if (opts?.sort) results.sort(opts.sort);
95
- return results;
96
- },
97
- add(item) {
98
- const validated = standardValidate(schema, item);
99
- const id = getId(validated);
100
- if (data.has(id)) throw new DuplicateIdError(id);
101
- const resource = makeResource(id, validated, getEventstamp());
102
- data.set(id, resource);
103
- pendingMutations.added.push({
104
- id,
105
- item: validated
106
- });
107
- if (autoFlush) flushMutations();
108
- return validated;
109
- },
110
- update(id, updates) {
111
- const existing = data.get(id);
112
- if (!existing) throw new IdNotFoundError(id);
113
- const before = existing.attributes;
114
- const merged = mergeResources(existing, makeResource(id, updates, getEventstamp()));
115
- standardValidate(schema, merged.attributes);
116
- data.set(id, merged);
117
- pendingMutations.updated.push({
118
- id,
119
- before,
120
- after: merged.attributes
121
- });
122
- if (autoFlush) flushMutations();
123
- },
124
- remove(id) {
125
- const existing = data.get(id);
126
- if (!existing) throw new IdNotFoundError(id);
127
- const item = existing.attributes;
128
- const removed = deleteResource(existing, getEventstamp());
129
- data.set(id, removed);
130
- pendingMutations.removed.push({
131
- id,
132
- item
133
- });
134
- if (autoFlush) flushMutations();
135
- },
136
- merge(document) {
137
- const beforeState = /* @__PURE__ */ new Map();
138
- for (const [id, resource] of data.entries()) beforeState.set(id, resource.attributes);
139
- const result = mergeDocuments(mapToDocument(name, data, getEventstamp()), document);
140
- data.clear();
141
- for (const [id, resource] of Object.entries(result.document.resources)) data.set(id, resource);
142
- for (const [id, resource] of result.changes.added) {
143
- standardValidate(schema, resource.attributes);
144
- pendingMutations.added.push({
145
- id,
146
- item: resource.attributes
147
- });
148
- }
149
- for (const [id, resource] of result.changes.updated) {
150
- standardValidate(schema, resource.attributes);
151
- const before = beforeState.get(id);
152
- pendingMutations.updated.push({
153
- id,
154
- before,
155
- after: resource.attributes
156
- });
157
- }
158
- for (const id of result.changes.deleted) {
159
- const before = beforeState.get(id);
160
- pendingMutations.removed.push({
161
- id,
162
- item: before
163
- });
164
- }
165
- if (autoFlush) flushMutations();
166
- },
167
- toDocument() {
168
- return mapToDocument(name, data, getEventstamp());
169
- },
170
- on(event, handler) {
171
- return emitter.on(event, handler);
172
- },
173
- [CollectionInternals.data]() {
174
- return new Map(data);
175
- },
176
- [CollectionInternals.getPendingMutations]() {
177
- return {
178
- added: [...pendingMutations.added],
179
- updated: [...pendingMutations.updated],
180
- removed: [...pendingMutations.removed]
181
- };
182
- },
183
- [CollectionInternals.emitMutations](mutations) {
184
- if (mutations.added.length > 0 || mutations.updated.length > 0 || mutations.removed.length > 0) emitter.emit("mutation", mutations);
185
- },
186
- [CollectionInternals.replaceData](newData) {
187
- data.clear();
188
- for (const [id, resource] of newData.entries()) data.set(id, resource);
189
- }
36
+ else return {
37
+ ms: current.ms,
38
+ seq: current.seq + 1
190
39
  };
191
40
  }
192
- var IdNotFoundError = class extends Error {
193
- constructor(id) {
194
- super(`Resource with id ${id} not found`);
195
- this.name = "IdNotFoundError";
196
- }
197
- };
198
- var DuplicateIdError = class extends Error {
199
- constructor(id) {
200
- super(`Resource with id ${id} already exists`);
201
- this.name = "DuplicateIdError";
202
- }
203
- };
41
+ function makeStamp(ms, seq) {
42
+ return `${toHex(ms, MS_LENGTH)}${toHex(seq, SEQ_LENGTH)}${nonce(NONCE_LENGTH)}`;
43
+ }
204
44
 
205
45
  //#endregion
206
- //#region src/database/query.ts
46
+ //#region lib/core/flatten.ts
207
47
  /**
208
- * Execute a reactive query with automatic re-computation on mutations.
209
- *
210
- * @param db - Database instance to query
211
- * @param callback - Query callback receiving read-only collection handles
212
- * @returns QueryHandle with result, subscribe, and dispose methods
48
+ * Flattens a nested object into a flat object with dot-notation keys
49
+ * @param obj - The object to flatten
50
+ * @param mapper - Optional callback to transform leaf values
51
+ * @returns A flattened object with dot-notation keys
213
52
  */
214
- function executeQuery(db, callback) {
215
- const accessedCollections = /* @__PURE__ */ new Set();
216
- const subscribers = /* @__PURE__ */ new Set();
217
- let currentResult;
218
- const createTrackingHandles = () => {
219
- const handles = {};
220
- for (const name of db.collectionKeys()) {
221
- const collection = db[name];
222
- handles[name] = createTrackingHandle(name, collection, accessedCollections);
223
- }
224
- return handles;
53
+ function flatten(obj, mapper) {
54
+ const result = {};
55
+ const addLeaf = (value, path) => {
56
+ if (path) result[path] = mapper ? mapper(value, path) : value;
225
57
  };
226
- const runQuery = () => {
227
- return callback(createTrackingHandles());
228
- };
229
- currentResult = runQuery();
230
- const unsubscribeMutation = db.on("mutation", (event) => {
231
- if (accessedCollections.has(event.collection)) {
232
- currentResult = runQuery();
233
- for (const subscriber of subscribers) subscriber(currentResult);
234
- }
235
- });
236
- let disposed = false;
237
- return {
238
- get result() {
239
- return currentResult;
240
- },
241
- subscribe(callback$1) {
242
- if (disposed) throw new Error("Cannot subscribe to a disposed query");
243
- subscribers.add(callback$1);
244
- return () => {
245
- subscribers.delete(callback$1);
246
- };
247
- },
248
- dispose() {
249
- if (disposed) return;
250
- disposed = true;
251
- unsubscribeMutation();
252
- subscribers.clear();
253
- accessedCollections.clear();
58
+ function traverse(current, prefix = "") {
59
+ if (!shouldTraverse(current)) {
60
+ addLeaf(current, prefix);
61
+ return;
254
62
  }
255
- };
63
+ for (const [key, value] of Object.entries(current)) traverse(value, prefix ? `${prefix}.${key}` : key);
64
+ }
65
+ traverse(obj);
66
+ return result;
256
67
  }
257
68
  /**
258
- * Create a read-only collection handle that tracks access.
69
+ * Unflattens a flat object with dot-notation keys into a nested object
70
+ * @param obj - The flattened object to unflatten
71
+ * @param mapper - Optional callback to transform leaf values before placing them
72
+ * @returns A nested object
259
73
  */
260
- function createTrackingHandle(name, collection, accessedCollections) {
261
- const trackAccess = () => {
262
- accessedCollections.add(name);
263
- };
264
- return {
265
- get(id, opts) {
266
- trackAccess();
267
- return collection.get(id, opts);
268
- },
269
- getAll(opts) {
270
- trackAccess();
271
- return collection.getAll(opts);
272
- },
273
- find(filter, opts) {
274
- trackAccess();
275
- return collection.find(filter, opts);
74
+ function unflatten(obj, mapper) {
75
+ const result = {};
76
+ for (const [path, value] of Object.entries(obj)) {
77
+ const keys = path.split(".");
78
+ const mappedValue = mapper ? mapper(value, path) : value;
79
+ let current = result;
80
+ for (let i = 0; i < keys.length - 1; i++) {
81
+ const key = keys[i];
82
+ if (!(key in current)) current[key] = {};
83
+ current = current[key];
276
84
  }
277
- };
85
+ const finalKey = keys[keys.length - 1];
86
+ current[finalKey] = mappedValue;
87
+ }
88
+ return result;
89
+ }
90
+ function isPlainObject(value) {
91
+ return typeof value === "object" && value !== null && !Array.isArray(value) && (value.constructor === Object || Object.getPrototypeOf(value) === null);
92
+ }
93
+ function shouldTraverse(value) {
94
+ return isPlainObject(value) && Object.keys(value).length > 0;
278
95
  }
279
96
 
280
97
  //#endregion
281
- //#region src/database/transaction.ts
282
- /**
283
- * Execute a transaction with snapshot isolation and copy-on-write optimization.
284
- *
285
- * @param configs - Collection configurations for creating new instances
286
- * @param collections - Active collection instances (mutable reference)
287
- * @param getEventstamp - Function to generate eventstamps
288
- * @param callback - Transaction callback with tx context
289
- * @returns The return value from the callback
290
- *
291
- * @remarks
292
- * - Collections are cloned lazily on first access (read or write)
293
- * - Provides snapshot isolation: tx sees consistent data from first access
294
- * - Explicit rollback via tx.rollback() or implicit on exception
295
- * - Only modified collections are committed back
296
- */
297
- function executeTransaction(configs, collections, getEventstamp, callback) {
298
- const clonedCollections = /* @__PURE__ */ new Map();
299
- const txHandles = {};
300
- for (const name of Object.keys(collections)) {
301
- const originalCollection = collections[name];
302
- const config = configs[name];
303
- const getClonedCollection = () => {
304
- if (!clonedCollections.has(name)) {
305
- const cloned = createCollection(name, config.schema, config.getId, getEventstamp, originalCollection[CollectionInternals.data](), { autoFlush: false });
306
- clonedCollections.set(name, cloned);
307
- }
308
- return clonedCollections.get(name);
309
- };
310
- txHandles[name] = createLazyTransactionHandle(originalCollection, getClonedCollection);
98
+ //#region lib/core/document.ts
99
+ function makeDocument(fields, stamp) {
100
+ return flatten(fields, (value) => ({
101
+ "~value": value,
102
+ "~stamp": stamp
103
+ }));
104
+ }
105
+ function parseDocument(document) {
106
+ return unflatten(document, (field) => field["~value"]);
107
+ }
108
+ function mergeDocuments(target, source) {
109
+ const result = {};
110
+ const keys = new Set([...Object.keys(target), ...Object.keys(source)]);
111
+ for (const key of keys) {
112
+ const targetValue = target[key];
113
+ const sourceValue = source[key];
114
+ if (targetValue && sourceValue) result[key] = targetValue["~stamp"] > sourceValue["~stamp"] ? targetValue : sourceValue;
115
+ else if (targetValue) result[key] = targetValue;
116
+ else if (sourceValue) result[key] = sourceValue;
117
+ else throw new Error(`Key ${key} not found in either document`);
311
118
  }
312
- let shouldRollback = false;
313
- const tx = {
314
- ...txHandles,
315
- rollback() {
316
- shouldRollback = true;
317
- }
318
- };
319
- let result;
320
- result = callback(tx);
321
- if (!shouldRollback) for (const [name, clonedCollection] of clonedCollections.entries()) {
322
- const originalCollection = collections[name];
323
- const pendingMutations = clonedCollection[CollectionInternals.getPendingMutations]();
324
- originalCollection[CollectionInternals.replaceData](clonedCollection[CollectionInternals.data]());
325
- originalCollection[CollectionInternals.emitMutations](pendingMutations);
119
+ return result;
120
+ }
121
+
122
+ //#endregion
123
+ //#region lib/core/tombstone.ts
124
+ function mergeTombstones(target, source) {
125
+ const result = {};
126
+ const keys = new Set([...Object.keys(target), ...Object.keys(source)]);
127
+ for (const key of keys) {
128
+ const targetStamp = target[key];
129
+ const sourceStamp = source[key];
130
+ if (targetStamp && sourceStamp) result[key] = targetStamp > sourceStamp ? targetStamp : sourceStamp;
131
+ else if (targetStamp) result[key] = targetStamp;
132
+ else if (sourceStamp) result[key] = sourceStamp;
326
133
  }
327
134
  return result;
328
135
  }
329
- /**
330
- * Create a transaction handle that lazily clones on first access (copy-on-write).
331
- *
332
- * @param originalCollection - The base collection (not modified)
333
- * @param getClonedCollection - Lazy cloner (invoked on first access)
334
- * @returns A collection handle with snapshot isolation
335
- *
336
- * @remarks
337
- * First read or write triggers cloning, providing snapshot isolation.
338
- * All subsequent operations use the cloned collection.
339
- * Excluded methods:
340
- * - on(): events are only emitted after the transaction commits
341
- * - toDocument(): serialization should happen outside transactions
342
- */
343
- function createLazyTransactionHandle(_originalCollection, getClonedCollection) {
344
- let cloned = null;
345
- const ensureCloned = () => {
346
- if (!cloned) cloned = getClonedCollection();
347
- return cloned;
136
+
137
+ //#endregion
138
+ //#region lib/core/collection.ts
139
+ function mergeCollections$1(target, source) {
140
+ const mergedTombstones = mergeTombstones(target.tombstones, source.tombstones);
141
+ const mergedDocuments = {};
142
+ const allDocumentIds = new Set([...Object.keys(target.documents), ...Object.keys(source.documents)]);
143
+ for (const id of allDocumentIds) {
144
+ const targetDoc = target.documents[id];
145
+ const sourceDoc = source.documents[id];
146
+ if (mergedTombstones[id]) continue;
147
+ if (targetDoc && sourceDoc) mergedDocuments[id] = mergeDocuments(targetDoc, sourceDoc);
148
+ else if (targetDoc) mergedDocuments[id] = targetDoc;
149
+ else if (sourceDoc) mergedDocuments[id] = sourceDoc;
150
+ }
151
+ return {
152
+ documents: mergedDocuments,
153
+ tombstones: mergedTombstones
348
154
  };
155
+ }
156
+
157
+ //#endregion
158
+ //#region lib/store/collection.ts
159
+ function addDocument($documents, config, tick, data) {
160
+ const getId = defineGetId(config);
161
+ const valid = validate(config.schema, data);
162
+ const doc = makeDocument(valid, tick());
163
+ const id = getId(valid);
164
+ $documents.setKey(id, doc);
165
+ }
166
+ function removeDocument($documents, $tombstones, tick, id) {
167
+ $tombstones.setKey(id, tick());
168
+ $documents.setKey(id, void 0);
169
+ }
170
+ function updateDocument($documents, config, tick, id, document) {
171
+ const current = $documents.get()[id];
172
+ if (!current) return;
173
+ const doc = mergeDocuments(current, makeDocument(document, tick()));
174
+ validate(config.schema, parseDocument(doc));
175
+ $documents.setKey(id, doc);
176
+ }
177
+ function mergeCollectionSnapshot($documents, $tombstones, currentSnapshot, incomingSnapshot) {
178
+ const merged = mergeCollections$1(currentSnapshot, incomingSnapshot);
179
+ $documents.set(merged.documents);
180
+ $tombstones.set(merged.tombstones);
181
+ }
182
+ function createCollection(config, clock) {
183
+ const { $data, $snapshot, $documents, $tombstones } = createCollectionState();
349
184
  return {
350
- get(id, opts) {
351
- return ensureCloned().get(id, opts);
185
+ $data,
186
+ $snapshot,
187
+ get(key) {
188
+ return $data.get().get(key);
189
+ },
190
+ has(key) {
191
+ return $data.get().has(key);
192
+ },
193
+ keys() {
194
+ return $data.get().keys();
195
+ },
196
+ values() {
197
+ return $data.get().values();
352
198
  },
353
- getAll(opts) {
354
- return ensureCloned().getAll(opts);
199
+ entries() {
200
+ return $data.get().entries();
355
201
  },
356
- find(filter, opts) {
357
- return ensureCloned().find(filter, opts);
202
+ forEach(callbackfn, thisArg) {
203
+ return $data.get().forEach(callbackfn, thisArg);
358
204
  },
359
- add(item) {
360
- return ensureCloned().add(item);
205
+ get size() {
206
+ return $data.get().size;
361
207
  },
362
- update(id, updates) {
363
- ensureCloned().update(id, updates);
208
+ add(data) {
209
+ addDocument($documents, config, clock.tick, data);
364
210
  },
365
211
  remove(id) {
366
- ensureCloned().remove(id);
212
+ removeDocument($documents, $tombstones, clock.tick, id);
367
213
  },
368
- merge(document) {
369
- ensureCloned().merge(document);
214
+ update(id, document) {
215
+ updateDocument($documents, config, clock.tick, id, document);
216
+ },
217
+ merge(snapshot) {
218
+ mergeCollectionSnapshot($documents, $tombstones, $snapshot.get(), snapshot);
370
219
  }
371
220
  };
372
221
  }
222
+ function createCollectionState() {
223
+ const $documents = map({});
224
+ const $tombstones = map({});
225
+ const $snapshot = batched([$documents, $tombstones], parseSnapshot);
226
+ return {
227
+ $data: batched([$documents, $tombstones], parseCollection),
228
+ $snapshot,
229
+ $documents,
230
+ $tombstones
231
+ };
232
+ }
233
+ function hasIdProperty(data) {
234
+ return typeof data === "object" && data !== null && "id" in data && typeof data.id === "string";
235
+ }
236
+ function parseCollection(documents, tombstones) {
237
+ const result = /* @__PURE__ */ new Map();
238
+ for (const [id, doc] of Object.entries(documents)) if (!tombstones[id] && doc) result.set(id, parseDocument(doc));
239
+ return result;
240
+ }
241
+ function parseSnapshot(documents, tombstones) {
242
+ return {
243
+ documents,
244
+ tombstones
245
+ };
246
+ }
247
+ function hasGetId(config) {
248
+ return "getId" in config && typeof config.getId === "function";
249
+ }
250
+ function defineGetId(config) {
251
+ return hasGetId(config) ? config.getId : defaultGetId;
252
+ }
253
+ function defaultGetId(data) {
254
+ if (hasIdProperty(data)) return data.id;
255
+ throw new Error("Schema must have an 'id' property when getId is not provided");
256
+ }
373
257
 
374
258
  //#endregion
375
- //#region src/database/db.ts
376
- /**
377
- * Create a typed database instance with collection access.
378
- * @param config - Database configuration
379
- * @param config.name - Database name used for persistence and routing
380
- * @param config.schema - Collection schema definitions
381
- * @param config.version - Optional database version, defaults to 1
382
- * @returns A database instance with typed collection properties
383
- *
384
- * @example
385
- * ```typescript
386
- * const db = await createDatabase({
387
- * name: "my-app",
388
- * schema: {
389
- * tasks: { schema: taskSchema, getId: (task) => task.id },
390
- * },
391
- * })
392
- * .use(idbPlugin())
393
- * .init();
394
- *
395
- * const task = db.tasks.add({ title: 'Learn Starling' });
396
- * ```
397
- */
398
- function createDatabase(config) {
399
- const { name, schema, version = 1 } = config;
400
- const clock = createClock();
401
- const getEventstamp = () => clock.now();
402
- const collections = makeCollections(schema, getEventstamp);
403
- const publicCollections = collections;
404
- const dbEmitter = createEmitter();
405
- for (const collectionName of Object.keys(collections)) collections[collectionName].on("mutation", (mutations) => {
406
- if (mutations.added.length > 0 || mutations.updated.length > 0 || mutations.removed.length > 0) dbEmitter.emit("mutation", {
407
- collection: collectionName,
408
- added: mutations.added,
409
- updated: mutations.updated,
410
- removed: mutations.removed
259
+ //#region lib/store/clock.ts
260
+ function createClock() {
261
+ const $state = atom(nowClock());
262
+ const tick = () => {
263
+ const next = advanceClock($state.get(), nowClock());
264
+ $state.set(next);
265
+ return makeStamp(next.ms, next.seq);
266
+ };
267
+ const advance = (ms, seq) => {
268
+ const next = advanceClock($state.get(), {
269
+ ms,
270
+ seq
411
271
  });
412
- });
413
- const plugins = [];
414
- const db = {
415
- ...publicCollections,
416
- name,
417
- version,
418
- begin(callback) {
419
- return executeTransaction(schema, collections, getEventstamp, callback);
420
- },
421
- query(callback) {
422
- return executeQuery(db, callback);
423
- },
424
- toDocuments() {
425
- const documents = {};
426
- for (const dbName of Object.keys(collections)) documents[dbName] = collections[dbName].toDocument();
427
- return documents;
428
- },
429
- on(event, handler) {
430
- return dbEmitter.on(event, handler);
431
- },
432
- use(plugin) {
433
- plugins.push(plugin);
434
- return db;
435
- },
436
- async init() {
437
- for (const plugin of plugins) if (plugin.handlers.init) await plugin.handlers.init(db);
438
- return db;
439
- },
440
- async dispose() {
441
- for (let i = plugins.length - 1; i >= 0; i--) {
442
- const plugin = plugins[i];
443
- if (plugin?.handlers.dispose) await plugin.handlers.dispose(db);
444
- }
272
+ $state.set(next);
273
+ };
274
+ return {
275
+ $state,
276
+ tick,
277
+ advance
278
+ };
279
+ }
280
+ function nowClock() {
281
+ return {
282
+ ms: Date.now(),
283
+ seq: 0
284
+ };
285
+ }
286
+
287
+ //#endregion
288
+ //#region lib/store/store.ts
289
+ function createStore(config) {
290
+ const clock = createClock();
291
+ const collections = initCollections(config.collections, clock);
292
+ const $snapshot = parseCollections(collections, clock.$state);
293
+ function getCollectionDataStores(collectionNames) {
294
+ return collectionNames.map((name) => collections[name].$data);
295
+ }
296
+ return {
297
+ ...collections,
298
+ $snapshot,
299
+ query: (collectionNames, callback) => {
300
+ return computed(getCollectionDataStores(collectionNames), (...values) => {
301
+ const entries = collectionNames.map((name, i) => [name, values[i]]);
302
+ return callback(Object.fromEntries(entries));
303
+ });
445
304
  },
446
- collectionKeys() {
447
- return Object.keys(collections);
305
+ merge: (snapshot) => {
306
+ clock.advance(snapshot.clock.ms, snapshot.clock.seq);
307
+ mergeCollections(collections, snapshot.collections);
448
308
  }
449
309
  };
450
- return db;
451
310
  }
452
- function makeCollections(configs, getEventstamp) {
453
- const collections = {};
454
- for (const name of Object.keys(configs)) {
455
- const config = configs[name];
456
- collections[name] = createCollection(name, config.schema, config.getId, getEventstamp);
311
+ function initCollections(collectionsConfig, clock) {
312
+ return Object.fromEntries(Object.entries(collectionsConfig).map(([name, config]) => [name, createCollection(config, clock)]));
313
+ }
314
+ function parseCollections(collections, clockState) {
315
+ const collectionNames = Object.keys(collections);
316
+ const collectionSnapshotAtoms = [];
317
+ for (const name of collectionNames) {
318
+ const collection = collections[name];
319
+ if (collection) collectionSnapshotAtoms.push(collection.$snapshot);
320
+ }
321
+ return batched([clockState, ...collectionSnapshotAtoms], (clock, ...snapshots) => {
322
+ const collectionsSnapshot = {};
323
+ for (let i = 0; i < collectionNames.length; i++) {
324
+ const name = collectionNames[i];
325
+ const snapshot = snapshots[i];
326
+ if (name && snapshot !== void 0) collectionsSnapshot[name] = snapshot;
327
+ }
328
+ return {
329
+ clock,
330
+ collections: collectionsSnapshot
331
+ };
332
+ });
333
+ }
334
+ function mergeCollections(target, source) {
335
+ for (const [collectionName, collectionSnapshot] of Object.entries(source)) {
336
+ const collection = target[collectionName];
337
+ if (collection) collection.merge(collectionSnapshot);
457
338
  }
458
- return collections;
459
339
  }
460
340
 
461
341
  //#endregion
462
- export { CollectionInternals, DuplicateIdError, IdNotFoundError, createDatabase };
342
+ export { createStore };
343
+ //# sourceMappingURL=index.js.map