@procedures/store 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +109 -2
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/indexeddb/idb.d.ts +100 -0
  5. package/dist/indexeddb/idb.d.ts.map +1 -0
  6. package/dist/indexeddb/idb.js +137 -0
  7. package/dist/indexeddb/idb.js.map +1 -0
  8. package/dist/indexeddb/program-store.d.ts +42 -0
  9. package/dist/indexeddb/program-store.d.ts.map +1 -0
  10. package/dist/indexeddb/program-store.js +71 -0
  11. package/dist/indexeddb/program-store.js.map +1 -0
  12. package/dist/indexeddb/queue-store.d.ts +38 -0
  13. package/dist/indexeddb/queue-store.d.ts.map +1 -0
  14. package/dist/indexeddb/queue-store.js +73 -0
  15. package/dist/indexeddb/queue-store.js.map +1 -0
  16. package/dist/indexeddb.d.ts +19 -0
  17. package/dist/indexeddb.d.ts.map +1 -0
  18. package/dist/indexeddb.js +17 -0
  19. package/dist/indexeddb.js.map +1 -0
  20. package/dist/program-store/file.d.ts +5 -0
  21. package/dist/program-store/file.d.ts.map +1 -1
  22. package/dist/program-store/file.js +5 -2
  23. package/dist/program-store/file.js.map +1 -1
  24. package/dist/program-store/procedures.d.ts.map +1 -1
  25. package/dist/program-store/procedures.js +5 -2
  26. package/dist/program-store/procedures.js.map +1 -1
  27. package/dist/storage/api.d.ts +18 -7
  28. package/dist/storage/api.d.ts.map +1 -1
  29. package/dist/storage/api.js +34 -3
  30. package/dist/storage/api.js.map +1 -1
  31. package/dist/storage/factory.d.ts +13 -2
  32. package/dist/storage/factory.d.ts.map +1 -1
  33. package/dist/storage/factory.js +12 -4
  34. package/dist/storage/factory.js.map +1 -1
  35. package/dist/storage/file-queue.d.ts +32 -0
  36. package/dist/storage/file-queue.d.ts.map +1 -0
  37. package/dist/storage/file-queue.js +101 -0
  38. package/dist/storage/file-queue.js.map +1 -0
  39. package/dist/storage/hybrid.d.ts +355 -42
  40. package/dist/storage/hybrid.d.ts.map +1 -1
  41. package/dist/storage/hybrid.js +1093 -250
  42. package/dist/storage/hybrid.js.map +1 -1
  43. package/dist/storage/index.d.ts +4 -2
  44. package/dist/storage/index.d.ts.map +1 -1
  45. package/dist/storage/index.js +5 -1
  46. package/dist/storage/index.js.map +1 -1
  47. package/dist/storage/queue.d.ts +67 -0
  48. package/dist/storage/queue.d.ts.map +1 -0
  49. package/dist/storage/queue.js +85 -0
  50. package/dist/storage/queue.js.map +1 -0
  51. package/dist/storage/synced-registry.d.ts +18 -2
  52. package/dist/storage/synced-registry.d.ts.map +1 -1
  53. package/dist/storage/synced-registry.js +37 -3
  54. package/dist/storage/synced-registry.js.map +1 -1
  55. package/dist/storage/types.d.ts +5 -2
  56. package/dist/storage/types.d.ts.map +1 -1
  57. package/package.json +16 -6
  58. package/src/indexeddb/idb.ts +190 -0
  59. package/src/indexeddb/program-store.ts +80 -0
  60. package/src/indexeddb/queue-store.ts +80 -0
  61. package/src/indexeddb.ts +19 -0
  62. package/src/program-store/file.ts +5 -2
  63. package/src/program-store/procedures.ts +5 -2
  64. package/src/storage/api.ts +50 -10
  65. package/src/storage/factory.ts +21 -4
  66. package/src/storage/file-queue.ts +103 -0
  67. package/src/storage/hybrid.ts +1142 -274
  68. package/src/storage/index.ts +15 -2
  69. package/src/storage/queue.ts +128 -0
  70. package/src/storage/synced-registry.ts +49 -4
  71. package/src/storage/types.ts +6 -2
@@ -1,48 +1,76 @@
1
1
  /**
2
2
  * Hybrid Storage Implementation
3
3
  *
4
- * Combines local cache (InMemoryStorage) with remote persistence (ApiStorage).
5
- * Provides fast local reads with remote backup and sync.
4
+ * A local copy (an `InMemoryStorage`, or the `local` storage that the application gives) in
5
+ * front of a remote `CollectionStorage` (often an `ApiStorage`). The storage has four modes, and
6
+ * `setMode()` changes the mode while the storage operates (roadmap R28):
6
7
  *
7
- * Features:
8
- * - Local cache for fast reads, with a remote read on a miss
9
- * - Write-through or write-back strategies
10
- * - Automatic sync on initialization
11
- * - Conflict resolution
12
- * - Offline operation queue
8
+ * | Mode | Reads | Writes | Remote calls |
9
+ * |---|---|---|---|
10
+ * | `local-only` | the local copy | the local copy and the queue | none |
11
+ * | `write-through` | the local copy, the remote on a miss | the local copy, then the remote | yes |
12
+ * | `write-back` | the local copy, the remote on a miss | the local copy and the queue, a timer sends it | yes |
13
+ * | `remote-only` | the remote | the remote | yes, and no local copy |
14
+ *
15
+ * - The queue is durable through the `queueStore` that the application gives.
16
+ * - A sync asks for the changes since a version when the remote has `getChangesSince`.
17
+ * - With `versionOf`, a conflict is a change on both sides, and `conflictResolution` decides.
18
+ * - `pullInterval` keeps the local copy fresh.
13
19
  */
14
20
  import type { CollectionStorage, StorageMetadata } from "@procedures/collections";
21
+ import { type Duration } from "@procedures/client";
22
+ import { type QueueStore } from "./queue.js";
15
23
  /**
16
24
  * Conflict resolution strategy.
17
25
  */
18
26
  export type ConflictResolution = "local" | "remote" | "merge" | "error";
19
27
  /**
20
28
  * Write strategy for hybrid storage.
29
+ * @deprecated Use `HybridMode`: the strategies are two of its modes.
21
30
  */
22
31
  export type WriteStrategy = "write-through" | "write-back";
32
+ /**
33
+ * The mode of a `HybridStorage` (roadmap R28).
34
+ *
35
+ * - `local-only`: the storage makes no remote call. Each write goes to the local copy and to the
36
+ * queue, so a later switch to a remote mode sends it.
37
+ * - `write-through`: each write goes to the local copy, then to the remote.
38
+ * - `write-back`: each write goes to the local copy and to the queue. A timer sends the queue.
39
+ * - `remote-only`: each read and each write goes to the remote. The storage keeps no local copy.
40
+ */
41
+ export type HybridMode = "local-only" | "write-through" | "write-back" | "remote-only";
23
42
  /**
24
43
  * Hybrid storage configuration options.
25
44
  */
26
45
  export interface HybridStorageOptions {
27
46
  /**
28
- * Write strategy
29
- * - write-through: Write to both local and remote immediately
30
- * - write-back: Write to local immediately, sync to remote later
47
+ * The first mode. `setMode()` changes it later.
31
48
  * @default "write-through"
32
49
  */
50
+ mode?: HybridMode;
51
+ /**
52
+ * The first mode, with its old name. `mode` comes first when both are given.
53
+ * @deprecated Use `mode`.
54
+ */
33
55
  writeStrategy?: WriteStrategy;
34
56
  /**
35
57
  * Conflict resolution strategy
36
58
  * - local: Local changes win
37
59
  * - remote: Remote changes win
38
60
  * - merge: Custom merge function (requires mergeFn)
39
- * - error: Throw error on conflict
61
+ * - error: The sync throws `ConflictError` with the ids, and changes nothing for them
62
+ *
63
+ * With `versionOf`, a conflict is a queued local write of an id whose remote version changed
64
+ * after the last version that the storage saw. A queued write of an id without a known remote
65
+ * version wins. Without `versionOf`, a conflict is a local value that differs from the remote
66
+ * value, with no queued write: a queued write always wins.
40
67
  * @default "remote"
41
68
  */
42
69
  conflictResolution?: ConflictResolution;
43
70
  /**
44
71
  * Custom merge function for conflict resolution.
45
- * Only used when conflictResolution is "merge".
72
+ * Only used when conflictResolution is "merge". A conflict where one side deleted the item
73
+ * keeps the local side.
46
74
  *
47
75
  * @param local - Local version
48
76
  * @param remote - Remote version
@@ -55,28 +83,126 @@ export interface HybridStorageOptions {
55
83
  */
56
84
  keyOf?: (item: any) => string | undefined;
57
85
  /**
58
- * Automatic sync interval in milliseconds (for write-back mode).
59
- * Set to 0 to disable automatic sync.
60
- * @default 5000 (5 seconds)
86
+ * The version of an item, for example its ETag or a revision field (roadmap R28). The sync
87
+ * compares the versions, not the JSON of the values. A remote change of an id with a queued
88
+ * local write is a conflict.
61
89
  */
62
- syncInterval?: number;
90
+ versionOf?: (item: any) => string | undefined;
63
91
  /**
64
- * Initialize by syncing from remote on construction.
92
+ * Automatic sync interval (for write-back mode), for example `seconds(5)`.
93
+ * Set to 0 to disable automatic sync. In write-through mode, it is also the delay before the
94
+ * storage sends the queue after a failure (5 seconds when it is 0).
95
+ * @default 5 seconds
96
+ */
97
+ syncInterval?: Duration;
98
+ /**
99
+ * The interval of the periodic pull, for example `minutes(1)` (roadmap R28). A pull reads the
100
+ * remote changes into the local copy in the modes `write-through` and `write-back`.
101
+ * @default no periodic pull
102
+ */
103
+ pullInterval?: Duration;
104
+ /**
105
+ * Initialize by syncing from remote on construction: send the loaded queue, then read the
106
+ * remote. The mode `local-only` makes no remote call.
65
107
  * @default true
66
108
  */
67
109
  syncOnInit?: boolean;
68
110
  /**
69
- * Maximum offline operations to queue.
111
+ * Maximum offline operations to queue. A full queue refuses the next write.
70
112
  * @default 1000
71
113
  */
72
114
  maxOfflineOps?: number;
73
115
  /**
74
- * Enable offline operation queue.
75
- * When remote is unavailable, operations are queued and retried.
116
+ * The queue of the write-through mode.
117
+ * In write-through mode, a write that the remote cannot take stays in the local cache and the
118
+ * queue, and the write throws `PendingWriteError`. A timer sends the queue later.
119
+ * Without the queue, the write is undone in the local cache and the write throws.
120
+ * The modes `write-back` and `local-only` always use the queue.
76
121
  * @default true
77
122
  */
78
123
  offlineQueue?: boolean;
124
+ /**
125
+ * The store that keeps the queue (roadmap R28). A new storage with the same store loads the
126
+ * queue again and sends it. `FileQueueStore` of `@procedures/store/file-queue` keeps it in a
127
+ * file.
128
+ * @default a new `MemoryQueueStore`
129
+ */
130
+ queueStore?: QueueStore;
131
+ /**
132
+ * True when an error of the remote means "not available now", so the write can wait in the
133
+ * queue. Another error (a refusal of the remote) undoes the write. The default: an error with
134
+ * `retryable: true`, or an error without a `code`.
135
+ */
136
+ isUnavailable?: (error: unknown) => boolean;
137
+ /**
138
+ * The storage of the local copy (roadmap R45). A durable storage, for example `SqliteStorage`
139
+ * of `@procedures/sqlite`, keeps the local copy after a restart. Then a new storage in
140
+ * `local-only` mode reads the items of the last run. Give it with a durable `queueStore`.
141
+ * `close()` closes it.
142
+ * @default a new `InMemoryStorage`
143
+ */
144
+ local?: LocalCopyStorage<any>;
79
145
  }
146
+ /**
147
+ * The remote did not take a write of the write-through mode. The write is in the local cache and
148
+ * in the queue, and the storage sends it later (roadmap R2). `cause` is the error of the remote.
149
+ */
150
+ export declare class PendingWriteError extends Error {
151
+ readonly name = "PendingWriteError";
152
+ readonly code = "WRITE_PENDING";
153
+ readonly pending = true;
154
+ constructor(operation: string, cause: unknown);
155
+ }
156
+ /** The queue of operations is full, so the storage refuses the write (roadmap R2). */
157
+ export declare class QueueFullError extends Error {
158
+ readonly name = "QueueFullError";
159
+ readonly code = "QUEUE_FULL";
160
+ constructor(max: number);
161
+ }
162
+ /** The remote gave items without a key, so a sync cannot know which local items to keep. */
163
+ export declare class MissingKeysError extends Error {
164
+ readonly name = "MissingKeysError";
165
+ readonly code = "MISSING_KEYS";
166
+ constructor(count: number);
167
+ }
168
+ /**
169
+ * The queue store failed (roadmap R28). A write that the store did not keep is undone and not
170
+ * acknowledged. A storage whose store cannot load refuses each operation.
171
+ */
172
+ export declare class QueueStoreError extends Error {
173
+ readonly name = "QueueStoreError";
174
+ readonly code = "QUEUE_STORE";
175
+ constructor(action: "load" | "write", cause: unknown);
176
+ }
177
+ /**
178
+ * The sync found conflicts and `conflictResolution` is "error" (roadmap R28). The local values and
179
+ * their queued writes stay. `ids` are the ids of the conflicts.
180
+ */
181
+ export declare class ConflictError extends Error {
182
+ readonly ids: string[];
183
+ readonly name = "ConflictError";
184
+ readonly code = "SYNC_CONFLICT";
185
+ constructor(ids: string[]);
186
+ }
187
+ /**
188
+ * `setMode()` could not change the mode (roadmap R28). The mode did not change. `cause` is the
189
+ * error of the flush or of the sync.
190
+ */
191
+ export declare class ModeSwitchError extends Error {
192
+ readonly from: HybridMode;
193
+ readonly to: HybridMode;
194
+ readonly name = "ModeSwitchError";
195
+ readonly code = "MODE_SWITCH_FAILED";
196
+ constructor(from: HybridMode, to: HybridMode, cause: unknown);
197
+ }
198
+ /**
199
+ * A storage for the local copy of a `HybridStorage` (roadmap R45): a collection storage that gives
200
+ * its entries. A durable one, for example `SqliteStorage` of `@procedures/sqlite`, keeps the local
201
+ * copy after a restart.
202
+ */
203
+ export type LocalCopyStorage<T> = CollectionStorage<T> & {
204
+ getEntries(): Promise<Array<[string, T]>>;
205
+ };
80
206
  /**
81
207
  * Hybrid storage combining local cache with remote persistence.
82
208
  *
@@ -88,9 +214,9 @@ export interface HybridStorageOptions {
88
214
  *
89
215
  * const remote = new ApiStorage(client, { collection: "users" });
90
216
  * const hybrid = new HybridStorage(remote, {
91
- * writeStrategy: "write-through",
217
+ * mode: "write-through",
92
218
  * conflictResolution: "remote",
93
- * syncInterval: 5000
219
+ * syncInterval: seconds(5)
94
220
  * });
95
221
  *
96
222
  * // Fast local reads
@@ -98,44 +224,136 @@ export interface HybridStorageOptions {
98
224
  *
99
225
  * // Writes go to both local and remote
100
226
  * await hybrid.set("123", { name: "John" });
227
+ *
228
+ * // Offline: no remote call. The writes wait in the queue.
229
+ * await hybrid.setMode("local-only");
230
+ * // Online again: the switch sends the queue, then syncs the local copy
231
+ * await hybrid.setMode("write-through");
101
232
  * ```
102
233
  */
103
234
  export declare class HybridStorage<T> implements CollectionStorage<T> {
104
- private local;
105
- private remote;
235
+ private readonly local;
236
+ private readonly remote;
237
+ private readonly queueStore;
106
238
  private options;
239
+ private mode;
107
240
  private offlineOps;
241
+ /** The operations in `offlineOps` that the queue store does not have yet */
242
+ private readonly unpersisted;
243
+ /** True when the queue store can differ from `offlineOps`: a failed write, or sent operations */
244
+ private storeDirty;
245
+ /** The calls of the queue store, one at a time */
246
+ private storeChain;
247
+ /** The push timer of the write-back mode */
108
248
  private syncTimer;
249
+ /** The periodic pull */
250
+ private pullTimer;
251
+ /** True while pushQueue sends the queue: the first operation can be on its way */
252
+ private pushing;
253
+ /** The timer that sends the queue of the write-through mode after a failure */
254
+ private drainTimer;
255
+ /** The load of the queue. The writes wait for it. */
256
+ private readonly loaded;
257
+ /** The load and the first sync. The reads wait for it, so a read never sees an empty cache by mistake. */
258
+ private readonly ready;
259
+ /** The error of the load (of the queue store, or of the local copy): the storage refuses each operation */
260
+ private loadError;
261
+ /** A mode switch that runs. The operations wait for it. */
262
+ private gate;
263
+ /** The operations that run, and the switches that wait for them to end */
264
+ private active;
265
+ private idleWaiters;
109
266
  private isOnline;
267
+ private closed;
110
268
  /** A counter of local writes for each id, so a sync does not restore an older remote value */
111
269
  private readonly writeVersions;
112
270
  private clearVersion;
113
271
  /** The running sync: syncs run one at a time */
114
272
  private syncChain;
273
+ /** The remote version of the last delta pull */
274
+ private cursor;
275
+ /** True when the remote has `getChangesSince` but its server does not know `changes` */
276
+ private changesUnsupported;
277
+ /** With `versionOf`: the last known remote version of each id. `null`: the remote has no item. */
278
+ private readonly bases;
115
279
  private stats;
116
280
  constructor(remote: CollectionStorage<T>, options?: HybridStorageOptions);
281
+ /** The mode of the storage now. */
282
+ getMode(): HybridMode;
283
+ /**
284
+ * This method changes the mode while the storage operates (roadmap R28).
285
+ *
286
+ * - The switch waits for the operations that run. The operations that start later wait for
287
+ * the switch. Switches and syncs run one at a time.
288
+ * - A switch to a remote mode sends the queue first. A switch to `local-only` makes no remote
289
+ * call: the queue stays until the next remote mode.
290
+ * - A switch to `remote-only` drops the local copy, after the queue reached the remote.
291
+ * - A switch from `local-only` or `remote-only` to a mode with a local copy syncs the local
292
+ * copy. The reads wait for the sync.
293
+ * - When the flush or the sync fails, the mode does not change, and the switch throws
294
+ * `ModeSwitchError`.
295
+ */
296
+ setMode(mode: HybridMode): Promise<void>;
297
+ private switchTo;
298
+ /** This method drops the local copy. A read in flight does not fill it again. */
299
+ private dropLocal;
300
+ /** This method makes the local copy from the queue only, before a full sync. */
301
+ private rebuildLocal;
302
+ /** This method starts and stops the timers for the mode. */
303
+ private updateTimers;
117
304
  /**
118
305
  * The local item, or else the remote item (which the cache then keeps). When the remote
119
- * cannot answer, the storage is offline and the result is undefined.
306
+ * cannot answer, the storage is offline and the result is undefined. In `local-only` mode, a
307
+ * miss is undefined. In `remote-only` mode, the remote answers, and its error passes through.
120
308
  */
121
309
  get(id: string): Promise<T | undefined>;
122
310
  getAll(): Promise<T[]>;
311
+ /** Every item with its id. */
312
+ getEntries(): Promise<Array<[string, T]>>;
123
313
  find(predicate: (item: T) => boolean): Promise<T[]>;
124
314
  has(id: string): Promise<boolean>;
125
315
  size(): Promise<number>;
316
+ getBatch(ids: string[]): Promise<Map<string, T>>;
317
+ /** A read of the remote-only mode. A queue that a restart loaded reaches the remote first. */
318
+ private remoteRead;
126
319
  set(id: string, value: T): Promise<void>;
127
320
  delete(id: string): Promise<boolean>;
128
321
  clear(): Promise<void>;
129
322
  setBatch(items: Array<[string, T]>): Promise<void>;
130
323
  deleteBatch(ids: string[]): Promise<number>;
131
- getBatch(ids: string[]): Promise<Map<string, T>>;
132
324
  /**
133
- * Sync all data from remote to local.
134
- * Handles conflicts according to conflict resolution strategy.
325
+ * One write, in the mode of the storage. The result is the result of the local copy, or of the
326
+ * remote in `remote-only` mode.
327
+ *
328
+ * - `remote-only`: the remote only. Its error passes through.
329
+ * - `write-back` and `local-only`: the local copy and the queue. A full queue refuses the write
330
+ * before the local change. The queue store keeps the operation before the write ends.
331
+ * - `write-through`, the remote refuses the write: the local change is undone, and the error
332
+ * of the remote passes through.
333
+ * - `write-through`, the remote is not available: with the queue, the write waits in it, a timer
334
+ * sends it later, and the write throws `PendingWriteError`. Without the queue, or with a full
335
+ * queue, the local change is undone.
135
336
  *
136
- * - Each remote item goes into the cache under `keyOf(item)`.
337
+ * Before, a failed write-through stayed in the local cache and in the queue, no timer sent the
338
+ * queue, a second try queued the operation again, and a full queue dropped the oldest operation
339
+ * without a word (roadmap R2).
340
+ */
341
+ private write;
342
+ /**
343
+ * The local values of the ids before a write, and a function that puts them back. It puts back
344
+ * only the ids that no later write changed.
345
+ */
346
+ private snapshot;
347
+ /**
348
+ * Sync from the remote to the local copy, in the modes `write-through` and `write-back`. The
349
+ * other modes have no local copy to sync (`remote-only`) or make no remote call (`local-only`):
350
+ * the call does nothing.
351
+ *
352
+ * - The sync asks for the changes since the version of the last sync when the remote has
353
+ * `getChangesSince`. Else it reads all entries.
354
+ * - Each remote item goes into the cache under its id.
137
355
  * - A local item that the remote does not have is removed, unless a queued local operation
138
- * changes it (the remote deleted it).
356
+ * changes it.
139
357
  * - An id that a local write changed during the sync keeps the local value: the remote value
140
358
  * is older.
141
359
  * - Syncs run one at a time.
@@ -144,43 +362,138 @@ export declare class HybridStorage<T> implements CollectionStorage<T> {
144
362
  /**
145
363
  * Sync all pending offline operations to remote.
146
364
  * Processes queued operations in order. When one fails, it and the later operations stay in
147
- * the queue: the operations that reached the remote are not sent again.
365
+ * the queue: the operations that reached the remote are not sent again. In `local-only` mode,
366
+ * the call does nothing.
148
367
  */
149
368
  syncToRemote(): Promise<void>;
369
+ /**
370
+ * This method sends the queue to the remote (roadmap R28). `ProcedureServer.useStorage` uses it. In
371
+ * `local-only` mode, it sends nothing: the queue waits for a remote mode.
372
+ */
373
+ flush(): Promise<void>;
150
374
  /**
151
375
  * Force a full sync (both directions).
152
376
  */
153
377
  sync(): Promise<void>;
378
+ /**
379
+ * This method stops the timers, sends the queue (not in `local-only` mode), and closes the remote and the
380
+ * local copy. The queue store keeps the operations that the remote did not take. A durable local
381
+ * copy keeps its items.
382
+ */
154
383
  close(): Promise<void>;
155
384
  getMetadata(): Promise<StorageMetadata>;
385
+ /**
386
+ * This method starts a public operation: after the load of the queue (a write) or the first sync (a read),
387
+ * and never during a mode switch.
388
+ */
389
+ private run;
390
+ /** A promise that resolves when no operation runs. */
391
+ private idle;
392
+ /** This method loads the queue, and puts its writes in the local copy. */
393
+ private loadQueue;
394
+ /** The first sync: send the loaded queue, then read the remote. */
395
+ private initialSync;
396
+ /** This method applies a queued operation to the local copy. */
397
+ private applyToLocal;
398
+ /**
399
+ * One pull. `full`: ask for all items (after the local copy was dropped).
400
+ *
401
+ * - The versions of the local writes at the start: a write during the pull keeps its value.
402
+ * - The cursor moves only after a pull without conflicts, so a conflict of the "error" policy
403
+ * comes again at the next pull.
404
+ */
156
405
  private pullRemote;
406
+ /**
407
+ * The remote changes: since the cursor from `getChangesSince` when the remote has it, or else
408
+ * all entries.
409
+ */
410
+ private fetchChanges;
411
+ /** This method applies one remote item. It gives true when the queue changed. */
412
+ private pullItem;
413
+ /** This method applies one remote delete. It gives true when the queue changed. */
414
+ private pullDelete;
415
+ /**
416
+ * A queued local write of an id whose remote version changed: a conflict, and the policy
417
+ * decides. True when the queue changed.
418
+ */
419
+ private resolvePending;
420
+ /**
421
+ * This method removes an id from the queued operations. It gives false when a queued clear touches it: nothing
422
+ * changes then.
423
+ */
424
+ private dropQueued;
425
+ /**
426
+ * The remote items with their ids: from `getEntries()` when the remote has it, or else from
427
+ * `getAll()` and `keyOf`. An item without a key stops the sync before any change. Before, the
428
+ * sync skipped such items and then deleted each local item that the remote list did not have,
429
+ * so the remote list without ids emptied the cache (roadmap R2).
430
+ */
431
+ private remoteEntries;
432
+ /**
433
+ * This method sends the queue. The operations that reached the remote leave the queue. The queue store
434
+ * gets the change at the end.
435
+ */
157
436
  private pushQueue;
158
437
  /** Run syncs one at a time. */
159
438
  private serialize;
160
- private localIds;
161
439
  private bump;
162
- private versionOf;
440
+ /** The count of local writes of an id: it changes with each write and each clear. */
441
+ private writeKey;
442
+ private versionOfItem;
443
+ /** This method records the remote version of an id that the storage read. */
444
+ private noteBase;
445
+ /** This method records the remote versions after a write reached the remote. */
446
+ private noteRemoteWrite;
447
+ /**
448
+ * This method puts in the operation the remote version that it changes, for each id: the version that an
449
+ * older queued operation of the id writes, or else the known remote version. A storage that
450
+ * loads the queue after a restart takes the version of the first operation of each id.
451
+ */
452
+ private attachBase;
453
+ /** The remote version of an id after the queued operations, or undefined when it is not known. */
454
+ private expectedBase;
163
455
  private hasPendingOp;
164
456
  /**
165
457
  * Resolve conflict between local and remote versions.
166
458
  */
167
459
  private resolveConflict;
168
460
  /**
169
- * Queue operation for later sync.
461
+ * This method puts an operation in the queue, then in the queue store. The write ends only after the store
462
+ * kept it. When the store fails, the operation leaves the queue, the local change is undone,
463
+ * and the write throws `QueueStoreError`.
464
+ *
465
+ * A set or a delete of one id replaces the queued set or delete of that id, when no later
466
+ * operation touches the id: a second try of a write does not queue it two times.
170
467
  */
171
- private queueOperation;
468
+ private enqueue;
172
469
  /**
173
- * Execute a queued operation against remote storage.
470
+ * This method writes one operation to the queue store: an append when the store holds each operation
471
+ * before it, or else the whole queue. On a failure, `rollback` removes the operation from the
472
+ * queue before the next call of the store.
174
473
  */
175
- private executeOperation;
474
+ private persist;
475
+ /** This method writes the whole queue to the queue store when the store can differ from the queue. */
476
+ private persistAll;
176
477
  /**
177
- * Handle remote operation failure.
478
+ * Before a write goes to the remote directly, the queue store must not hold a sent operation:
479
+ * after a restart, it would replace this newer write.
178
480
  */
179
- private handleRemoteFailure;
481
+ private ensureStoreClean;
482
+ /** The index of the queued operation that `op` replaces, or -1. */
483
+ private replaceableIndex;
484
+ /** True when the queue can take the operation. */
485
+ private hasQueueRoom;
486
+ /** A full queue refuses the write. Before, it dropped the oldest operation (roadmap R2). */
487
+ private ensureQueueRoom;
180
488
  /**
181
- * Start periodic background sync.
489
+ * In write-through mode, a timer sends the queue after a failure, and again after each failed
490
+ * send, until the queue is empty.
182
491
  */
183
- private startPeriodicSync;
492
+ private scheduleDrain;
493
+ /**
494
+ * Execute a queued operation against remote storage.
495
+ */
496
+ private executeOperation;
184
497
  /**
185
498
  * Get current online status.
186
499
  */
@@ -1 +1 @@
1
- {"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/storage/hybrid.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/storage/hybrid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAkB,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAElG,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAA4D,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvG;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAExE;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,CAAC;AAE3D;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;AAIvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;OAGG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IAExC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IAE1C;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IAE9C;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC;IAExB;;;;OAIG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC;IAExB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IAExB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE5C;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;CAC/B;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAkB,IAAI,uBAAuB;IAC7C,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,QAAQ;IACxB,YAAY,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAE5C;CACF;AAED,sFAAsF;AACtF,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAkB,IAAI,oBAAoB;IAC1C,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,YAAY,GAAG,EAAE,MAAM,EAEtB;CACF;AAED,4FAA4F;AAC5F,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;IAC5C,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,YAAY,KAAK,EAAE,MAAM,EAExB;CACF;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;IAC3C,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,YAAY,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAEnD;CACF;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAG1B,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE;IAFlC,SAAkB,IAAI,mBAAmB;IACzC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,YAAqB,GAAG,EAAE,MAAM,EAAE,EAEjC;CACF;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAG5B,QAAQ,CAAC,IAAI,EAAE,UAAU;IAAE,QAAQ,CAAC,EAAE,EAAE,UAAU;IAF9D,SAAkB,IAAI,qBAAqB;IAC3C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,YAAqB,IAAI,EAAE,UAAU,EAAW,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAE7E;CACF;AA2BD;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG;IAAE,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC;AAEvG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,aAAa,CAAC,CAAC,CAAE,YAAW,iBAAiB,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,OAAO,CAWb;IACF,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,UAAU,CAAyB;IAC3C,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAC1D,iGAAiG;IACjG,OAAO,CAAC,UAAU,CAAS;IAC3B,kDAAkD;IAClD,OAAO,CAAC,UAAU,CAAuC;IACzD,4CAA4C;IAC5C,OAAO,CAAC,SAAS,CAA+C;IAChE,wBAAwB;IACxB,OAAO,CAAC,SAAS,CAA+C;IAChE,kFAAkF;IAClF,OAAO,CAAC,OAAO,CAAS;IACxB,+EAA+E;IAC/E,OAAO,CAAC,UAAU,CAA8C;IAChE,qDAAqD;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,0GAA0G;IAC1G,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,2GAA2G;IAC3G,OAAO,CAAC,SAAS,CAAoB;IACrC,2DAA2D;IAC3D,OAAO,CAAC,IAAI,CAAoC;IAChD,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,YAAY,CAAK;IACzB,gDAAgD;IAChD,OAAO,CAAC,SAAS,CAAuC;IACxD,gDAAgD;IAChD,OAAO,CAAC,MAAM,CAAqB;IACnC,wFAAwF;IACxF,OAAO,CAAC,kBAAkB,CAAS;IACnC,kGAAkG;IAClG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,KAAK,CAKX;IAEF,YACE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,oBAAyB,EAuBnC;IAMD,mCAAmC;IACnC,OAAO,IAAI,UAAU,CAEpB;IAED;;;;;;;;;;;;OAYG;IACG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAc7C;YAEa,QAAQ;IAsBtB,iFAAiF;YACnE,SAAS;IAMvB,gFAAgF;YAClE,YAAY;IAK1B,4DAA4D;IAC5D,OAAO,CAAC,YAAY;IAuCpB;;;;OAIG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CA6B5C;IAEK,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAE3B;IAED,8BAA8B;IACxB,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAE9C;IAEK,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAIxD;IAEK,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEtC;IAEK,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAE5B;IAEK,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAIrD;IAED,8FAA8F;YAChF,UAAU;IAgBlB,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAK7C;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKzC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;IAMK,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvD;IAEK,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAKhD;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,KAAK;IA+Eb;;;OAGG;YACW,QAAQ;IAqBtB;;;;;;;;;;;;;OAaG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAM9B;IAED;;;;;OAKG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAM5B;IAED;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAErB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;IAMD;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA6B3B;IAEK,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC,CAqB5C;IAMD;;;OAGG;YACW,GAAG;IAmBjB,sDAAsD;IACtD,OAAO,CAAC,IAAI;IAKZ,0EAA0E;YAC5D,SAAS;IAyBvB,mEAAmE;YACrD,WAAW;IAiBzB,gEAAgE;YAClD,YAAY;IAqB1B;;;;;;OAMG;YACW,UAAU;IA0CxB;;;OAGG;YACW,YAAY;IAa1B,iFAAiF;YACnE,QAAQ;IA8CtB,mFAAmF;YACrE,UAAU;IAoCxB;;;OAGG;YACW,cAAc;IAmC5B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAWlB;;;;;OAKG;YACW,aAAa;IAc3B;;;OAGG;YACW,SAAS;IAsCvB,+BAA+B;IAC/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,IAAI;IAIZ,qFAAqF;IACrF,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,aAAa;IAIrB,6EAA6E;IAC7E,OAAO,CAAC,QAAQ;IAWhB,gFAAgF;IAChF,OAAO,CAAC,eAAe;IAsBvB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAWlB,kGAAkG;IAClG,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,eAAe;IA8BvB;;;;;;;OAOG;YACW,OAAO;IAmCrB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IA6Bf,sGAAsG;IACtG,OAAO,CAAC,UAAU;IAkBlB;;;OAGG;YACW,gBAAgB;IAI9B,mEAAmE;IACnE,OAAO,CAAC,gBAAgB;IAexB,kDAAkD;IAClD,OAAO,CAAC,YAAY;IAIpB,4FAA4F;IAC5F,OAAO,CAAC,eAAe;IAIvB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;YACW,gBAAgB;IA0B9B;;OAEG;IACH,cAAc,IAAI,OAAO,CAExB;IAED;;OAEG;IACH,kBAAkB,IAAI,MAAM,CAE3B;CACF"}