@byearlybird/starling 0.11.1 → 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/README.md +274 -0
- package/dist/index.d.ts +73 -3
- package/dist/index.js +291 -410
- package/dist/index.js.map +1 -0
- package/package.json +36 -44
- package/dist/core-DI0FfUjX.js +0 -423
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -3
- package/dist/db-DJ_6dO-K.d.ts +0 -199
- package/dist/index-D7bXWDg6.d.ts +0 -270
- package/dist/plugin-http.d.ts +0 -139
- package/dist/plugin-http.js +0 -191
- package/dist/plugin-idb.d.ts +0 -59
- package/dist/plugin-idb.js +0 -169
package/dist/index.js
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { atom, batched, computed, map } from "nanostores";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
4
|
-
function
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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(name, 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(name, 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(data, getEventstamp()), document);
|
|
140
|
-
data.clear();
|
|
141
|
-
for (const resource of result.document.data) data.set(resource.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(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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
46
|
+
//#region lib/core/flatten.ts
|
|
207
47
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* @param
|
|
211
|
-
* @
|
|
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
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
*
|
|
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
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
354
|
-
return
|
|
199
|
+
entries() {
|
|
200
|
+
return $data.get().entries();
|
|
355
201
|
},
|
|
356
|
-
|
|
357
|
-
return
|
|
202
|
+
forEach(callbackfn, thisArg) {
|
|
203
|
+
return $data.get().forEach(callbackfn, thisArg);
|
|
358
204
|
},
|
|
359
|
-
|
|
360
|
-
return
|
|
205
|
+
get size() {
|
|
206
|
+
return $data.get().size;
|
|
361
207
|
},
|
|
362
|
-
|
|
363
|
-
|
|
208
|
+
add(data) {
|
|
209
|
+
addDocument($documents, config, clock.tick, data);
|
|
364
210
|
},
|
|
365
211
|
remove(id) {
|
|
366
|
-
|
|
212
|
+
removeDocument($documents, $tombstones, clock.tick, id);
|
|
367
213
|
},
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
447
|
-
|
|
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
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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 {
|
|
342
|
+
export { createStore };
|
|
343
|
+
//# sourceMappingURL=index.js.map
|