@procedures/store 0.17.3 → 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.
- package/CHANGELOG.md +20 -0
- package/README.md +58 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.d.ts +4 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -0
- package/dist/browser.js.map +1 -1
- package/dist/storage/hybrid.d.ts +9 -0
- package/dist/storage/hybrid.d.ts.map +1 -1
- package/dist/storage/hybrid.js +14 -0
- package/dist/storage/hybrid.js.map +1 -1
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/tab-coordinator.d.ts +284 -0
- package/dist/storage/tab-coordinator.d.ts.map +1 -0
- package/dist/storage/tab-coordinator.js +584 -0
- package/dist/storage/tab-coordinator.js.map +1 -0
- package/package.json +5 -5
- package/src/browser.ts +11 -0
- package/src/storage/hybrid.ts +14 -0
- package/src/storage/index.ts +10 -0
- package/src/storage/tab-coordinator.ts +692 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @procedures/store
|
|
2
2
|
|
|
3
|
+
## 0.17.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b0ccc96: Added: `TabCoordinator`, so one leader tab of a browser does the sync of a `HybridStorage`, and the other tabs copy its changes (roadmap CLI-25). It is in `@procedures/store/browser` and in the package root.
|
|
8
|
+
|
|
9
|
+
- The election uses the Web Locks API. When the leader closes, or the browser closes its tab, the next tab becomes the leader.
|
|
10
|
+
- Only the leader makes the `HybridStorage`, with `capture: "observe"`. The followers make no remote call and start no timer.
|
|
11
|
+
- The tabs send the change batches of their local copies on a `BroadcastChannel`. `observe()` gives each batch of each tab one time, with its origin. A tab does not send again a batch that it got.
|
|
12
|
+
- The leader captures the batches of the followers, and its queue store keeps them. Only the leader writes to the shared queue store.
|
|
13
|
+
- A follower keeps each own batch until the leader acknowledges it. A new leader gets the batches that no leader acknowledged.
|
|
14
|
+
- The option `copy` is `"shared"` for one durable local copy of all tabs, or `"tab"` for a local copy in each tab, for example a SignalDB collection.
|
|
15
|
+
- Without the Web Locks API or `BroadcastChannel`, the tab does its own sync in the single mode.
|
|
16
|
+
|
|
17
|
+
Added: `HybridStorage.whenQueued()`. It resolves when the queue store holds the queue, and it sends nothing.
|
|
18
|
+
|
|
19
|
+
PATCH: new exports and a new method that are backward compatible, before 1.0.0.
|
|
20
|
+
- @procedures/client@0.17.4
|
|
21
|
+
- @procedures/collections@0.17.4
|
|
22
|
+
|
|
3
23
|
## 0.17.3
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -319,6 +319,63 @@ const users = new HybridStorage(new ApiStorage(client, { collection: "users" }),
|
|
|
319
319
|
- The server serves the events with the option `events` of `ProcedureServer`, for example `{ topics: ["collections.*"] }`. Without a subscriber, the procedures publish nothing.
|
|
320
320
|
- `ApiStorage` sends the `origin` of a write to the procedure. Thus the storage of the server and the event have it.
|
|
321
321
|
|
|
322
|
+
### The tabs of a browser
|
|
323
|
+
|
|
324
|
+
`TabCoordinator` of `@procedures/store/browser` lets one leader tab run the sync of a `HybridStorage` (roadmap CLI-25). The other tabs copy its changes. Without it, each tab pulls and pushes, and each tab writes over the queue of the other tabs.
|
|
325
|
+
|
|
326
|
+
- The election uses the Web Locks API. The tab that holds the lock of the coordinator is the leader.
|
|
327
|
+
- Only the leader makes the `HybridStorage`, with `capture: "observe"` on its local copy. The followers make no remote call and start no timer.
|
|
328
|
+
- The application writes to the local copy in each tab. A tab sends the change batches of its local copy to the other tabs on a `BroadcastChannel`.
|
|
329
|
+
- `observe()` gives the batches of this tab and of the other tabs, each batch one time, with the origin of each change.
|
|
330
|
+
- The leader also sends the changes of its sync. They have the origin `syncOrigin`, `"sync"` by default.
|
|
331
|
+
- A tab does not send again a batch of another tab.
|
|
332
|
+
- The leader captures the batches of the followers: its `HybridStorage` queues them and pushes them. Thus a write of a follower reaches the remote one time.
|
|
333
|
+
- Only the leader writes to the queue store and to the store of the sync state. Give the same `IndexedDbQueueStore` and `IndexedDbSyncStateStore` in each tab.
|
|
334
|
+
|
|
335
|
+
The option `copy` tells the kind of the local copy:
|
|
336
|
+
|
|
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
|
+
- `"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.
|
|
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
|
+
|
|
342
|
+
A new leader and a closed tab:
|
|
343
|
+
|
|
344
|
+
- A follower keeps each own batch until the leader acknowledges it. The leader acknowledges a batch when its queue store holds it.
|
|
345
|
+
- A new leader tells the other tabs. Each follower then sends the batches that no leader acknowledged. Thus a write during the change of the leader reaches the new leader.
|
|
346
|
+
- `close()` of the leader sends the queue, and then it releases the lock. The queue store keeps the operations that the remote did not take.
|
|
347
|
+
- When the browser closes the tab of the leader, the browser releases the lock. The next leader loads the shared queue store and pushes the queue.
|
|
348
|
+
- When the leader stops during a push, the next leader sends the operation again. A set or a delete gives the same result two times.
|
|
349
|
+
- The load of the queue writes the queued operations to the local copy again. Thus the observers can get them again, with the origin `syncOrigin`.
|
|
350
|
+
- `close()` of a follower stops its lock request. `close()` does not close the local copy, because the application owns it.
|
|
351
|
+
|
|
352
|
+
Without the Web Locks API or `BroadcastChannel`, the coordinator is in the single mode. The tab does its own sync, and it opens no channel. A browser has the Web Locks API only in a secure context, for example HTTPS or `localhost`.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { ApiStorage, IndexedDbQueueStore, IndexedDbStorage, IndexedDbSyncStateStore, TabCoordinator } from "@procedures/store/browser";
|
|
356
|
+
|
|
357
|
+
const local = new IndexedDbStorage<User>("users");
|
|
358
|
+
const users = new TabCoordinator<User>({
|
|
359
|
+
name: "users",
|
|
360
|
+
local,
|
|
361
|
+
copy: "shared",
|
|
362
|
+
remote: () => new ApiStorage(client, { collection: "users" }),
|
|
363
|
+
hybrid: {
|
|
364
|
+
mode: "write-back",
|
|
365
|
+
queueStore: new IndexedDbQueueStore("users-queue"),
|
|
366
|
+
syncState: new IndexedDbSyncStateStore("users-sync"),
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
users.observe((batch) => render(batch));
|
|
371
|
+
await local.set("1", { name: "Ada" });
|
|
372
|
+
users.syncStorage?.getStatus(); // In the leader only
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- `role` is `follower`, `leader`, `single` or `closed`. `onRole()` tells each change.
|
|
376
|
+
- 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.
|
|
377
|
+
- `HybridStorage.whenQueued()` resolves when the queue store holds the queue. The leader acknowledges the batches of the followers after it.
|
|
378
|
+
|
|
322
379
|
### The change of a storage in a server
|
|
323
380
|
|
|
324
381
|
`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.
|
|
@@ -341,6 +398,7 @@ The entry `@procedures/store/browser` has the parts of the store that run in a b
|
|
|
341
398
|
- `HybridStorage`, its errors and its types.
|
|
342
399
|
- `ApiStorage`, the remote storage over the collection procedures of a server.
|
|
343
400
|
- `EventsChangeFeed`, the push feed of the remote changes.
|
|
401
|
+
- `TabCoordinator`: one leader tab does the sync of a `HybridStorage`, and the other tabs copy its changes.
|
|
344
402
|
- All of `@procedures/store/indexeddb`: `IndexedDbStorage`, the IndexedDB stores and their fallbacks in memory.
|
|
345
403
|
|
|
346
404
|
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.
|