@procedures/store 0.17.2 → 0.17.4

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 (39) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +92 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/browser.d.ts +6 -2
  5. package/dist/browser.d.ts.map +1 -1
  6. package/dist/browser.js +5 -2
  7. package/dist/browser.js.map +1 -1
  8. package/dist/indexeddb/document-storage.d.ts +186 -0
  9. package/dist/indexeddb/document-storage.d.ts.map +1 -0
  10. package/dist/indexeddb/document-storage.js +598 -0
  11. package/dist/indexeddb/document-storage.js.map +1 -0
  12. package/dist/indexeddb/idb.d.ts +82 -9
  13. package/dist/indexeddb/idb.d.ts.map +1 -1
  14. package/dist/indexeddb/idb.js +100 -22
  15. package/dist/indexeddb/idb.js.map +1 -1
  16. package/dist/indexeddb.d.ts +8 -3
  17. package/dist/indexeddb.d.ts.map +1 -1
  18. package/dist/indexeddb.js +8 -3
  19. package/dist/indexeddb.js.map +1 -1
  20. package/dist/storage/hybrid.d.ts +9 -0
  21. package/dist/storage/hybrid.d.ts.map +1 -1
  22. package/dist/storage/hybrid.js +14 -0
  23. package/dist/storage/hybrid.js.map +1 -1
  24. package/dist/storage/index.d.ts +2 -0
  25. package/dist/storage/index.d.ts.map +1 -1
  26. package/dist/storage/index.js +2 -0
  27. package/dist/storage/index.js.map +1 -1
  28. package/dist/storage/tab-coordinator.d.ts +284 -0
  29. package/dist/storage/tab-coordinator.d.ts.map +1 -0
  30. package/dist/storage/tab-coordinator.js +584 -0
  31. package/dist/storage/tab-coordinator.js.map +1 -0
  32. package/package.json +5 -5
  33. package/src/browser.ts +13 -2
  34. package/src/indexeddb/document-storage.ts +689 -0
  35. package/src/indexeddb/idb.ts +136 -21
  36. package/src/indexeddb.ts +8 -3
  37. package/src/storage/hybrid.ts +14 -0
  38. package/src/storage/index.ts +10 -0
  39. package/src/storage/tab-coordinator.ts +692 -0
@@ -0,0 +1,584 @@
1
+ /**
2
+ * The coordinator of the tabs of a browser (roadmap CLI-25, server stack D9).
3
+ *
4
+ * Each tab of a page has a local copy of a collection. Without a coordinator, each tab runs its own
5
+ * `HybridStorage`: each tab pulls, each tab pushes, and the tabs share one queue store. A
6
+ * `TabCoordinator` lets one leader tab run the sync, and the other tabs copy its changes.
7
+ *
8
+ * - The election uses the Web Locks API: the tab that holds the lock of the coordinator is the
9
+ * leader. When the leader closes, or the browser closes its tab, the lock goes to the next tab.
10
+ * - Only the leader makes a `HybridStorage`, with `capture: "observe"` on the local copy. The
11
+ * followers make no remote call, and they run no timer of a sync.
12
+ * - The tabs send the change batches of their local copies on a `BroadcastChannel`. A tab gives the
13
+ * batches of the other tabs to its observers, with their origins.
14
+ * - The leader captures the batches of the followers: its `HybridStorage` queues them and pushes
15
+ * them. Only the leader writes to the queue store, so the tabs can share it by its name.
16
+ * - A follower keeps each own batch until the leader acknowledges it. The leader acknowledges a
17
+ * batch when its queue store holds it. A new leader gets the batches that no leader
18
+ * acknowledged.
19
+ * - Without the Web Locks API or `BroadcastChannel`, the coordinator is in the single mode: the tab
20
+ * is the leader at once, and it opens no channel.
21
+ *
22
+ * The limits:
23
+ * - When the leader stops during a push, the next leader sends the operation again: the delivery to
24
+ * the remote is at least once.
25
+ * - With a shared local copy, a pull of the leader can come between the write of a follower and its
26
+ * capture. The tabs agree again after the push and the next pull.
27
+ * - The load of the queue in a new leader writes the queued operations to the local copy again, and
28
+ * the observers get them again with the origin `syncOrigin`.
29
+ *
30
+ * Nothing in this module reads a global value when it loads: Node can import it. The coordinator
31
+ * reads `navigator.locks` and `BroadcastChannel` when it starts, and a test gives its own.
32
+ */
33
+ import { StorageListeners, reportListenerError } from "@procedures/collections";
34
+ import { HybridStorage } from "./hybrid.js";
35
+ /** The version of the messages. A tab ignores a message of another version. */
36
+ const PROTOCOL = 1;
37
+ /**
38
+ * One leader tab runs the sync of a `HybridStorage`, and the other tabs copy its changes.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { ApiStorage, IndexedDbQueueStore, IndexedDbStorage, IndexedDbSyncStateStore, TabCoordinator } from "@procedures/store/browser";
43
+ *
44
+ * const local = new IndexedDbStorage<User>("users");
45
+ * const users = new TabCoordinator<User>({
46
+ * name: "users",
47
+ * local,
48
+ * copy: "shared",
49
+ * remote: () => new ApiStorage(client, { collection: "users" }),
50
+ * hybrid: {
51
+ * mode: "write-back",
52
+ * queueStore: new IndexedDbQueueStore("users-queue"),
53
+ * syncState: new IndexedDbSyncStateStore("users-sync"),
54
+ * },
55
+ * });
56
+ *
57
+ * users.observe((batch) => render(batch)); // The changes of this tab and of the other tabs
58
+ * await local.set("1", { name: "Ada" }); // The leader pushes the write
59
+ * ```
60
+ */
61
+ export class TabCoordinator {
62
+ /** The id of this tab: a new random id for each coordinator. */
63
+ id;
64
+ /** The name of the lock and of the channel. */
65
+ lockName;
66
+ local;
67
+ shared;
68
+ makeRemote;
69
+ hybridOptions;
70
+ /** The origin of the `HybridStorage` and of the writes of the coordinator to the local copy. */
71
+ tag;
72
+ writeOptions;
73
+ onError;
74
+ observers;
75
+ roleListeners;
76
+ /** The observers that the `HybridStorage` of the leader adds to its local copy: the capture */
77
+ captures;
78
+ currentRole = "follower";
79
+ channel;
80
+ hybrid;
81
+ /** The stop of the observer of the changes of this tab: on the local copy, or on the `HybridStorage` of the leader */
82
+ stopTap;
83
+ /** True while the leader gives a batch of another tab to the capture of its `HybridStorage` */
84
+ capturing = false;
85
+ /** The number of the last batch of this tab */
86
+ seq = 0;
87
+ /** The batches of this tab that no leader acknowledged, the oldest first */
88
+ outbox = [];
89
+ /** The number of the last batch of each other tab that this tab copied */
90
+ seen = new Map();
91
+ /** In the leader: the number of the last batch of each tab that the leader captured. A tab without a hello is not here. */
92
+ captured = new Map();
93
+ /** The batches of the other tabs: they reach the local copy and the observers in their order */
94
+ incoming = Promise.resolve();
95
+ closed = false;
96
+ closing;
97
+ /** The abort of the lock request that waits */
98
+ abortRequest;
99
+ /** The release of the lock */
100
+ releaseLock;
101
+ constructor(options) {
102
+ if (typeof options?.name !== "string" || options.name === "")
103
+ throw new TypeError("TabCoordinator needs a name");
104
+ if (typeof options.local?.observe !== "function")
105
+ throw new TypeError("TabCoordinator needs a local copy with observe()");
106
+ if (options.copy !== "shared" && options.copy !== "tab") {
107
+ throw new TypeError(`Unknown copy ${JSON.stringify(options.copy)}: use shared or tab`);
108
+ }
109
+ if (typeof options.remote !== "function")
110
+ throw new TypeError("TabCoordinator needs the function remote");
111
+ this.id = randomId();
112
+ this.lockName = `@procedures/store/tab:${options.name}`;
113
+ this.local = options.local;
114
+ this.shared = options.copy === "shared";
115
+ this.makeRemote = options.remote;
116
+ this.hybridOptions = options.hybrid ?? {};
117
+ this.tag = this.hybridOptions.origin ?? `tab-${this.id}`;
118
+ this.writeOptions = { origin: this.tag };
119
+ this.onError = options.onError ?? reportListenerError;
120
+ this.observers = new StorageListeners(this.onError);
121
+ this.roleListeners = new StorageListeners(this.onError);
122
+ this.captures = new StorageListeners(this.onError);
123
+ const locks = options.locks === undefined ? globalLocks() : (options.locks ?? undefined);
124
+ const openChannel = options.channel === undefined ? globalChannelFactory() : (options.channel ?? undefined);
125
+ if (!locks || !openChannel) {
126
+ this.lead("single");
127
+ return;
128
+ }
129
+ try {
130
+ this.channel = openChannel(this.lockName);
131
+ }
132
+ catch (error) {
133
+ this.onError(error);
134
+ this.lead("single");
135
+ return;
136
+ }
137
+ this.channel.onmessage = (event) => this.receive(event?.data);
138
+ this.stopTap = this.local.observe((batch) => this.onFollowerBatch(batch));
139
+ this.hello();
140
+ this.elect(locks);
141
+ }
142
+ /** The role of this tab now. */
143
+ get role() {
144
+ return this.currentRole;
145
+ }
146
+ /** True when this tab runs the sync: the leader, or a tab in the single mode. */
147
+ get isLeader() {
148
+ return this.currentRole === "leader" || this.currentRole === "single";
149
+ }
150
+ /**
151
+ * The `HybridStorage` of the sync while this tab runs it, or else undefined. Its `getStatus()`
152
+ * gives the state of the sync. Write to the local copy, not to this storage: each tab then writes
153
+ * in the same way.
154
+ */
155
+ get syncStorage() {
156
+ return this.hybrid;
157
+ }
158
+ /**
159
+ * This method starts to send the change batches to an observer: the batches of the local copy of
160
+ * this tab, and the batches of the other tabs. Each batch comes one time, with the origin of each
161
+ * change. In the leader, the batches of the sync have the origin `syncOrigin` of the
162
+ * `HybridStorage`. A batch of another tab comes after the local copy of this tab has it.
163
+ *
164
+ * @returns The function that stops the batches. A second use of it does nothing.
165
+ * @throws Error after `close()`
166
+ */
167
+ observe(observer) {
168
+ if (this.closed)
169
+ throw new Error("The TabCoordinator is closed");
170
+ return this.observers.add(observer);
171
+ }
172
+ /**
173
+ * This method starts to send the role of this tab to a listener at each change.
174
+ *
175
+ * @returns The function that stops the roles. A second use of it does nothing.
176
+ * @throws Error after `close()`
177
+ */
178
+ onRole(listener) {
179
+ if (this.closed)
180
+ throw new Error("The TabCoordinator is closed");
181
+ return this.roleListeners.add(listener);
182
+ }
183
+ /**
184
+ * This method closes the coordinator.
185
+ *
186
+ * - A follower stops its lock request. It does not become the leader.
187
+ * - The leader closes its `HybridStorage`: it sends the queue, and the queue store keeps the
188
+ * operations that the remote did not take. Then the leader releases the lock, and the next
189
+ * tab becomes the leader.
190
+ * - The coordinator closes its channel. It does not close the local copy: the application owns it.
191
+ * - After it, the coordinator sends and copies nothing. A second `close()` gives the promise of
192
+ * the first one.
193
+ */
194
+ close() {
195
+ if (!this.closing)
196
+ this.closing = this.shutDown();
197
+ return this.closing;
198
+ }
199
+ async shutDown() {
200
+ this.closed = true;
201
+ this.stopTap?.();
202
+ this.stopTap = undefined;
203
+ this.abortRequest?.();
204
+ this.abortRequest = undefined;
205
+ this.setRole("closed");
206
+ const hybrid = this.hybrid;
207
+ this.hybrid = undefined;
208
+ try {
209
+ // The leader holds the lock until its queue reached the remote or its queue store
210
+ if (hybrid)
211
+ await hybrid.close();
212
+ }
213
+ catch (error) {
214
+ this.onError(error);
215
+ }
216
+ finally {
217
+ this.releaseLock?.();
218
+ this.releaseLock = undefined;
219
+ // The acknowledgements of the close of the HybridStorage went out before
220
+ try {
221
+ this.channel?.close();
222
+ }
223
+ catch {
224
+ // The channel is closed already
225
+ }
226
+ this.channel = undefined;
227
+ this.observers.clear();
228
+ this.roleListeners.clear();
229
+ this.captures.clear();
230
+ }
231
+ }
232
+ //
233
+ // ═══ The election ═══
234
+ //
235
+ /** This method asks for the lock. The tab becomes the leader when it gets the lock. */
236
+ elect(locks) {
237
+ const Controller = globalAbortController();
238
+ const controller = Controller ? new Controller() : undefined;
239
+ this.abortRequest = controller ? () => controller.abort() : undefined;
240
+ const held = new Promise((resolve) => (this.releaseLock = resolve));
241
+ let request;
242
+ try {
243
+ request = locks.request(this.lockName, controller ? { signal: controller.signal } : {}, async () => {
244
+ // A lock after close(): the tab releases it at once
245
+ if (this.closed)
246
+ return;
247
+ this.abortRequest = undefined;
248
+ // A HybridStorage that cannot start releases the lock: another tab tries
249
+ if (!this.lead("leader"))
250
+ return;
251
+ await held;
252
+ });
253
+ }
254
+ catch (error) {
255
+ request = Promise.reject(error);
256
+ }
257
+ request.then(undefined, (error) => {
258
+ // The abort of close(), or a tab that leads already
259
+ if (this.closed || this.hybrid)
260
+ return;
261
+ // The lock manager refused the request: this tab runs the sync alone
262
+ this.onError(error);
263
+ this.stopChannel();
264
+ this.lead("single");
265
+ });
266
+ }
267
+ /**
268
+ * This method makes the `HybridStorage` of the sync: the tab becomes the leader, or the single
269
+ * tab. The capture of the storage gets the batches of this tab that no leader acknowledged.
270
+ *
271
+ * @returns False when the storage could not start
272
+ */
273
+ lead(role) {
274
+ if (this.closed)
275
+ return false;
276
+ let hybrid;
277
+ try {
278
+ hybrid = new HybridStorage(this.makeRemote(), {
279
+ ...this.hybridOptions,
280
+ local: this.captureCopy(),
281
+ capture: "observe",
282
+ origin: this.tag,
283
+ });
284
+ }
285
+ catch (error) {
286
+ this.onError(error);
287
+ return false;
288
+ }
289
+ this.hybrid = hybrid;
290
+ // The new observer starts before the old one stops: no change of the local copy goes between them
291
+ const stopFollower = this.stopTap;
292
+ this.stopTap = hybrid.observe((batch) => this.onLeaderBatch(batch));
293
+ stopFollower?.();
294
+ for (const batch of this.outbox.splice(0))
295
+ this.capture(batch.changes);
296
+ this.setRole(role);
297
+ if (role === "leader")
298
+ this.post({ v: PROTOCOL, type: "leader", from: this.id });
299
+ return true;
300
+ }
301
+ /**
302
+ * The local copy for the `HybridStorage` of the leader. Its `observe()` gives the changes of the
303
+ * local copy and the batches of the other tabs, so the capture queues both. Its `close()` does
304
+ * nothing: the application owns the local copy.
305
+ */
306
+ captureCopy() {
307
+ const captures = this.captures;
308
+ const observe = (observer) => {
309
+ const stopLocal = this.local.observe(observer);
310
+ const stopCapture = captures.add(observer);
311
+ return () => {
312
+ stopLocal();
313
+ stopCapture();
314
+ };
315
+ };
316
+ return new Proxy(this.local, {
317
+ get(target, property) {
318
+ if (property === "observe")
319
+ return observe;
320
+ if (property === "close")
321
+ return () => Promise.resolve();
322
+ const value = Reflect.get(target, property, target);
323
+ return typeof value === "function" ? value.bind(target) : value;
324
+ },
325
+ });
326
+ }
327
+ /**
328
+ * This method gives a batch of another tab to the capture of the `HybridStorage` of the leader.
329
+ * The storage queues it. Its observers get it at once, and the coordinator ignores it there: the
330
+ * observers of the coordinator get it from the channel.
331
+ */
332
+ capture(changes) {
333
+ if (changes.length === 0)
334
+ return;
335
+ this.capturing = true;
336
+ try {
337
+ this.captures.emit(changes);
338
+ }
339
+ finally {
340
+ this.capturing = false;
341
+ }
342
+ }
343
+ setRole(role) {
344
+ if (this.currentRole === role)
345
+ return;
346
+ this.currentRole = role;
347
+ this.roleListeners.emit(role);
348
+ }
349
+ //
350
+ // ═══ The batches of this tab ═══
351
+ //
352
+ /**
353
+ * A batch of the local copy of a follower. The writes of the coordinator itself (the batches of
354
+ * the other tabs in a copy of each tab) have the tag of the coordinator, and do not go out again.
355
+ */
356
+ onFollowerBatch(batch) {
357
+ if (this.closed)
358
+ return;
359
+ const own = batch.filter((change) => change.origin !== this.tag);
360
+ if (own.length > 0)
361
+ this.share(own, true);
362
+ }
363
+ /**
364
+ * A batch of the `HybridStorage` of the leader: a write of the application to the local copy,
365
+ * a write to the storage, or a change of the sync.
366
+ */
367
+ onLeaderBatch(batch) {
368
+ if (this.closed || this.capturing)
369
+ return;
370
+ this.share(batch, false);
371
+ }
372
+ /** This method gives a batch of this tab to its observers and to the other tabs. A follower keeps it for the leader. */
373
+ share(changes, keep) {
374
+ const seq = ++this.seq;
375
+ this.observers.emit(changes);
376
+ const sent = this.post({ v: PROTOCOL, type: "changes", from: this.id, seq, changes });
377
+ if (sent && keep)
378
+ this.outbox.push({ seq, changes });
379
+ }
380
+ /** This method tells the leader the batches of this tab that no leader acknowledged. */
381
+ hello() {
382
+ this.post({ v: PROTOCOL, type: "hello", from: this.id, seq: this.seq, pending: this.outbox });
383
+ }
384
+ /** @returns False when the message did not go out: no channel, or a value that the channel cannot clone */
385
+ post(message) {
386
+ const channel = this.channel;
387
+ if (!channel)
388
+ return false;
389
+ try {
390
+ channel.postMessage(message);
391
+ return true;
392
+ }
393
+ catch (error) {
394
+ this.onError(error);
395
+ return false;
396
+ }
397
+ }
398
+ stopChannel() {
399
+ const channel = this.channel;
400
+ this.channel = undefined;
401
+ if (!channel)
402
+ return;
403
+ channel.onmessage = null;
404
+ try {
405
+ channel.close();
406
+ }
407
+ catch {
408
+ // The channel is closed already
409
+ }
410
+ }
411
+ //
412
+ // ═══ The batches of the other tabs ═══
413
+ //
414
+ receive(data) {
415
+ if (this.closed || !isTabMessage(data) || data.from === this.id)
416
+ return;
417
+ switch (data.type) {
418
+ case "changes":
419
+ this.onChanges(data.from, { seq: data.seq, changes: data.changes });
420
+ break;
421
+ case "hello":
422
+ this.onHello(data.from, data.seq, data.pending);
423
+ break;
424
+ case "leader":
425
+ // The new leader gets the batches that no leader acknowledged
426
+ if (this.currentRole === "follower")
427
+ this.hello();
428
+ break;
429
+ case "ack":
430
+ if (data.to === this.id)
431
+ this.outbox = this.outbox.filter((batch) => batch.seq > data.seq);
432
+ break;
433
+ }
434
+ }
435
+ /**
436
+ * A batch of another tab. The leader captures it when the tab said hello to it. Each tab copies
437
+ * it one time.
438
+ */
439
+ onChanges(from, batch) {
440
+ const known = this.captured.get(from);
441
+ if (this.hybrid && this.currentRole === "leader" && known !== undefined && batch.seq > known) {
442
+ this.capture(batch.changes);
443
+ this.captured.set(from, batch.seq);
444
+ this.acknowledge(from, batch.seq);
445
+ }
446
+ this.copy(from, batch);
447
+ }
448
+ /**
449
+ * The hello of another tab. The leader captures the batches that it did not capture, and then each
450
+ * batch of the tab after `seq`. Each tab copies the batches that it did not see.
451
+ */
452
+ onHello(from, seq, pending) {
453
+ if (this.hybrid && this.currentRole === "leader") {
454
+ const known = this.captured.get(from);
455
+ for (const batch of pending)
456
+ if (known === undefined || batch.seq > known)
457
+ this.capture(batch.changes);
458
+ this.captured.set(from, Math.max(known ?? 0, seq));
459
+ if (pending.length > 0)
460
+ this.acknowledge(from, seq);
461
+ }
462
+ for (const batch of pending)
463
+ this.copy(from, batch);
464
+ }
465
+ /**
466
+ * This method copies a batch of another tab that this tab did not see: to the local copy of a
467
+ * tab, and then to the observers. The batches go in their order.
468
+ */
469
+ copy(from, batch) {
470
+ if (batch.seq <= (this.seen.get(from) ?? 0))
471
+ return;
472
+ this.seen.set(from, batch.seq);
473
+ this.incoming = this.incoming
474
+ .then(async () => {
475
+ if (this.closed)
476
+ return;
477
+ if (!this.shared)
478
+ await this.write(batch.changes);
479
+ if (!this.closed)
480
+ this.observers.emit(batch.changes);
481
+ })
482
+ .catch((error) => this.onError(error));
483
+ }
484
+ /**
485
+ * This method writes a batch of another tab to the local copy of this tab, with the tag of the
486
+ * coordinator: the observer of this tab and the capture of the leader ignore the writes. The sets
487
+ * in a row go in one `setBatch`, and the deletes in a row in one `deleteBatch`.
488
+ */
489
+ async write(changes) {
490
+ let index = 0;
491
+ while (index < changes.length) {
492
+ const change = changes[index];
493
+ if (change.type === "clear") {
494
+ await this.local.clear(this.writeOptions);
495
+ index++;
496
+ }
497
+ else if (change.type === "set") {
498
+ const items = [];
499
+ for (let next = changes[index]; next?.type === "set"; next = changes[++index])
500
+ items.push([next.id, next.value]);
501
+ await this.local.setBatch(items, this.writeOptions);
502
+ }
503
+ else {
504
+ const ids = [];
505
+ for (let next = changes[index]; next?.type === "delete"; next = changes[++index])
506
+ ids.push(next.id);
507
+ await this.local.deleteBatch(ids, this.writeOptions);
508
+ }
509
+ }
510
+ }
511
+ /** This method tells a tab that the queue store of the leader holds its batches up to `seq`. */
512
+ acknowledge(to, seq) {
513
+ const hybrid = this.hybrid;
514
+ if (!hybrid)
515
+ return;
516
+ hybrid.whenQueued().then(() => void this.post({ v: PROTOCOL, type: "ack", from: this.id, to, seq }), (error) => this.onError(error));
517
+ }
518
+ }
519
+ /** A new random id. */
520
+ function randomId() {
521
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
522
+ }
523
+ /** `navigator.locks` of the global scope, or undefined. */
524
+ function globalLocks() {
525
+ return globalThis.navigator?.locks ?? undefined;
526
+ }
527
+ /** A function that opens a `BroadcastChannel` of the global scope, or undefined. */
528
+ function globalChannelFactory() {
529
+ const Channel = globalThis.BroadcastChannel;
530
+ return typeof Channel === "function" ? (name) => new Channel(name) : undefined;
531
+ }
532
+ /** The `AbortController` of the global scope, or undefined. */
533
+ function globalAbortController() {
534
+ const Controller = globalThis.AbortController;
535
+ return typeof Controller === "function" ? Controller : undefined;
536
+ }
537
+ function isSeq(value) {
538
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
539
+ }
540
+ /** True when a value is a change batch. The value of a message of another tab can be anything. */
541
+ function isBatch(value) {
542
+ if (!Array.isArray(value))
543
+ return false;
544
+ return value.every((change) => {
545
+ if (typeof change !== "object" || change === null)
546
+ return false;
547
+ const { type, id, origin } = change;
548
+ if (origin !== undefined && typeof origin !== "string")
549
+ return false;
550
+ if (type === "clear")
551
+ return true;
552
+ if (typeof id !== "string")
553
+ return false;
554
+ return type === "delete" || (type === "set" && "value" in change);
555
+ });
556
+ }
557
+ /** True when a value is a message of the channel, of this version. */
558
+ function isTabMessage(value) {
559
+ if (typeof value !== "object" || value === null)
560
+ return false;
561
+ const message = value;
562
+ if (message.v !== PROTOCOL || typeof message.from !== "string")
563
+ return false;
564
+ switch (message.type) {
565
+ case "changes":
566
+ return isSeq(message.seq) && isBatch(message.changes);
567
+ case "hello":
568
+ return (isSeq(message.seq) &&
569
+ Array.isArray(message.pending) &&
570
+ message.pending.every((batch) => {
571
+ if (typeof batch !== "object" || batch === null)
572
+ return false;
573
+ const { seq, changes } = batch;
574
+ return isSeq(seq) && isBatch(changes);
575
+ }));
576
+ case "leader":
577
+ return true;
578
+ case "ack":
579
+ return typeof message.to === "string" && isSeq(message.seq);
580
+ default:
581
+ return false;
582
+ }
583
+ }
584
+ //# sourceMappingURL=tab-coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-coordinator.js","sourceRoot":"","sources":["../../src/storage/tab-coordinator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/store",
4
- "version": "0.17.2",
4
+ "version": "0.17.4",
5
5
  "description": "A program store for procedure-as-data: save the programs that procedure.define makes, load them back after a restart, in memory, a JSON file, IndexedDB or a remote collection storage.",
6
6
  "keywords": [
7
7
  "program-store",
@@ -77,13 +77,13 @@
77
77
  ]
78
78
  },
79
79
  "dependencies": {
80
- "@mark1russell7/service": "^0.5.1",
81
- "@procedures/client": "0.17.2",
82
- "@procedures/collections": "0.17.2",
80
+ "@mark1russell7/service": "^0.5.8",
81
+ "@procedures/client": "0.17.4",
82
+ "@procedures/collections": "0.17.4",
83
83
  "zod": "^4.6.5"
84
84
  },
85
85
  "devDependencies": {
86
- "@procedures/fs": "0.17.2",
86
+ "@procedures/fs": "0.17.4",
87
87
  "@mark1russell7/cue": "^1.0.0",
88
88
  "@types/node": "^22.0.0",
89
89
  "fake-indexeddb": "^6.2.5",
package/src/browser.ts CHANGED
@@ -5,8 +5,10 @@
5
5
  * - `HybridStorage` keeps a local copy in front of a remote storage, with its errors and its types.
6
6
  * - `ApiStorage` is the remote storage over the collection procedures of a server.
7
7
  * - `EventsChangeFeed` pushes the remote changes to a `HybridStorage`.
8
- * - The entry also has all of `@procedures/store/indexeddb`: the IndexedDB stores and their
9
- * fallbacks in memory.
8
+ * - `TabCoordinator` lets one leader tab run the sync of a `HybridStorage`, and the other tabs
9
+ * copy its changes (roadmap CLI-25). It uses the Web Locks API and `BroadcastChannel`.
10
+ * - The entry also has all of `@procedures/store/indexeddb`: `IndexedDbStorage`, the IndexedDB
11
+ * stores and their fallbacks in memory.
10
12
  *
11
13
  * No module of this entry imports a module of Node, directly or through another module. The
12
14
  * modules import `@procedures/client/browser` and not the root of the client, because the root
@@ -28,6 +30,15 @@ export {
28
30
  SyncStateError,
29
31
  } from "./storage/hybrid.js";
30
32
  export type { HybridStorageOptions, HybridMode, LocalCopyStorage } from "./storage/hybrid.js";
33
+ export { TabCoordinator } from "./storage/tab-coordinator.js";
34
+ export type {
35
+ TabAbortSignalLike,
36
+ TabChannelFactory,
37
+ TabChannelLike,
38
+ TabCoordinatorOptions,
39
+ TabLockManagerLike,
40
+ TabRole,
41
+ } from "./storage/tab-coordinator.js";
31
42
  export type { ConflictResolution, SyncError, SyncStatus } from "./storage/types.js";
32
43
  export { ApiStorage } from "./storage/api.js";
33
44
  export type { ApiStorageOptions } from "./storage/api.js";