@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.
@@ -1,15 +1,18 @@
1
1
  /**
2
- * Collection Storage Interface
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
- * Abstract storage layer for collections, enabling different backends:
5
- * - InMemoryStorage: Fast, volatile storage using Map
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
- * IMPORTANT: All methods return Promise to ensure client code is
11
- * transport-agnostic (cannot distinguish local from remote storage).
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]>): Promise<void>;
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":"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"}
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,16 @@
1
1
  /**
2
- * Collection Storage Interface
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
- * Abstract storage layer for collections, enabling different backends:
5
- * - InMemoryStorage: Fast, volatile storage using Map
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
- * IMPORTANT: All methods return Promise to ensure client code is
11
- * transport-agnostic (cannot distinguish local from remote storage).
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;;;;;;;;;;;GAWG;AAkOH;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAI,KAAiB;IACzD,OAAO,KAAK,CAAC;AACf,CAAC"}
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"}
@@ -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]>): Promise<void>;
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":"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"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"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"}
@@ -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
- this.data.set(id, this.copy(value));
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
- let deleted = 0;
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
- return deleted;
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
  //