@procedures/collections 0.7.0 → 0.8.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 +40 -0
- package/README.md +90 -37
- package/dist/.tsbuildinfo +1 -1
- package/dist/storage/index.d.ts +13 -8
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +13 -7
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/interface.d.ts +105 -13
- package/dist/storage/interface.d.ts.map +1 -1
- package/dist/storage/interface.js +10 -8
- package/dist/storage/interface.js.map +1 -1
- package/dist/storage/listeners.d.ts +39 -0
- package/dist/storage/listeners.d.ts.map +1 -0
- package/dist/storage/listeners.js +65 -0
- package/dist/storage/listeners.js.map +1 -0
- package/dist/storage/memory.d.ts +46 -6
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +81 -10
- package/dist/storage/memory.js.map +1 -1
- package/dist/testing/storage-contract.d.ts +13 -0
- package/dist/testing/storage-contract.d.ts.map +1 -1
- package/dist/testing/storage-contract.js +472 -0
- package/dist/testing/storage-contract.js.map +1 -1
- package/package.json +4 -1
- package/src/storage/index.ts +54 -8
- package/src/storage/interface.ts +117 -13
- package/src/storage/listeners.ts +72 -0
- package/src/storage/memory.ts +110 -11
- package/src/testing/storage-contract.ts +522 -2
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The interface of the collection storages. These storages implement it:
|
|
3
|
+
* - `InMemoryStorage` (this package): a fast storage in a `Map`, in memory only.
|
|
4
|
+
* - `ApiStorage` (`@procedures/store`): a remote collection through a `Client`.
|
|
5
|
+
* - `HybridStorage` (`@procedures/store`): a local copy that syncs with a remote, with a
|
|
6
|
+
* conflict policy and a queue of the local writes.
|
|
7
|
+
* - `MongoStorage` (`@procedures/mongo`).
|
|
3
8
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - ApiStorage: Remote persistence via universal client
|
|
7
|
-
* - HybridStorage: Local cache + remote sync with conflict resolution
|
|
8
|
-
* - SyncedStorage: Periodic background sync with offline support
|
|
9
|
+
* `@procedures/client` declares a copy of this interface with the same shape, so that the core
|
|
10
|
+
* does not depend on this package. `SqliteStorage` (`@procedures/sqlite`) implements that copy.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* Each method gives a `Promise`. Thus the code of a caller cannot tell a local storage from a
|
|
13
|
+
* remote storage.
|
|
12
14
|
*/
|
|
15
|
+
import type { StoragePatch, StorageQueryOptions, StorageQueryPolicy, StorageSelector } from "@procedures/client/query";
|
|
13
16
|
/**
|
|
14
17
|
* Storage backend interface for collections.
|
|
15
18
|
*
|
|
@@ -25,6 +28,11 @@
|
|
|
25
28
|
* - A delta of `getChangesSince` follows the order of the last change of each id.
|
|
26
29
|
* - After `close()`, each operation rejects with an error whose message says "closed". A second
|
|
27
30
|
* `close()` does nothing.
|
|
31
|
+
* - `query()` gives the result of `queryEntries()` for the entries of the storage.
|
|
32
|
+
* - `observe()` gives one batch for each write that changes the storage, before the write resolves.
|
|
33
|
+
* - `patch()` changes the item in one step: two patches at the same time keep each field.
|
|
34
|
+
*
|
|
35
|
+
* The optional methods are features: a caller tests for each one before it uses it.
|
|
28
36
|
*/
|
|
29
37
|
export interface CollectionStorage<T> {
|
|
30
38
|
/**
|
|
@@ -71,6 +79,35 @@ export interface CollectionStorage<T> {
|
|
|
71
79
|
* @returns Promise resolving to array of matching items
|
|
72
80
|
*/
|
|
73
81
|
find(predicate: (item: T) => boolean): Promise<T[]>;
|
|
82
|
+
/**
|
|
83
|
+
* The entries whose items match a selector, with an order, a skip and a limit. The selector is
|
|
84
|
+
* JSON, so a remote storage sends it to its server, and a database can run it itself. The result
|
|
85
|
+
* is the result of `queryEntries()` for the entries of the storage. A storage without this method
|
|
86
|
+
* does not run selectors: a caller uses `queryEntries()` on the entries.
|
|
87
|
+
*
|
|
88
|
+
* @param selector - The selector. `{}` matches each item.
|
|
89
|
+
* @param options - The order, the skip and the limit
|
|
90
|
+
* @param policy - The query policy of the caller: the procedure `find` gives the policy of the
|
|
91
|
+
* host. Without it, the storage uses its own policy. The default policy does not permit `$regex`.
|
|
92
|
+
* @returns Promise resolving to the [id, item] pairs, with copies of the items. A selector or
|
|
93
|
+
* options with a problem, or that the policy does not permit, reject with a `TypeError`.
|
|
94
|
+
*/
|
|
95
|
+
query?(selector: StorageSelector, options?: StorageQueryOptions, policy?: StorageQueryPolicy): Promise<Array<[string, T]>>;
|
|
96
|
+
/**
|
|
97
|
+
* This method starts to send the changes of the storage to an observer. Each write that changes
|
|
98
|
+
* the storage gives one batch, before the write resolves. A write that changes nothing gives no
|
|
99
|
+
* batch. A storage without this method does not tell its changes.
|
|
100
|
+
*
|
|
101
|
+
* - A set gives a `set` change with a copy of the value. A patch gives a `set` change with the new item.
|
|
102
|
+
* - A delete gives a `delete` change for each id that existed.
|
|
103
|
+
* - A clear of a storage with items gives one `clear` change.
|
|
104
|
+
* - Each change has the `origin` of the write options, or no origin.
|
|
105
|
+
*
|
|
106
|
+
* @param observer - The function that gets each batch. Its error does not change the write.
|
|
107
|
+
* @returns The function that stops the batches. A second use of it does nothing.
|
|
108
|
+
* @throws Error after `close()`
|
|
109
|
+
*/
|
|
110
|
+
observe?(observer: StorageObserver<T>): () => void;
|
|
74
111
|
/**
|
|
75
112
|
* Check if item exists.
|
|
76
113
|
*
|
|
@@ -89,36 +126,55 @@ export interface CollectionStorage<T> {
|
|
|
89
126
|
*
|
|
90
127
|
* @param id - Item identifier
|
|
91
128
|
* @param value - Item to store
|
|
129
|
+
* @param options - The origin of the write, for the observers
|
|
92
130
|
* @returns Promise resolving when operation completes
|
|
93
131
|
*/
|
|
94
|
-
set(id: string, value: T): Promise<void>;
|
|
132
|
+
set(id: string, value: T, options?: StorageWriteOptions): Promise<void>;
|
|
95
133
|
/**
|
|
96
134
|
* Delete item by ID.
|
|
97
135
|
*
|
|
98
136
|
* @param id - Item identifier
|
|
137
|
+
* @param options - The origin of the write, for the observers
|
|
99
138
|
* @returns Promise resolving to true if item was deleted, false if not found
|
|
100
139
|
*/
|
|
101
|
-
delete(id: string): Promise<boolean>;
|
|
140
|
+
delete(id: string, options?: StorageWriteOptions): Promise<boolean>;
|
|
102
141
|
/**
|
|
103
142
|
* Clear all items from collection.
|
|
104
143
|
*
|
|
144
|
+
* @param options - The origin of the write, for the observers
|
|
105
145
|
* @returns Promise resolving when operation completes
|
|
106
146
|
*/
|
|
107
|
-
clear(): Promise<void>;
|
|
147
|
+
clear(options?: StorageWriteOptions): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* This method changes one item in one step. The fields are a JSON Merge Patch (RFC 7386): each
|
|
150
|
+
* field replaces the field of the item, an object field merges into an object field, and `null`
|
|
151
|
+
* removes the field. Or they are the operations `{ $set, $unset }`: `$set` can set `null` and
|
|
152
|
+
* nested field paths. Two patches of one item at the same time keep the fields of each. A storage
|
|
153
|
+
* without this method cannot change an item in one step.
|
|
154
|
+
*
|
|
155
|
+
* @param id - Item identifier
|
|
156
|
+
* @param fields - The fields: an object of one form. Another value rejects with a `TypeError`.
|
|
157
|
+
* @param options - The origin of the write, for the observers
|
|
158
|
+
* @returns Promise resolving to a copy of the new item, or undefined when the id has no item. A
|
|
159
|
+
* missing id stays missing.
|
|
160
|
+
*/
|
|
161
|
+
patch?(id: string, fields: StoragePatch, options?: StorageWriteOptions): Promise<T | undefined>;
|
|
108
162
|
/**
|
|
109
163
|
* Set multiple items at once (more efficient than individual sets).
|
|
110
164
|
*
|
|
111
165
|
* @param items - Array of [id, value] tuples
|
|
166
|
+
* @param options - The origin of the write, for the observers
|
|
112
167
|
* @returns Promise resolving when operation completes
|
|
113
168
|
*/
|
|
114
|
-
setBatch(items: Array<[string, T]
|
|
169
|
+
setBatch(items: Array<[string, T]>, options?: StorageWriteOptions): Promise<void>;
|
|
115
170
|
/**
|
|
116
171
|
* Delete multiple items at once.
|
|
117
172
|
*
|
|
118
173
|
* @param ids - Array of item identifiers
|
|
174
|
+
* @param options - The origin of the write, for the observers
|
|
119
175
|
* @returns Promise resolving to number of items actually deleted
|
|
120
176
|
*/
|
|
121
|
-
deleteBatch(ids: string[]): Promise<number>;
|
|
177
|
+
deleteBatch(ids: string[], options?: StorageWriteOptions): Promise<number>;
|
|
122
178
|
/**
|
|
123
179
|
* Get multiple items by ID at once.
|
|
124
180
|
*
|
|
@@ -149,6 +205,42 @@ export interface CollectionStorage<T> {
|
|
|
149
205
|
*/
|
|
150
206
|
getMetadata?(): Promise<StorageMetadata>;
|
|
151
207
|
}
|
|
208
|
+
/** The options of a write. */
|
|
209
|
+
export interface StorageWriteOptions {
|
|
210
|
+
/**
|
|
211
|
+
* The tag of the writer. Each change that the write gives has it, so an observer knows its own
|
|
212
|
+
* writes: a sync does not send back the changes that it wrote itself.
|
|
213
|
+
*/
|
|
214
|
+
readonly origin?: string | undefined;
|
|
215
|
+
}
|
|
216
|
+
/** A change of `observe()`: an id got a value. */
|
|
217
|
+
export interface StorageSetChange<T> {
|
|
218
|
+
readonly type: "set";
|
|
219
|
+
readonly id: string;
|
|
220
|
+
/** A copy of the new item. */
|
|
221
|
+
readonly value: T;
|
|
222
|
+
/** The origin of the write, or undefined when the write had none. */
|
|
223
|
+
readonly origin?: string | undefined;
|
|
224
|
+
}
|
|
225
|
+
/** A change of `observe()`: an id lost its item. */
|
|
226
|
+
export interface StorageDeleteChange {
|
|
227
|
+
readonly type: "delete";
|
|
228
|
+
readonly id: string;
|
|
229
|
+
/** The origin of the write, or undefined when the write had none. */
|
|
230
|
+
readonly origin?: string | undefined;
|
|
231
|
+
}
|
|
232
|
+
/** A change of `observe()`: the storage lost each item. */
|
|
233
|
+
export interface StorageClearChange {
|
|
234
|
+
readonly type: "clear";
|
|
235
|
+
/** The origin of the write, or undefined when the write had none. */
|
|
236
|
+
readonly origin?: string | undefined;
|
|
237
|
+
}
|
|
238
|
+
/** One change of `observe()`. */
|
|
239
|
+
export type StorageChangeEvent<T> = StorageSetChange<T> | StorageDeleteChange | StorageClearChange;
|
|
240
|
+
/** The changes of one write, in the order of the write. The observers share it: do not change it. */
|
|
241
|
+
export type StorageChangeBatch<T> = readonly StorageChangeEvent<T>[];
|
|
242
|
+
/** The function that gets each batch of `observe()`. */
|
|
243
|
+
export type StorageObserver<T> = (changes: StorageChangeBatch<T>) => void;
|
|
152
244
|
/**
|
|
153
245
|
* The changes of a storage after a version (roadmap R28).
|
|
154
246
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEvH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAKlC;;;;;OAKG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAExC;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvB;;;;;OAKG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3C;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE7B;;;;;;;OAOG;IACH,eAAe,CAAC,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7D;;;;;OAKG;IACH,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3H;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAEnD;;;;;OAKG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElC;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAMxB;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExE;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpE;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAMhG;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElF;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE3E;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAMjD;;;;;OAKG;IACH,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;;OAKG;IACH,WAAW,CAAC,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED,8BAA8B;AAC9B,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,iCAAiC;AACjC,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEnG,qGAAqG;AACrG,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,SAAS,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;AAErE,wDAAwD;AACxD,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,EAAE,OAAO,CAAC;IACd,8CAA8C;IAC9C,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,IAAI,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAExD,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IAEb,6BAA6B;IAC7B,KAAK,CAAC,EAAE;QACN,iDAAiD;QACjD,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB,+CAA+C;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;QAElB,mDAAmD;QACnD,UAAU,CAAC,EAAE,MAAM,CAAC;QAEpB,iDAAiD;QACjD,WAAW,CAAC,EAAE,MAAM,CAAC;QAErB,mBAAmB;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEvE"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The interface of the collection storages. These storages implement it:
|
|
3
|
+
* - `InMemoryStorage` (this package): a fast storage in a `Map`, in memory only.
|
|
4
|
+
* - `ApiStorage` (`@procedures/store`): a remote collection through a `Client`.
|
|
5
|
+
* - `HybridStorage` (`@procedures/store`): a local copy that syncs with a remote, with a
|
|
6
|
+
* conflict policy and a queue of the local writes.
|
|
7
|
+
* - `MongoStorage` (`@procedures/mongo`).
|
|
3
8
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - ApiStorage: Remote persistence via universal client
|
|
7
|
-
* - HybridStorage: Local cache + remote sync with conflict resolution
|
|
8
|
-
* - SyncedStorage: Periodic background sync with offline support
|
|
9
|
+
* `@procedures/client` declares a copy of this interface with the same shape, so that the core
|
|
10
|
+
* does not depend on this package. `SqliteStorage` (`@procedures/sqlite`) implements that copy.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* Each method gives a `Promise`. Thus the code of a caller cannot tell a local storage from a
|
|
13
|
+
* remote storage.
|
|
12
14
|
*/
|
|
13
15
|
/**
|
|
14
16
|
* @deprecated All storage methods now return Promise.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAwUH;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAI,KAAiB;IACzD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A set of listeners for the events of a storage: the change batches of `observe()`, or the status
|
|
3
|
+
* of a sync. A storage package uses it, so each storage treats its listeners in the same way.
|
|
4
|
+
*/
|
|
5
|
+
/** The function that gets the error of a listener. */
|
|
6
|
+
export type ListenerErrorHandler = (error: unknown) => void;
|
|
7
|
+
/**
|
|
8
|
+
* The default handler of the error of a listener. It throws the error again in a microtask, as an
|
|
9
|
+
* `EventTarget` reports the error of a listener. Thus the error is not lost, and the write that
|
|
10
|
+
* sent the event does not fail.
|
|
11
|
+
*/
|
|
12
|
+
export declare const reportListenerError: ListenerErrorHandler;
|
|
13
|
+
/**
|
|
14
|
+
* The listeners of one kind of event.
|
|
15
|
+
*
|
|
16
|
+
* - `emit()` gives the event to each listener, in the order of `add()`.
|
|
17
|
+
* - A listener that `add()` gets during an `emit()` gets the next event, not this one.
|
|
18
|
+
* - The error of a listener goes to the error handler. The other listeners get the event.
|
|
19
|
+
* - The same function can be added two times. Each `add()` gives its own function that removes it.
|
|
20
|
+
*/
|
|
21
|
+
export declare class StorageListeners<E> {
|
|
22
|
+
private readonly listeners;
|
|
23
|
+
private readonly onError;
|
|
24
|
+
/** @param onError - The function that gets the error of a listener. The default is `reportListenerError`. */
|
|
25
|
+
constructor(onError?: ListenerErrorHandler);
|
|
26
|
+
/** The number of listeners. */
|
|
27
|
+
get size(): number;
|
|
28
|
+
/**
|
|
29
|
+
* This method adds a listener.
|
|
30
|
+
*
|
|
31
|
+
* @returns The function that removes the listener. A second use of it does nothing.
|
|
32
|
+
*/
|
|
33
|
+
add(listener: (event: E) => void): () => void;
|
|
34
|
+
/** This method gives the event to each listener. */
|
|
35
|
+
emit(event: E): void;
|
|
36
|
+
/** This method removes each listener. */
|
|
37
|
+
clear(): void;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=listeners.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listeners.d.ts","sourceRoot":"","sources":["../../src/storage/listeners.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,oBAIjC,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,gBAAgB,CAAC,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwD;IAClF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAE/C,6GAA6G;IAC7G,YAAY,OAAO,GAAE,oBAA0C,EAE9D;IAED,+BAA+B;IAC/B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAM5C;IAED,oDAAoD;IACpD,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAUnB;IAED,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAEZ;CACF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A set of listeners for the events of a storage: the change batches of `observe()`, or the status
|
|
3
|
+
* of a sync. A storage package uses it, so each storage treats its listeners in the same way.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The default handler of the error of a listener. It throws the error again in a microtask, as an
|
|
7
|
+
* `EventTarget` reports the error of a listener. Thus the error is not lost, and the write that
|
|
8
|
+
* sent the event does not fail.
|
|
9
|
+
*/
|
|
10
|
+
export const reportListenerError = (error) => {
|
|
11
|
+
queueMicrotask(() => {
|
|
12
|
+
throw error;
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The listeners of one kind of event.
|
|
17
|
+
*
|
|
18
|
+
* - `emit()` gives the event to each listener, in the order of `add()`.
|
|
19
|
+
* - A listener that `add()` gets during an `emit()` gets the next event, not this one.
|
|
20
|
+
* - The error of a listener goes to the error handler. The other listeners get the event.
|
|
21
|
+
* - The same function can be added two times. Each `add()` gives its own function that removes it.
|
|
22
|
+
*/
|
|
23
|
+
export class StorageListeners {
|
|
24
|
+
listeners = new Set();
|
|
25
|
+
onError;
|
|
26
|
+
/** @param onError - The function that gets the error of a listener. The default is `reportListenerError`. */
|
|
27
|
+
constructor(onError = reportListenerError) {
|
|
28
|
+
this.onError = onError;
|
|
29
|
+
}
|
|
30
|
+
/** The number of listeners. */
|
|
31
|
+
get size() {
|
|
32
|
+
return this.listeners.size;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* This method adds a listener.
|
|
36
|
+
*
|
|
37
|
+
* @returns The function that removes the listener. A second use of it does nothing.
|
|
38
|
+
*/
|
|
39
|
+
add(listener) {
|
|
40
|
+
const entry = { listener };
|
|
41
|
+
this.listeners.add(entry);
|
|
42
|
+
return () => {
|
|
43
|
+
this.listeners.delete(entry);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** This method gives the event to each listener. */
|
|
47
|
+
emit(event) {
|
|
48
|
+
for (const entry of [...this.listeners]) {
|
|
49
|
+
// A listener that a listener before it removed gets no event
|
|
50
|
+
if (!this.listeners.has(entry))
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
entry.listener(event);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.onError(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** This method removes each listener. */
|
|
61
|
+
clear() {
|
|
62
|
+
this.listeners.clear();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=listeners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listeners.js","sourceRoot":"","sources":["../../src/storage/listeners.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAyB,CAAC,KAAK,EAAE,EAAE;IACjE,cAAc,CAAC,GAAG,EAAE;QAClB,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,OAAO,gBAAgB;IACV,SAAS,GAAG,IAAI,GAAG,EAA6C,CAAC;IACjE,OAAO,CAAuB;IAE/C,6GAA6G;IAC7G,YAAY,OAAO,GAAyB,mBAAmB;QAC7D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,+BAA+B;IAC/B,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,QAA4B;QAC9B,MAAM,KAAK,GAAG,EAAE,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC;IACJ,CAAC;IAED,oDAAoD;IACpD,IAAI,CAAC,KAAQ;QACX,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACxC,6DAA6D;YAC7D,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACzC,IAAI,CAAC;gBACH,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;CACF"}
|
package/dist/storage/memory.d.ts
CHANGED
|
@@ -4,9 +4,23 @@
|
|
|
4
4
|
* Fast, volatile storage using JavaScript Map.
|
|
5
5
|
* All operations return Promises for interface consistency.
|
|
6
6
|
*/
|
|
7
|
-
import type { CollectionStorage, StorageChanges, StorageMetadata } from "./interface.js";
|
|
7
|
+
import type { CollectionStorage, StorageChanges, StorageMetadata, StorageObserver, StorageWriteOptions } from "./interface.js";
|
|
8
|
+
import { type ListenerErrorHandler } from "./listeners.js";
|
|
9
|
+
import { type StoragePatch, type StorageQueryOptions, type StorageQueryPolicy, type StorageSelector } from "@procedures/client/query";
|
|
8
10
|
/** Options of an in-memory storage. */
|
|
9
11
|
export interface InMemoryStorageOptions {
|
|
12
|
+
/**
|
|
13
|
+
* The function that gets the error of an observer of `observe()`. The default throws the error
|
|
14
|
+
* again in a microtask (`reportListenerError`). The write and the other observers do not fail.
|
|
15
|
+
*/
|
|
16
|
+
onObserverError?: ListenerErrorHandler | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The query policy of `query()` for a call without a policy: the operators and the limits. The
|
|
19
|
+
* procedure `find` gives the policy of its host. A storage that gets selectors of callers that it
|
|
20
|
+
* does not trust keeps the default.
|
|
21
|
+
* @default DEFAULT_QUERY_POLICY: no `$regex`
|
|
22
|
+
*/
|
|
23
|
+
queryPolicy?: StorageQueryPolicy | undefined;
|
|
10
24
|
/**
|
|
11
25
|
* The number of deletes that the change log keeps (roadmap R28). After more deletes, the log
|
|
12
26
|
* drops the oldest ones, and `getChangesSince` of an older version gives all items.
|
|
@@ -37,6 +51,8 @@ export interface InMemoryStorageOptions {
|
|
|
37
51
|
* - Copies: a change of a written or a read value does not change the stored item (the option
|
|
38
52
|
* `copies: false` turns this off)
|
|
39
53
|
* - `getIds()` gives the ids without a copy of the values
|
|
54
|
+
* - `query()` runs a selector, `observe()` tells each change with the origin of its write, and
|
|
55
|
+
* `patch()` merges fields into an item in one step
|
|
40
56
|
* - After `close()`, each operation rejects, and a second `close()` does nothing
|
|
41
57
|
*
|
|
42
58
|
* @example
|
|
@@ -59,6 +75,10 @@ export declare class InMemoryStorage<T> implements CollectionStorage<T> {
|
|
|
59
75
|
private readonly maxTombstones;
|
|
60
76
|
private readonly copy;
|
|
61
77
|
private closed;
|
|
78
|
+
/** The observers of `observe()` */
|
|
79
|
+
private readonly observers;
|
|
80
|
+
/** The query policy of a call without a policy */
|
|
81
|
+
private readonly queryPolicy;
|
|
62
82
|
constructor(options?: InMemoryStorageOptions);
|
|
63
83
|
get(id: string): Promise<T | undefined>;
|
|
64
84
|
getAll(): Promise<T[]>;
|
|
@@ -73,14 +93,32 @@ export declare class InMemoryStorage<T> implements CollectionStorage<T> {
|
|
|
73
93
|
getChangesSince(since?: string): Promise<StorageChanges<T>>;
|
|
74
94
|
/** The items for which the predicate gives true. The predicate gets a copy of each item. */
|
|
75
95
|
find(predicate: (item: T) => boolean): Promise<T[]>;
|
|
96
|
+
/**
|
|
97
|
+
* The entries whose items match the selector, with the order, the skip and the limit of
|
|
98
|
+
* `queryEntries()`, under the policy of the call or of the options. The result has copies of the
|
|
99
|
+
* items.
|
|
100
|
+
*/
|
|
101
|
+
query(selector: StorageSelector, options?: StorageQueryOptions, policy?: StorageQueryPolicy): Promise<Array<[string, T]>>;
|
|
102
|
+
/**
|
|
103
|
+
* This method starts to send the changes of each write to the observer, before the write resolves.
|
|
104
|
+
* The values of the changes are copies, unless the option `copies` is false. The observers share
|
|
105
|
+
* each batch.
|
|
106
|
+
*/
|
|
107
|
+
observe(observer: StorageObserver<T>): () => void;
|
|
76
108
|
has(id: string): Promise<boolean>;
|
|
77
109
|
size(): Promise<number>;
|
|
78
|
-
set(id: string, value: T): Promise<void>;
|
|
79
|
-
delete(id: string): Promise<boolean>;
|
|
80
|
-
clear(): Promise<void>;
|
|
110
|
+
set(id: string, value: T, options?: StorageWriteOptions): Promise<void>;
|
|
111
|
+
delete(id: string, options?: StorageWriteOptions): Promise<boolean>;
|
|
112
|
+
clear(options?: StorageWriteOptions): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* This method changes the item of the id in one step (a JSON Merge Patch, or the operations
|
|
115
|
+
* `$set` and `$unset`), and gives a copy of the new item. A missing id stays missing, and the
|
|
116
|
+
* result is undefined. A patch that cannot apply to the item changes nothing.
|
|
117
|
+
*/
|
|
118
|
+
patch(id: string, fields: StoragePatch, options?: StorageWriteOptions): Promise<T | undefined>;
|
|
81
119
|
/** This method sets several items. When one value has no copy, nothing changes. */
|
|
82
|
-
setBatch(items: Array<[string, T]
|
|
83
|
-
deleteBatch(ids: string[]): Promise<number>;
|
|
120
|
+
setBatch(items: Array<[string, T]>, options?: StorageWriteOptions): Promise<void>;
|
|
121
|
+
deleteBatch(ids: string[], options?: StorageWriteOptions): Promise<number>;
|
|
84
122
|
getBatch(ids: string[]): Promise<Map<string, T>>;
|
|
85
123
|
/**
|
|
86
124
|
* This method closes the storage and drops its items. After it, each operation rejects (and
|
|
@@ -121,6 +159,8 @@ export declare class InMemoryStorage<T> implements CollectionStorage<T> {
|
|
|
121
159
|
private checkOpen;
|
|
122
160
|
/** The entries with copies of the values. */
|
|
123
161
|
private copiedEntries;
|
|
162
|
+
/** The `set` change of a stored value: a copy of it for the observers. */
|
|
163
|
+
private setChange;
|
|
124
164
|
/** A set moves the id to the end of the order of the log: the order of the last change. */
|
|
125
165
|
private noteSet;
|
|
126
166
|
private noteDelete;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"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"}
|
package/dist/storage/memory.js
CHANGED
|
@@ -4,12 +4,18 @@
|
|
|
4
4
|
* Fast, volatile storage using JavaScript Map.
|
|
5
5
|
* All operations return Promises for interface consistency.
|
|
6
6
|
*/
|
|
7
|
+
import { StorageListeners } from "./listeners.js";
|
|
8
|
+
import { applyPatch, patchProblems, queryEntries, } from "@procedures/client/query";
|
|
7
9
|
/** A new epoch: a version of another storage does not match it. */
|
|
8
10
|
function newEpoch() {
|
|
9
11
|
return Math.random().toString(36).slice(2, 10);
|
|
10
12
|
}
|
|
11
13
|
const same = (value) => value;
|
|
12
14
|
const clone = (value) => structuredClone(value);
|
|
15
|
+
/** A change with the origin of the write, when the write has one. */
|
|
16
|
+
function withOrigin(change, options) {
|
|
17
|
+
return options?.origin === undefined ? change : { ...change, origin: options.origin };
|
|
18
|
+
}
|
|
13
19
|
/**
|
|
14
20
|
* In-memory storage backed by JavaScript Map.
|
|
15
21
|
*
|
|
@@ -23,6 +29,8 @@ const clone = (value) => structuredClone(value);
|
|
|
23
29
|
* - Copies: a change of a written or a read value does not change the stored item (the option
|
|
24
30
|
* `copies: false` turns this off)
|
|
25
31
|
* - `getIds()` gives the ids without a copy of the values
|
|
32
|
+
* - `query()` runs a selector, `observe()` tells each change with the origin of its write, and
|
|
33
|
+
* `patch()` merges fields into an item in one step
|
|
26
34
|
* - After `close()`, each operation rejects, and a second `close()` does nothing
|
|
27
35
|
*
|
|
28
36
|
* @example
|
|
@@ -47,9 +55,15 @@ export class InMemoryStorage {
|
|
|
47
55
|
maxTombstones;
|
|
48
56
|
copy;
|
|
49
57
|
closed = false;
|
|
58
|
+
/** The observers of `observe()` */
|
|
59
|
+
observers;
|
|
60
|
+
/** The query policy of a call without a policy */
|
|
61
|
+
queryPolicy;
|
|
50
62
|
constructor(options = {}) {
|
|
51
63
|
this.maxTombstones = Math.max(0, options.maxTombstones ?? 10_000);
|
|
52
64
|
this.copy = options.copies === false ? same : clone;
|
|
65
|
+
this.observers = new StorageListeners(options.onObserverError);
|
|
66
|
+
this.queryPolicy = options.queryPolicy;
|
|
53
67
|
}
|
|
54
68
|
//
|
|
55
69
|
// ═══ Read Operations ═══
|
|
@@ -107,6 +121,24 @@ export class InMemoryStorage {
|
|
|
107
121
|
}
|
|
108
122
|
return results;
|
|
109
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The entries whose items match the selector, with the order, the skip and the limit of
|
|
126
|
+
* `queryEntries()`, under the policy of the call or of the options. The result has copies of the
|
|
127
|
+
* items.
|
|
128
|
+
*/
|
|
129
|
+
async query(selector, options, policy) {
|
|
130
|
+
this.checkOpen();
|
|
131
|
+
return queryEntries(this.data, selector, options, policy ?? this.queryPolicy).map(([id, value]) => [id, this.copy(value)]);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* This method starts to send the changes of each write to the observer, before the write resolves.
|
|
135
|
+
* The values of the changes are copies, unless the option `copies` is false. The observers share
|
|
136
|
+
* each batch.
|
|
137
|
+
*/
|
|
138
|
+
observe(observer) {
|
|
139
|
+
this.checkOpen();
|
|
140
|
+
return this.observers.add(observer);
|
|
141
|
+
}
|
|
110
142
|
async has(id) {
|
|
111
143
|
this.checkOpen();
|
|
112
144
|
return this.data.has(id);
|
|
@@ -118,46 +150,80 @@ export class InMemoryStorage {
|
|
|
118
150
|
//
|
|
119
151
|
// ═══ Write Operations ═══
|
|
120
152
|
//
|
|
121
|
-
async set(id, value) {
|
|
153
|
+
async set(id, value, options) {
|
|
122
154
|
this.checkOpen();
|
|
123
|
-
|
|
155
|
+
const stored = this.copy(value);
|
|
156
|
+
this.data.set(id, stored);
|
|
124
157
|
this.noteSet(id);
|
|
158
|
+
if (this.observers.size > 0)
|
|
159
|
+
this.observers.emit([this.setChange(id, stored, options)]);
|
|
125
160
|
}
|
|
126
|
-
async delete(id) {
|
|
161
|
+
async delete(id, options) {
|
|
127
162
|
this.checkOpen();
|
|
128
163
|
const existed = this.data.delete(id);
|
|
129
|
-
if (existed)
|
|
164
|
+
if (existed) {
|
|
130
165
|
this.noteDelete(id);
|
|
166
|
+
if (this.observers.size > 0)
|
|
167
|
+
this.observers.emit([withOrigin({ type: "delete", id }, options)]);
|
|
168
|
+
}
|
|
131
169
|
return existed;
|
|
132
170
|
}
|
|
133
|
-
async clear() {
|
|
171
|
+
async clear(options) {
|
|
134
172
|
this.checkOpen();
|
|
173
|
+
const had = this.data.size > 0;
|
|
135
174
|
for (const id of this.data.keys())
|
|
136
175
|
this.noteDelete(id);
|
|
137
176
|
this.data.clear();
|
|
177
|
+
if (had && this.observers.size > 0)
|
|
178
|
+
this.observers.emit([withOrigin({ type: "clear" }, options)]);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* This method changes the item of the id in one step (a JSON Merge Patch, or the operations
|
|
182
|
+
* `$set` and `$unset`), and gives a copy of the new item. A missing id stays missing, and the
|
|
183
|
+
* result is undefined. A patch that cannot apply to the item changes nothing.
|
|
184
|
+
*/
|
|
185
|
+
async patch(id, fields, options) {
|
|
186
|
+
this.checkOpen();
|
|
187
|
+
const [problem] = patchProblems(fields);
|
|
188
|
+
if (problem !== undefined)
|
|
189
|
+
throw new TypeError(`The patch of ${id} is not correct: ${problem}`);
|
|
190
|
+
if (!this.data.has(id))
|
|
191
|
+
return undefined;
|
|
192
|
+
const stored = this.copy(applyPatch(this.data.get(id), fields));
|
|
193
|
+
this.data.set(id, stored);
|
|
194
|
+
this.noteSet(id);
|
|
195
|
+
if (this.observers.size > 0)
|
|
196
|
+
this.observers.emit([this.setChange(id, stored, options)]);
|
|
197
|
+
return this.copy(stored);
|
|
138
198
|
}
|
|
139
199
|
//
|
|
140
200
|
// ═══ Bulk Operations ═══
|
|
141
201
|
//
|
|
142
202
|
/** This method sets several items. When one value has no copy, nothing changes. */
|
|
143
|
-
async setBatch(items) {
|
|
203
|
+
async setBatch(items, options) {
|
|
144
204
|
this.checkOpen();
|
|
145
205
|
const copies = items.map(([id, value]) => [id, this.copy(value)]);
|
|
146
206
|
for (const [id, value] of copies) {
|
|
147
207
|
this.data.set(id, value);
|
|
148
208
|
this.noteSet(id);
|
|
149
209
|
}
|
|
210
|
+
if (copies.length > 0 && this.observers.size > 0) {
|
|
211
|
+
this.observers.emit(copies.map(([id, value]) => this.setChange(id, value, options)));
|
|
212
|
+
}
|
|
150
213
|
}
|
|
151
|
-
async deleteBatch(ids) {
|
|
214
|
+
async deleteBatch(ids, options) {
|
|
152
215
|
this.checkOpen();
|
|
153
|
-
|
|
216
|
+
const deleted = [];
|
|
154
217
|
for (const id of ids) {
|
|
155
218
|
if (this.data.delete(id)) {
|
|
156
219
|
this.noteDelete(id);
|
|
157
|
-
deleted
|
|
220
|
+
deleted.push(id);
|
|
158
221
|
}
|
|
159
222
|
}
|
|
160
|
-
|
|
223
|
+
if (deleted.length > 0 && this.observers.size > 0) {
|
|
224
|
+
this.observers.emit(deleted.map((id) => withOrigin({ type: "delete", id }, options)));
|
|
225
|
+
}
|
|
226
|
+
return deleted.length;
|
|
161
227
|
}
|
|
162
228
|
async getBatch(ids) {
|
|
163
229
|
this.checkOpen();
|
|
@@ -184,6 +250,7 @@ export class InMemoryStorage {
|
|
|
184
250
|
this.data.clear();
|
|
185
251
|
this.changedAt.clear();
|
|
186
252
|
this.deletedAt.clear();
|
|
253
|
+
this.observers.clear();
|
|
187
254
|
}
|
|
188
255
|
async getMetadata() {
|
|
189
256
|
this.checkOpen();
|
|
@@ -257,6 +324,10 @@ export class InMemoryStorage {
|
|
|
257
324
|
copiedEntries() {
|
|
258
325
|
return Array.from(this.data, ([id, value]) => [id, this.copy(value)]);
|
|
259
326
|
}
|
|
327
|
+
/** The `set` change of a stored value: a copy of it for the observers. */
|
|
328
|
+
setChange(id, stored, options) {
|
|
329
|
+
return withOrigin({ type: "set", id, value: this.copy(stored) }, options);
|
|
330
|
+
}
|
|
260
331
|
//
|
|
261
332
|
// ═══ The change log ═══
|
|
262
333
|
//
|