@procedures/signaldb 0.0.0-stage → 0.24.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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +101 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/codec.d.ts +53 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +51 -0
- package/dist/codec.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +224 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +485 -0
- package/dist/storage.js.map +1 -0
- package/package.json +75 -3
- package/src/codec.ts +74 -0
- package/src/index.ts +13 -0
- package/src/storage.ts +566 -0
package/dist/storage.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A collection storage over the SignalDB collection of an app (roadmap CLI-23, server stack D9).
|
|
3
|
+
*
|
|
4
|
+
* The app owns the SignalDB `Collection`: its persistence, its indexes, its cursors and its
|
|
5
|
+
* reactivity. `SignalDbStorage` reads and writes the items of that collection through the
|
|
6
|
+
* `CollectionStorage` contract. Thus the collection is the local copy of a `HybridStorage`, and
|
|
7
|
+
* the `HybridStorage` owns the sync: the queue, the sync state and the remote.
|
|
8
|
+
*
|
|
9
|
+
* The rules:
|
|
10
|
+
* - Each operation waits for the load of the persistence of the collection (`isReady()`). A failed
|
|
11
|
+
* load does not stop the storage: the collection then keeps its items in memory, as SignalDB does.
|
|
12
|
+
* - A write of SignalDB is synchronous. Each write of the storage reads, tests and writes in one
|
|
13
|
+
* synchronous step. Thus two writes do not mix, and a patch and a precondition are atomic.
|
|
14
|
+
* - A write of several items is one `batch()` of the collection: the indexes and the cursors update
|
|
15
|
+
* one time. The validators of the collection examine each item before the first write. Thus an
|
|
16
|
+
* item that a validator refuses changes nothing.
|
|
17
|
+
* - Each write takes a copy of its value, and each read gives a new copy. A cursor of SignalDB gives
|
|
18
|
+
* copies (`structuredClone`) of the items, and the storage reads only through cursors.
|
|
19
|
+
* - `observe()` gives the batches of the writes of the storage, with their origins. It also gives
|
|
20
|
+
* each change that the app makes directly on the collection: one batch for each change, with no
|
|
21
|
+
* origin. Thus a `HybridStorage` with `capture: "observe"` sends the writes of the app.
|
|
22
|
+
* - `query()` applies `queryEntries()` to the values. It does not give the selector to SignalDB.
|
|
23
|
+
* SignalDB applies Mongo selectors to the items with `mingo`. The contract needs the query
|
|
24
|
+
* language of the client on the values.
|
|
25
|
+
* - `close()` stops the storage. The collection stays open, with its items: the app disposes it.
|
|
26
|
+
*
|
|
27
|
+
* The limits:
|
|
28
|
+
* - A load of the persistence (`persistence.received`) changes the items without the events of a
|
|
29
|
+
* change. `observe()` thus does not tell it. The order of the entries after a load is the order of
|
|
30
|
+
* the persistence adapter.
|
|
31
|
+
* - The collection must not have a `transform`: the storage reads the items through cursors.
|
|
32
|
+
* - Each id is a string. An item of the collection whose `id` is not a string is an error.
|
|
33
|
+
* - The storage keeps no change log, so it has no `getChangesSince()`. A cursor copies each item,
|
|
34
|
+
* so the storage has no cheap `getIds()` either.
|
|
35
|
+
* - A listener of the app that throws in a write of the storage stops the write, and the write
|
|
36
|
+
* rejects. SignalDB keeps the changes before the error. The observers get the changes that the
|
|
37
|
+
* storage knows.
|
|
38
|
+
*/
|
|
39
|
+
import { StorageListeners, reportListenerError } from "@procedures/collections";
|
|
40
|
+
import { VersionConflictError, applyPatch, patchProblems, preconditionNotSupported, queryEntries, queryOptionsProblems, selectorProblems, versionConflicts, } from "@procedures/client/query";
|
|
41
|
+
import { documentCodec, isPlainObject } from "./codec.js";
|
|
42
|
+
/** The options of the reads: no reactivity and no field tracking, also in a reactive scope of the app. */
|
|
43
|
+
const READ = { reactive: false, fieldTracking: false };
|
|
44
|
+
/** An id must be a string: it is the key of an item. */
|
|
45
|
+
function checkId(id) {
|
|
46
|
+
if (typeof id !== "string")
|
|
47
|
+
throw new TypeError(`An id must be a string, not ${typeof id}`);
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
/** A change with the origin of the write, when the write has one. */
|
|
51
|
+
function withOrigin(change, origin) {
|
|
52
|
+
return origin === undefined ? change : { ...change, origin };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A collection storage over the SignalDB collection of an app (roadmap CLI-23).
|
|
56
|
+
*
|
|
57
|
+
* - The collection keeps the items. The storage keeps nothing of its own, and a new storage of the
|
|
58
|
+
* same collection reads the same items.
|
|
59
|
+
* - `observe()` tells the writes of the storage and the changes that the app makes on the collection.
|
|
60
|
+
* - `query()` applies a selector, and `patch()` changes an item in one step.
|
|
61
|
+
* - After `close()`, each operation rejects, and a second `close()` does nothing.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import { Collection } from "@signaldb/core";
|
|
66
|
+
* import { ApiStorage, HybridStorage } from "@procedures/store/browser";
|
|
67
|
+
* import { SignalDbStorage } from "@procedures/signaldb";
|
|
68
|
+
*
|
|
69
|
+
* const todos = new Collection<Todo>({ persistence: createIndexedDBAdapter("todos") });
|
|
70
|
+
* const storage = new HybridStorage<Todo>(new ApiStorage(client, { collection: "todos" }), {
|
|
71
|
+
* mode: "write-back",
|
|
72
|
+
* local: new SignalDbStorage<Todo>(todos),
|
|
73
|
+
* capture: "observe",
|
|
74
|
+
* });
|
|
75
|
+
* todos.insert({ id: "t1", title: "Write the docs" }); // the storage sends it to the server
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export class SignalDbStorage {
|
|
79
|
+
collection;
|
|
80
|
+
codec;
|
|
81
|
+
/** The query policy of a call without a policy */
|
|
82
|
+
queryPolicy;
|
|
83
|
+
/** The version of a value, for the preconditions of the writes (roadmap CLI-16) */
|
|
84
|
+
versionOf;
|
|
85
|
+
onObserverError;
|
|
86
|
+
/** The observers of `observe()` */
|
|
87
|
+
observers;
|
|
88
|
+
/** The load of the persistence of the collection. A failed load also resolves. */
|
|
89
|
+
ready;
|
|
90
|
+
/** True while a write of this storage runs: its events are not changes of the app */
|
|
91
|
+
writing = false;
|
|
92
|
+
/** True while the storage listens to the events of the collection */
|
|
93
|
+
listening = false;
|
|
94
|
+
closed = false;
|
|
95
|
+
/**
|
|
96
|
+
* @param collection - The SignalDB collection of the app. The app owns it, and `close()` does not dispose it.
|
|
97
|
+
* @param options - The codec, the query policy, `versionOf` and the handler of the errors of the observers.
|
|
98
|
+
*/
|
|
99
|
+
constructor(collection, options = {}) {
|
|
100
|
+
if (typeof collection?.find !== "function" || typeof collection.replaceOne !== "function" || typeof collection.on !== "function") {
|
|
101
|
+
throw new TypeError("SignalDbStorage needs a SignalDB Collection");
|
|
102
|
+
}
|
|
103
|
+
this.collection = collection;
|
|
104
|
+
this.codec = options.codec ?? documentCodec();
|
|
105
|
+
this.queryPolicy = options.queryPolicy;
|
|
106
|
+
this.versionOf = options.versionOf;
|
|
107
|
+
this.onObserverError = options.onObserverError ?? reportListenerError;
|
|
108
|
+
this.observers = new StorageListeners(this.onObserverError);
|
|
109
|
+
// The collection still operates in memory after a failed load: the app gets `persistence.error`
|
|
110
|
+
this.ready = collection.isReady().then(() => undefined, () => undefined);
|
|
111
|
+
}
|
|
112
|
+
/** The name of the collection. */
|
|
113
|
+
get name() {
|
|
114
|
+
return this.collection.name;
|
|
115
|
+
}
|
|
116
|
+
//
|
|
117
|
+
// ═══ Read Operations ═══
|
|
118
|
+
//
|
|
119
|
+
async get(id) {
|
|
120
|
+
this.checkOpen();
|
|
121
|
+
checkId(id);
|
|
122
|
+
await this.whenReady();
|
|
123
|
+
const current = this.current(id);
|
|
124
|
+
return current.found ? current.value : undefined;
|
|
125
|
+
}
|
|
126
|
+
async getAll() {
|
|
127
|
+
return (await this.getEntries()).map(([, value]) => value);
|
|
128
|
+
}
|
|
129
|
+
/** Every item with its id, in the order of the collection: the order of the first set of each id. */
|
|
130
|
+
async getEntries() {
|
|
131
|
+
this.checkOpen();
|
|
132
|
+
await this.whenReady();
|
|
133
|
+
return this.collection
|
|
134
|
+
.find({}, READ)
|
|
135
|
+
.fetch()
|
|
136
|
+
.map((raw, index) => this.entryOf(raw, index));
|
|
137
|
+
}
|
|
138
|
+
/** The items for which the predicate gives true, in the order of the entries. The predicate gets a copy of each item. */
|
|
139
|
+
async find(predicate) {
|
|
140
|
+
return (await this.getAll()).filter((item) => predicate(item));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The entries whose values match the selector, with the order, the skip and the limit of
|
|
144
|
+
* `queryEntries()`. The policy is the policy of the call, or else of the options. A query that is
|
|
145
|
+
* not correct rejects with a `TypeError` before the storage reads the collection.
|
|
146
|
+
*/
|
|
147
|
+
async query(selector, options, policy) {
|
|
148
|
+
this.checkOpen();
|
|
149
|
+
const limits = policy ?? this.queryPolicy;
|
|
150
|
+
const problems = [...selectorProblems(selector, limits), ...queryOptionsProblems(options, limits)];
|
|
151
|
+
if (problems.length > 0)
|
|
152
|
+
throw new TypeError(`The query is not correct: ${problems.join(". ")}`);
|
|
153
|
+
return queryEntries(await this.getEntries(), selector, options, limits);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* This method starts to send the changes of the collection to the observer.
|
|
157
|
+
*
|
|
158
|
+
* - A write of this storage gives one batch, with the origin of its options, before the write resolves.
|
|
159
|
+
* - A change that the app makes directly on the collection gives one batch with one change and no
|
|
160
|
+
* origin, at once. Examples are `insert`, `updateOne`, `replaceOne` and `removeOne`.
|
|
161
|
+
* - The observers share each batch. A load of the persistence gives no batch.
|
|
162
|
+
*
|
|
163
|
+
* @returns The function that stops the batches. A second use of it does nothing.
|
|
164
|
+
* @throws Error after `close()`
|
|
165
|
+
*/
|
|
166
|
+
observe(observer) {
|
|
167
|
+
this.checkOpen();
|
|
168
|
+
const remove = this.observers.add(observer);
|
|
169
|
+
this.listen();
|
|
170
|
+
let stopped = false;
|
|
171
|
+
return () => {
|
|
172
|
+
if (stopped)
|
|
173
|
+
return;
|
|
174
|
+
stopped = true;
|
|
175
|
+
remove();
|
|
176
|
+
if (this.observers.size === 0)
|
|
177
|
+
this.unlisten();
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async has(id) {
|
|
181
|
+
this.checkOpen();
|
|
182
|
+
checkId(id);
|
|
183
|
+
await this.whenReady();
|
|
184
|
+
return this.collection.find({ id }, READ).count() > 0;
|
|
185
|
+
}
|
|
186
|
+
async size() {
|
|
187
|
+
this.checkOpen();
|
|
188
|
+
await this.whenReady();
|
|
189
|
+
return this.collection.find({}, READ).count();
|
|
190
|
+
}
|
|
191
|
+
//
|
|
192
|
+
// ═══ Write Operations ═══
|
|
193
|
+
//
|
|
194
|
+
async set(id, value, options) {
|
|
195
|
+
this.checkOpen();
|
|
196
|
+
checkId(id);
|
|
197
|
+
const expected = this.preconditions(options?.expectedVersion === undefined ? [] : [[id, options.expectedVersion]]);
|
|
198
|
+
const write = this.prepare(id, value);
|
|
199
|
+
await this.whenReady();
|
|
200
|
+
this.write(options?.origin, (changes) => {
|
|
201
|
+
this.checkVersions(expected);
|
|
202
|
+
this.put(write, changes);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
async delete(id, options) {
|
|
206
|
+
this.checkOpen();
|
|
207
|
+
checkId(id);
|
|
208
|
+
const expected = this.preconditions(options?.expectedVersion === undefined ? [] : [[id, options.expectedVersion]]);
|
|
209
|
+
await this.whenReady();
|
|
210
|
+
return this.write(options?.origin, (changes) => {
|
|
211
|
+
this.checkVersions(expected);
|
|
212
|
+
return this.remove(id, changes);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/** This method deletes each item of the collection in one batch. A collection with items gives one `clear` change. */
|
|
216
|
+
async clear(options) {
|
|
217
|
+
this.checkOpen();
|
|
218
|
+
await this.whenReady();
|
|
219
|
+
this.write(options?.origin, (changes) => {
|
|
220
|
+
let removed = 0;
|
|
221
|
+
this.batch(() => {
|
|
222
|
+
removed = this.collection.removeMany({});
|
|
223
|
+
});
|
|
224
|
+
if (removed > 0)
|
|
225
|
+
changes.push({ type: "clear" });
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* This method changes the item of the id in one step (a JSON Merge Patch, or the operations
|
|
230
|
+
* `$set`, `$unset`, `$remove` and `$insert`), and gives a copy of the new item. A missing id stays
|
|
231
|
+
* missing, and the result is undefined.
|
|
232
|
+
*/
|
|
233
|
+
async patch(id, fields, options) {
|
|
234
|
+
this.checkOpen();
|
|
235
|
+
checkId(id);
|
|
236
|
+
const [problem] = patchProblems(fields);
|
|
237
|
+
if (problem !== undefined)
|
|
238
|
+
throw new TypeError(`The patch of ${JSON.stringify(id)} is not correct: ${problem}`);
|
|
239
|
+
// A copy: a change of the fields during the wait does not change the patch
|
|
240
|
+
const own = structuredClone(fields);
|
|
241
|
+
await this.whenReady();
|
|
242
|
+
return this.write(options?.origin, (changes) => {
|
|
243
|
+
const current = this.current(id);
|
|
244
|
+
if (!current.found)
|
|
245
|
+
return undefined;
|
|
246
|
+
const next = applyPatch(current.value, own);
|
|
247
|
+
this.put({ id, value: next, item: this.itemOf(id, next) }, changes);
|
|
248
|
+
// The collection keeps `next`: the caller gets its own copy
|
|
249
|
+
return structuredClone(next);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
//
|
|
253
|
+
// ═══ Bulk Operations ═══
|
|
254
|
+
//
|
|
255
|
+
/**
|
|
256
|
+
* This method sets several items in one batch of the collection. When one expected version has a
|
|
257
|
+
* conflict, or a validator of the collection refuses one item, nothing changes.
|
|
258
|
+
*/
|
|
259
|
+
async setBatch(items, options) {
|
|
260
|
+
this.checkOpen();
|
|
261
|
+
for (const [id] of items)
|
|
262
|
+
checkId(id);
|
|
263
|
+
const expected = this.preconditions(options?.expectedVersions ?? []);
|
|
264
|
+
const writes = items.map(([id, value]) => this.prepare(id, value));
|
|
265
|
+
if (writes.length === 0 && expected.length === 0)
|
|
266
|
+
return;
|
|
267
|
+
await this.whenReady();
|
|
268
|
+
this.write(options?.origin, (changes) => {
|
|
269
|
+
this.checkVersions(expected);
|
|
270
|
+
// The validators of the app refuse an item before the first write, so a refusal changes nothing
|
|
271
|
+
if (writes.length > 1)
|
|
272
|
+
for (const { item } of writes)
|
|
273
|
+
this.collection.emit("validate", item);
|
|
274
|
+
this.batch(() => {
|
|
275
|
+
for (const write of writes)
|
|
276
|
+
this.put(write, changes);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/** This method deletes several items in one batch of the collection. */
|
|
281
|
+
async deleteBatch(ids, options) {
|
|
282
|
+
this.checkOpen();
|
|
283
|
+
for (const id of ids)
|
|
284
|
+
checkId(id);
|
|
285
|
+
if (ids.length === 0)
|
|
286
|
+
return 0;
|
|
287
|
+
await this.whenReady();
|
|
288
|
+
return this.write(options?.origin, (changes) => {
|
|
289
|
+
let deleted = 0;
|
|
290
|
+
this.batch(() => {
|
|
291
|
+
for (const id of new Set(ids))
|
|
292
|
+
if (this.remove(id, changes))
|
|
293
|
+
deleted++;
|
|
294
|
+
});
|
|
295
|
+
return deleted;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async getBatch(ids) {
|
|
299
|
+
this.checkOpen();
|
|
300
|
+
for (const id of ids)
|
|
301
|
+
checkId(id);
|
|
302
|
+
await this.whenReady();
|
|
303
|
+
const result = new Map();
|
|
304
|
+
for (const id of new Set(ids)) {
|
|
305
|
+
const current = this.current(id);
|
|
306
|
+
if (current.found)
|
|
307
|
+
result.set(id, current.value);
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
//
|
|
312
|
+
// ═══ Lifecycle & Metadata ═══
|
|
313
|
+
//
|
|
314
|
+
/**
|
|
315
|
+
* This method stops the storage. The collection stays open, with its items: a new storage of the
|
|
316
|
+
* collection reads them. After `close()`, each operation rejects, and a second `close()` does nothing.
|
|
317
|
+
*/
|
|
318
|
+
async close() {
|
|
319
|
+
if (this.closed)
|
|
320
|
+
return;
|
|
321
|
+
this.closed = true;
|
|
322
|
+
this.observers.clear();
|
|
323
|
+
this.unlisten();
|
|
324
|
+
}
|
|
325
|
+
async getMetadata() {
|
|
326
|
+
const size = await this.size();
|
|
327
|
+
return { type: "custom", size, stats: { backend: "signaldb", collection: this.name } };
|
|
328
|
+
}
|
|
329
|
+
//
|
|
330
|
+
// ═══ Internal Helpers ═══
|
|
331
|
+
//
|
|
332
|
+
/** This method throws after `close()`. In an async method, the throw rejects the promise. */
|
|
333
|
+
checkOpen() {
|
|
334
|
+
if (this.closed)
|
|
335
|
+
throw new Error(`The SignalDbStorage of the collection ${this.name} is closed`);
|
|
336
|
+
}
|
|
337
|
+
/** This method waits for the load of the persistence. A close during the wait rejects. */
|
|
338
|
+
async whenReady() {
|
|
339
|
+
await this.ready;
|
|
340
|
+
this.checkOpen();
|
|
341
|
+
}
|
|
342
|
+
problem(what) {
|
|
343
|
+
return new Error(`The SignalDB collection ${this.name} has ${what}`);
|
|
344
|
+
}
|
|
345
|
+
/** The id and the value of an item that a cursor gave. An item without a string id throws. */
|
|
346
|
+
entryOf(raw, index) {
|
|
347
|
+
if (!isPlainObject(raw) || typeof raw["id"] !== "string")
|
|
348
|
+
throw this.problem(`an item without a string id (item ${index + 1})`);
|
|
349
|
+
return [raw["id"], this.codec.fromItem(raw)];
|
|
350
|
+
}
|
|
351
|
+
/** The current value of an id: a copy. */
|
|
352
|
+
current(id) {
|
|
353
|
+
const raw = this.collection.findOne({ id }, READ);
|
|
354
|
+
if (raw === undefined)
|
|
355
|
+
return { found: false };
|
|
356
|
+
return { found: true, value: this.entryOf(raw, 0)[1] };
|
|
357
|
+
}
|
|
358
|
+
/** The item of a value. The codec must give a plain object with the id. */
|
|
359
|
+
itemOf(id, value) {
|
|
360
|
+
const item = this.codec.toItem(id, value);
|
|
361
|
+
if (!isPlainObject(item) || item["id"] !== id) {
|
|
362
|
+
throw new TypeError(`The codec of the SignalDbStorage ${this.name} gave no item with the id ${JSON.stringify(id)}`);
|
|
363
|
+
}
|
|
364
|
+
return item;
|
|
365
|
+
}
|
|
366
|
+
/** A write of a value: a copy of the value, and its item. */
|
|
367
|
+
prepare(id, value) {
|
|
368
|
+
const copy = structuredClone(value);
|
|
369
|
+
return { id, value: copy, item: this.itemOf(id, copy) };
|
|
370
|
+
}
|
|
371
|
+
/** This method writes an item. An existing id keeps its place in the order of the entries. */
|
|
372
|
+
put(write, changes) {
|
|
373
|
+
this.collection.replaceOne({ id: write.id }, write.item, { upsert: true });
|
|
374
|
+
changes.push({ type: "set", id: write.id, value: write.value });
|
|
375
|
+
}
|
|
376
|
+
/** This method deletes the item of an id. True when the item existed. */
|
|
377
|
+
remove(id, changes) {
|
|
378
|
+
if (this.collection.removeOne({ id }) === 0)
|
|
379
|
+
return false;
|
|
380
|
+
changes.push({ type: "delete", id });
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* One write of the storage: `body` reads, tests and writes in one synchronous step. The events of
|
|
385
|
+
* the collection during the step are not changes of the app. The observers get the changes of the
|
|
386
|
+
* step after it, also when it fails after a change, with copies of the values.
|
|
387
|
+
*/
|
|
388
|
+
write(origin, body) {
|
|
389
|
+
const changes = [];
|
|
390
|
+
this.writing = true;
|
|
391
|
+
try {
|
|
392
|
+
return body(changes);
|
|
393
|
+
}
|
|
394
|
+
finally {
|
|
395
|
+
this.writing = false;
|
|
396
|
+
if (changes.length > 0 && this.observers.size > 0) {
|
|
397
|
+
this.observers.emit(changes.map((change) => withOrigin(change.type === "set" ? { ...change, value: structuredClone(change.value) } : change, origin)));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* One batch of the collection. `Collection.batch()` of SignalDB does not end its batch when the
|
|
403
|
+
* callback throws, so the error leaves the batch first, and the batch ends.
|
|
404
|
+
*/
|
|
405
|
+
batch(body) {
|
|
406
|
+
let failure;
|
|
407
|
+
this.collection.batch(() => {
|
|
408
|
+
try {
|
|
409
|
+
body();
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
failure = { error };
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
if (failure)
|
|
416
|
+
throw failure.error;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* The preconditions of a write. A storage without `versionOf` throws a `TypeError` before the
|
|
420
|
+
* write starts (roadmap CLI-16).
|
|
421
|
+
*/
|
|
422
|
+
preconditions(expected) {
|
|
423
|
+
if (expected.length === 0)
|
|
424
|
+
return expected;
|
|
425
|
+
if (!this.versionOf)
|
|
426
|
+
throw preconditionNotSupported("This SignalDbStorage");
|
|
427
|
+
for (const [id] of expected)
|
|
428
|
+
checkId(id);
|
|
429
|
+
return expected;
|
|
430
|
+
}
|
|
431
|
+
/** This method tests the expected versions in the step of the write. A conflict throws `VersionConflictError`. */
|
|
432
|
+
checkVersions(expected) {
|
|
433
|
+
if (expected.length === 0 || !this.versionOf)
|
|
434
|
+
return;
|
|
435
|
+
const conflicts = versionConflicts(expected, (id) => this.current(id), this.versionOf);
|
|
436
|
+
if (conflicts.length > 0)
|
|
437
|
+
throw new VersionConflictError(conflicts);
|
|
438
|
+
}
|
|
439
|
+
//
|
|
440
|
+
// ═══ The changes of the app ═══
|
|
441
|
+
//
|
|
442
|
+
onAdded = (item) => this.appChange("set", item);
|
|
443
|
+
onChanged = (item) => this.appChange("set", item);
|
|
444
|
+
onRemoved = (item) => this.appChange("delete", item);
|
|
445
|
+
/** This method starts to listen to the changes of the collection. */
|
|
446
|
+
listen() {
|
|
447
|
+
if (this.listening)
|
|
448
|
+
return;
|
|
449
|
+
this.listening = true;
|
|
450
|
+
this.collection.on("added", this.onAdded);
|
|
451
|
+
this.collection.on("changed", this.onChanged);
|
|
452
|
+
this.collection.on("removed", this.onRemoved);
|
|
453
|
+
}
|
|
454
|
+
/** This method stops to listen to the changes of the collection. */
|
|
455
|
+
unlisten() {
|
|
456
|
+
if (!this.listening)
|
|
457
|
+
return;
|
|
458
|
+
this.listening = false;
|
|
459
|
+
this.collection.off("added", this.onAdded);
|
|
460
|
+
this.collection.off("changed", this.onChanged);
|
|
461
|
+
this.collection.off("removed", this.onRemoved);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* This method gives a change that the app made on the collection to the observers. It does not
|
|
465
|
+
* throw, because it operates in the write of the app. An item that the storage cannot read goes to
|
|
466
|
+
* `onObserverError`.
|
|
467
|
+
*/
|
|
468
|
+
appChange(type, item) {
|
|
469
|
+
if (this.writing || this.closed || this.observers.size === 0)
|
|
470
|
+
return;
|
|
471
|
+
let change;
|
|
472
|
+
try {
|
|
473
|
+
const raw = structuredClone(item);
|
|
474
|
+
if (!isPlainObject(raw) || typeof raw["id"] !== "string")
|
|
475
|
+
throw this.problem("a changed item without a string id");
|
|
476
|
+
change = type === "set" ? { type, id: raw["id"], value: this.codec.fromItem(raw) } : { type, id: raw["id"] };
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
this.onObserverError(error);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
this.observers.emit([change]);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
2
3
|
"name": "@procedures/signaldb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"version": "0.24.1",
|
|
5
|
+
"description": "SignalDbStorage: a CollectionStorage over the SignalDB collection of an app, so the collection is the local copy of a HybridStorage and the app keeps its reactive queries.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"signaldb",
|
|
8
|
+
"collection-storage",
|
|
9
|
+
"local-first",
|
|
10
|
+
"offline",
|
|
11
|
+
"sync",
|
|
12
|
+
"reactive",
|
|
13
|
+
"procedures"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://mark1russell7.github.io/client/",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/mark1russell7/client/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/mark1russell7/client.git",
|
|
22
|
+
"directory": "packages/signaldb"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Mark Russell (https://github.com/mark1russell7)",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src",
|
|
38
|
+
"!src/**/*.test.ts",
|
|
39
|
+
"!src/**/*.test.tsx",
|
|
40
|
+
"!src/**/__tests__",
|
|
41
|
+
"CHANGELOG.md"
|
|
42
|
+
],
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"client": {
|
|
45
|
+
"browser": [
|
|
46
|
+
"."
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"provenance": true
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@procedures/client": "0.24.1",
|
|
58
|
+
"@procedures/collections": "0.24.1"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@signaldb/core": "^1.8.1"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"typescript": "^7.0.2",
|
|
65
|
+
"@types/node": "^22.0.0",
|
|
66
|
+
"@mark1russell7/cue": "^1.0.0",
|
|
67
|
+
"@procedures/store": "0.24.1",
|
|
68
|
+
"@signaldb/core": "^1.8.1",
|
|
69
|
+
"vitest": "^5.0.3"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsc -b",
|
|
73
|
+
"typecheck": "tsc --noEmit",
|
|
74
|
+
"clean": "rm -rf dist .tsbuildinfo",
|
|
75
|
+
"test": "vitest run",
|
|
76
|
+
"test:watch": "vitest"
|
|
77
|
+
}
|
|
6
78
|
}
|
package/src/codec.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The form of the values in the items of a SignalDB collection (roadmap CLI-23).
|
|
3
|
+
*
|
|
4
|
+
* A `CollectionStorage` keeps any JSON value under a string id. A SignalDB collection keeps objects,
|
|
5
|
+
* and each object has its key in the field `id`. A codec changes a value to an item and back.
|
|
6
|
+
*
|
|
7
|
+
* `documentCodec()` is the default codec:
|
|
8
|
+
* - A document is a plain object whose field `id` is its key. The item is the document itself, so
|
|
9
|
+
* the cursors and the selectors of the app read the document as it is.
|
|
10
|
+
* - Each other value goes into an envelope: `{ id, __value: value }`. This is a value without the
|
|
11
|
+
* field `id`, a value with another `id`, an array, `null`, a string, a number or a boolean.
|
|
12
|
+
*
|
|
13
|
+
* An app whose items have another form gives its own codec. For example, an app that keeps the
|
|
14
|
+
* Mongo key `_id` of a document in the field `id` of the item maps the two fields.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** An item of a SignalDB collection: an object with the key of the entry in its field `id`. */
|
|
18
|
+
export interface SignalDbItem {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly [field: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The change of a value to an item of the collection and back. `fromItem(toItem(id, value))` must
|
|
25
|
+
* give a value that is equal to `value`.
|
|
26
|
+
*/
|
|
27
|
+
export interface SignalDbItemCodec<T> {
|
|
28
|
+
/**
|
|
29
|
+
* This function gives the item of a value. The item must be a plain object, and its field `id`
|
|
30
|
+
* must be the id of the entry. The storage gives a copy of the value, so the item can keep it.
|
|
31
|
+
*/
|
|
32
|
+
toItem(id: string, value: T): SignalDbItem;
|
|
33
|
+
/**
|
|
34
|
+
* This function gives the value of an item. The item is a copy, so the value can keep it. An item
|
|
35
|
+
* that has no value of this codec throws an error.
|
|
36
|
+
*/
|
|
37
|
+
fromItem(item: SignalDbItem): T;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The field of an envelope that holds a value that is not a document. */
|
|
41
|
+
export const VALUE_FIELD = "__value";
|
|
42
|
+
|
|
43
|
+
/** True for a plain object: not `null`, not an array, and not an instance of a class. */
|
|
44
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
45
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
46
|
+
const prototype = Object.getPrototypeOf(value) as unknown;
|
|
47
|
+
return prototype === Object.prototype || prototype === null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The default codec. A plain object whose `id` is its key is the item itself. Each other value goes
|
|
52
|
+
* into an envelope with the field `valueField`.
|
|
53
|
+
*
|
|
54
|
+
* - A document does not have the field `valueField`: a value with that field goes into an envelope.
|
|
55
|
+
* Thus an item with the field is always an envelope, and each value comes back as it was.
|
|
56
|
+
* - The field `valueField` is reserved. An item of the app with that field reads as an envelope.
|
|
57
|
+
*
|
|
58
|
+
* @param valueField - The field of an envelope. The default is `__value`.
|
|
59
|
+
* @returns The codec
|
|
60
|
+
*/
|
|
61
|
+
export function documentCodec<T>(valueField: string = VALUE_FIELD): SignalDbItemCodec<T> {
|
|
62
|
+
if (typeof valueField !== "string" || valueField === "" || valueField === "id") {
|
|
63
|
+
throw new TypeError(`The field of an envelope must be a text that is not empty and not "id", not ${JSON.stringify(valueField)}`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
toItem(id: string, value: T): SignalDbItem {
|
|
67
|
+
if (isPlainObject(value) && value["id"] === id && !Object.hasOwn(value, valueField)) return value as unknown as SignalDbItem;
|
|
68
|
+
return { id, [valueField]: value };
|
|
69
|
+
},
|
|
70
|
+
fromItem(item: SignalDbItem): T {
|
|
71
|
+
return (Object.hasOwn(item, valueField) ? item[valueField] : item) as T;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|