@procedures/store 0.24.0 → 0.25.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.
- package/CHANGELOG.md +33 -0
- package/README.md +59 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.d.ts +3 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/tab-coordinator.d.ts +194 -145
- package/dist/storage/tab-coordinator.d.ts.map +1 -1
- package/dist/storage/tab-coordinator.js +1018 -251
- package/dist/storage/tab-coordinator.js.map +1 -1
- package/package.json +4 -4
- package/src/browser.ts +6 -1
- package/src/storage/index.ts +5 -1
- package/src/storage/tab-coordinator.ts +1199 -256
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# @procedures/store
|
|
2
2
|
|
|
3
|
+
## 0.25.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [75bff3b]
|
|
8
|
+
- Updated dependencies [16562ee]
|
|
9
|
+
- @procedures/client@0.25.0
|
|
10
|
+
- @procedures/collections@0.25.0
|
|
11
|
+
|
|
12
|
+
## 0.24.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- ddf8328: Added: `TabGroup` does the sync of many collections with one lock and one channel (roadmap CLI-64). The option `snapshot` gives a new tab the entries of the leader (roadmap CLI-66). `TabCoordinator` does not change: its options, its messages and its lock stay the same.
|
|
17
|
+
|
|
18
|
+
- `new TabGroup({ name, open })` holds one Web Lock and one `BroadcastChannel` for each name, for example for each tenant. `collection(name, options)` gives a `TabCollection`, with `observe()`, `onRole()`, `getStatus()`, `onStatus()`, `role` and `syncStorage`.
|
|
19
|
+
- The leader of the group has one `HybridStorage` for each collection. Each message has the field `collection`. Each collection has its own batches, acknowledgements and status.
|
|
20
|
+
- Each collection tells the other tabs when it leads. Thus a slow collection does not stop the batches of the other collections.
|
|
21
|
+
- A collection whose setup fails tries again alone after its backoff (CLI-67), and the other collections keep their sync. The tab sends a status with the error to the other tabs (CLI-65).
|
|
22
|
+
- When no collection of the group leads or loads its queue, the tab releases the lock, as a `TabCoordinator` does. This also applies to a collection that the application adds after the tab got the lock.
|
|
23
|
+
- A `TabCoordinator` and a `TabGroup` do not see each other. When you move a collection to a `TabGroup`, give it a new queue store, or close the tabs of the older release first.
|
|
24
|
+
- With the option `open`, the leader also makes a collection that only another tab opened.
|
|
25
|
+
- The lock and the channel of a group have the prefix `@procedures/store/tabs:`, and those of a coordinator have `@procedures/store/tab:`. A group and a coordinator do not share a lock or a channel.
|
|
26
|
+
- `snapshot: true` (only with `copy: "tab"`) lets a new tab ask the leader for the entries of its copy. The leader sends them in chunks of `snapshot.chunk` entries (500). Each chunk has the number of the last batch of each tab that the entries hold.
|
|
27
|
+
- The new tab writes the entries first, and then only the batches after the snapshot. It keeps the values of its own writes. When the leader changes, it asks the new leader.
|
|
28
|
+
- The snapshot can repeat a set that also comes in a later batch. The second write gives the same state. `snapshot.chunk` counts entries, not bytes.
|
|
29
|
+
- When no chunk comes in `snapshot.timeout` (10 seconds), the tab gives an error to `onError` and continues without a snapshot. A leader of an older release does not answer.
|
|
30
|
+
- In the tab that holds the lock, the wait renews itself while the collection loads its queue, at most 5 times.
|
|
31
|
+
- The new message types `snapshot-request` and `snapshot` keep the version 1 of the messages. A tab of an older release ignores them.
|
|
32
|
+
- Updated dependencies [2735505]
|
|
33
|
+
- @procedures/client@0.24.1
|
|
34
|
+
- @procedures/collections@0.24.1
|
|
35
|
+
|
|
3
36
|
## 0.24.0
|
|
4
37
|
|
|
5
38
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -336,7 +336,7 @@ The option `copy` tells the kind of the local copy:
|
|
|
336
336
|
|
|
337
337
|
- `"shared"`: the tabs open the same durable local copy, for example an `IndexedDbStorage` of one database. A tab gives the batches of the other tabs only to its observers.
|
|
338
338
|
- `"tab"`: each tab has its own local copy, for example a SignalDB collection. A tab writes the batches of the other tabs to its copy, and then gives them to its observers.
|
|
339
|
-
- In the mode `"tab"`, a new tab gets only the changes after its start. Its local copy loads the older items itself, for example from its persistence.
|
|
339
|
+
- In the mode `"tab"`, a new tab gets only the changes after its start. Its local copy loads the older items itself, for example from its persistence. The option `snapshot` gives it the entries of the leader (roadmap CLI-66).
|
|
340
340
|
- In the mode `"shared"`, a pull of the leader can come between the write of a follower and its capture. The tabs agree again after the push and the next pull.
|
|
341
341
|
|
|
342
342
|
A new leader and a closed tab:
|
|
@@ -397,6 +397,63 @@ users.getStatus(); // The last status, in each tab
|
|
|
397
397
|
- The options `locks` and `channel` take a lock manager and a channel factory, for example the ones of a test. `null` sets the single mode.
|
|
398
398
|
- `HybridStorage.whenQueued()` resolves when the queue store holds the queue. The leader acknowledges the batches of the followers after it.
|
|
399
399
|
|
|
400
|
+
Many collections in one group (roadmap CLI-64):
|
|
401
|
+
|
|
402
|
+
`TabGroup` does the sync of many collections with one lock and one channel, for example all the collections of a tenant. With a `TabCoordinator` for each collection, each tab holds one lock and one channel for each collection.
|
|
403
|
+
|
|
404
|
+
- `collection(name, options)` adds a collection to the group and gives its `TabCollection`. A second use with the same name gives the same collection.
|
|
405
|
+
- A `TabCollection` has `observe()`, `onRole()`, `getStatus()`, `onStatus()`, `role`, `isLeader` and `syncStorage`, as a `TabCoordinator`.
|
|
406
|
+
- The options of a collection are `local`, `copy`, `remote`, `hybrid` and `snapshot`, as the options of a `TabCoordinator`. Give a different `queueStore` to each collection.
|
|
407
|
+
- The leader of the group has one `HybridStorage` for each collection. Each message has the name of its collection. Each collection has its own batches, acknowledgements and status.
|
|
408
|
+
- Each collection leads when its own queue loaded. It tells the other tabs at once, and the followers then send their batches of that collection. Thus a slow collection does not stop the batches of the other collections.
|
|
409
|
+
- A collection whose setup fails tries again alone after its backoff, and the other collections keep their sync.
|
|
410
|
+
- After a failed setup, the tab sends a status of the collection with the error (operation `load`) to the other tabs. Thus a follower does not show the status of an old leader.
|
|
411
|
+
- When no collection of the group leads or loads its queue, the tab releases the lock. It asks for the lock again after the backoff, as a `TabCoordinator` does.
|
|
412
|
+
- This rule also applies to a collection that the application adds after the tab got the lock. A group without collections keeps the lock until its first collection.
|
|
413
|
+
- The option `open` gives the options of a collection from its name. `collection(name)` uses it. The leader also uses it for a collection that only another tab opened.
|
|
414
|
+
- Without `open`, the leader does the sync of its own collections only. A write of a follower to another collection waits in the follower until a leader has that collection.
|
|
415
|
+
- `close()` of the group closes each collection. A collection has no `close()` of its own.
|
|
416
|
+
- The lock and the channel of a group have the name `@procedures/store/tabs:<name>`. A `TabCoordinator` uses `@procedures/store/tab:<name>`. Thus a group and a coordinator do not share a lock or a channel.
|
|
417
|
+
- A `TabCoordinator` sends its messages without the name of a collection, as before. Thus it works with a tab of an older release.
|
|
418
|
+
|
|
419
|
+
> **Warning:** A `TabCoordinator` and a `TabGroup` do not see each other. Thus, when you move a collection from a `TabCoordinator` to a `TabGroup`, a tab of the older release can stay open. Then two tabs lead the collection, with one queue store and one remote. To prevent this, give new names to the queue store and the sync state store of that collection. Or close the tabs of the older release before the new release opens. The operations in the old queue store stay there until a tab loads that store.
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
import { ApiStorage, IndexedDbQueueStore, TabGroup } from "@procedures/store/browser";
|
|
423
|
+
|
|
424
|
+
const tenant = new TabGroup({
|
|
425
|
+
name: tenantId,
|
|
426
|
+
open: (collection) => ({
|
|
427
|
+
local: localCopyOf(collection),
|
|
428
|
+
copy: "tab",
|
|
429
|
+
remote: () => new ApiStorage(client, { collection }),
|
|
430
|
+
hybrid: { queueStore: new IndexedDbQueueStore(`${tenantId}-${collection}-queue`) },
|
|
431
|
+
snapshot: true,
|
|
432
|
+
}),
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const notes = tenant.collection<Note>("notes");
|
|
436
|
+
notes.observe((batch) => render(batch));
|
|
437
|
+
notes.onStatus((status) => showSync("notes", status)); // The status of the sync of notes, in each tab
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
A snapshot for a new tab (roadmap CLI-66):
|
|
441
|
+
|
|
442
|
+
- In the mode `"tab"`, the option `snapshot: true` lets a new tab ask the leader for the entries of its local copy. The mode `"shared"` does not take the option.
|
|
443
|
+
- The leader reads the entries of its copy with `getEntries()`. It sends them in chunks of `snapshot.chunk` entries, 500 by default.
|
|
444
|
+
- Each chunk has the number of the last batch of each tab that the entries hold.
|
|
445
|
+
- The new tab keeps the batches of the other tabs until the last chunk comes. Then it writes the entries to its copy, and then the batches with a later number.
|
|
446
|
+
- The snapshot can repeat a set. For example, a write of the leader during `getEntries()` can be in the entries and also in a later batch. The second write of the set gives the same state.
|
|
447
|
+
- The observers get the entries in one batch of sets, with the origin `syncOrigin`.
|
|
448
|
+
- The new tab keeps the values of its own writes after the snapshot. The snapshot does not delete an item that only the new tab has.
|
|
449
|
+
- When the leader changes before the last chunk, the tab asks the new leader. It does not use the chunks of the old leader.
|
|
450
|
+
- When no chunk comes in the time `snapshot.timeout` (10 seconds by default), the tab gives an error to `onError`. Then it writes the batches that waited, without a snapshot.
|
|
451
|
+
- In the tab that holds the lock, no other tab sends a snapshot. The snapshot ends when the collection leads. While the collection loads its queue, the wait renews itself, at most 5 times. Thus the batches wait at most 6 × `snapshot.timeout`, also when the load stops.
|
|
452
|
+
- A leader of an older release does not answer. Thus a new tab with a leader of an older release gets the error after the timeout.
|
|
453
|
+
- The leader reads all the entries at one time, and the new tab keeps all the chunks until the last one. Thus a snapshot is for a copy that fits in the memory of a tab.
|
|
454
|
+
- `snapshot.chunk` limits the number of entries in a message, not its bytes. Thus large items give large messages.
|
|
455
|
+
- The snapshot is an option, because the coordinator cannot know if a local copy is durable. A copy with persistence is empty until its load ends, so an empty copy is not a sign.
|
|
456
|
+
|
|
400
457
|
### The change of a storage in a server
|
|
401
458
|
|
|
402
459
|
`ProcedureServer.useStorage(name, storage, { drain: true, close: true })` first starts `flush()` of the old storage. Then it uses the new storage and closes the old one. When the flush fails, the old storage stays.
|
|
@@ -420,6 +477,7 @@ The entry `@procedures/store/browser` has the parts of the store that run in a b
|
|
|
420
477
|
- `ApiStorage`, the remote storage over the collection procedures of a server.
|
|
421
478
|
- `EventsChangeFeed`, the push feed of the remote changes.
|
|
422
479
|
- `TabCoordinator`: one leader tab does the sync of a `HybridStorage`, and the other tabs copy its changes.
|
|
480
|
+
- `TabGroup`: one leader tab does the sync of many collections, with one lock and one channel.
|
|
423
481
|
- All of `@procedures/store/indexeddb`: `IndexedDbStorage`, the IndexedDB stores and their fallbacks in memory.
|
|
424
482
|
|
|
425
483
|
No module of the entry imports a module of Node. The modules import `@procedures/client/browser` and not the root of the client, because the root imports `http` and `ws`. The test `src/browser.test.ts` examines the imports of each module of the entry. The entry is in `client.browser` of `package.json`, so the CI check of the browser entries (`scripts/check-browser-entries.mjs`) bundles it for a browser too.
|