@procedures/store 0.47.1 → 0.48.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 (44) hide show
  1. package/CHANGELOG.md +41 -13
  2. package/README.md +7 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/browser.d.ts +1 -1
  5. package/dist/browser.d.ts.map +1 -1
  6. package/dist/browser.js +1 -1
  7. package/dist/browser.js.map +1 -1
  8. package/dist/indexeddb/document-storage.d.ts.map +1 -1
  9. package/dist/indexeddb/document-storage.js.map +1 -1
  10. package/dist/indexeddb/idb.d.ts +2 -12
  11. package/dist/indexeddb/idb.d.ts.map +1 -1
  12. package/dist/indexeddb/idb.js +7 -12
  13. package/dist/indexeddb/idb.js.map +1 -1
  14. package/dist/program-store/collection.d.ts.map +1 -1
  15. package/dist/program-store/collection.js.map +1 -1
  16. package/dist/program-store/file.d.ts.map +1 -1
  17. package/dist/program-store/file.js.map +1 -1
  18. package/dist/storage/factory.d.ts +1 -1
  19. package/dist/storage/feed.d.ts +3 -3
  20. package/dist/storage/feed.js +1 -1
  21. package/dist/storage/file-queue.d.ts.map +1 -1
  22. package/dist/storage/file-queue.js.map +1 -1
  23. package/dist/storage/hybrid.d.ts +36 -20
  24. package/dist/storage/hybrid.d.ts.map +1 -1
  25. package/dist/storage/hybrid.js +49 -27
  26. package/dist/storage/hybrid.js.map +1 -1
  27. package/dist/storage/index.d.ts +1 -1
  28. package/dist/storage/index.d.ts.map +1 -1
  29. package/dist/storage/index.js +1 -1
  30. package/dist/storage/index.js.map +1 -1
  31. package/dist/storage/types.d.ts +1 -1
  32. package/dist/storage/types.d.ts.map +1 -1
  33. package/package.json +5 -5
  34. package/src/browser.ts +1 -0
  35. package/src/indexeddb/document-storage.ts +18 -6
  36. package/src/indexeddb/idb.ts +27 -12
  37. package/src/program-store/collection.ts +5 -1
  38. package/src/program-store/file.ts +5 -1
  39. package/src/storage/factory.ts +1 -1
  40. package/src/storage/feed.ts +3 -3
  41. package/src/storage/file-queue.ts +5 -1
  42. package/src/storage/hybrid.ts +96 -43
  43. package/src/storage/index.ts +1 -0
  44. package/src/storage/types.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @procedures/store
2
2
 
3
+ ## 0.48.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 17a18ec: The source now uses only syntax that a type stripper erases, so the setting `erasableSyntaxOnly` of `@mark1russell7/cue` 2.1.1 stays on. Each enum is now a constant object with a type of the same name. Each parameter property is now a field and an assignment in the constructor.
8
+
9
+ **Breaking.** MINOR before 1.0.0: `ErrorCategory`, `ErrorSeverity` and `WebSocketState` are now constant objects. Their names and values stay the same, and each member stays usable as a type. A member is no longer a constant expression: `enum E { A = ErrorSeverity.WARNING }` gives TS18033, and a `const enum` gives TS2474. Also, a string with the value of a member now fits the type of the set.
10
+
11
+ **Migration.** In an enum initializer, write the value in place of the member, for example `"warning"`, or use a constant object. Other uses need no change. The site and the known apps need no change.
12
+ - c529a36: `HybridStorage` has a new mode `read-only`. It refuses each write with `ReadOnlyError` at once, with no change of the local copy, the queue or the remote. Reads, pulls and the feed work as in `write-through`. `HybridMode` has a new value, so an exhaustive `switch` on it needs a new case.
13
+ - Updated dependencies [17a18ec]
14
+ - Updated dependencies [06088f9]
15
+ - Updated dependencies [71f9c6e]
16
+ - Updated dependencies [c839add]
17
+ - @procedures/client@0.48.0
18
+ - @procedures/collections@0.48.0
19
+
20
+ ## 0.47.2
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies [0650616]
25
+ - Updated dependencies [2684531]
26
+ - Updated dependencies [ae1e3fd]
27
+ - Updated dependencies [f1ece74]
28
+ - @procedures/client@0.47.2
29
+ - @procedures/collections@0.47.2
30
+
3
31
  ## 0.47.1
4
32
 
5
33
  ### Patch Changes
@@ -111,9 +139,9 @@
111
139
  Migration steps:
112
140
 
113
141
  - The site: no step. It does not catch a `TIMEOUT` of a `HybridStorage` write.
114
- - MiniMongo: no step, unless code catches a `TIMEOUT` of a `write-through` write to show an undo. That code gets `PendingWriteError`, with the `TIMEOUT` as its `cause`.
115
- - Graph: the own `isUnavailable` that keeps `TIMEOUT` in the queue is not necessary at this time. Remove it, or keep it: the result is the same.
116
- - jarvis: no step. To keep the old behavior, pass `isUnavailable: (error) => error?.retryable === true || error?.code === undefined`.
142
+ - An app: no step, unless code catches a `TIMEOUT` of a `write-through` write to show an undo. That code gets `PendingWriteError`, with the `TIMEOUT` as its `cause`.
143
+ - An app: the own `isUnavailable` that keeps `TIMEOUT` in the queue is not necessary at this time. Remove it, or keep it: the result is the same.
144
+ - An app: no step. To keep the old behavior, pass `isUnavailable: (error) => error?.retryable === true || error?.code === undefined`.
117
145
 
118
146
  ### Patch Changes
119
147
 
@@ -216,8 +244,8 @@
216
244
  - A host whose `expose`, policy or limit pattern names such a path in text uses the encoded form, for example `collections.e2e%2Etabs.*`.
217
245
  - An `mcpExclude` entry, an `expose` deny entry or a limit entry in the old raw form, for example `collections.e2e.tabs.drop`, does not match. The check compares the encoded key, so the procedure becomes visible, or the limit does not apply. Write each such entry in the encoded form, for example `collections.e2e%2Etabs.drop`.
218
246
  - A subscriber of a dotted collection uses `collectionChangeTopic(name)`. The raw topic `collections.a.b` is the topic of the partition `b` of `a`.
219
- - Graph: read the collection of a topic with `collectionTopicPartition(topic)`, not with a pattern, for example `/^collections\.([^.]+)\./`.
220
- - The site, MiniMongo and jarvis: no change, if their collection names have no `.` and no `%`.
247
+ - An app: read the collection of a topic with `collectionTopicPartition(topic)`, not with a pattern, for example `/^collections\.([^.]+)\./`.
248
+ - Other apps: no change, if their collection names have no `.` and no `%`.
221
249
 
222
250
  ### Patch Changes
223
251
 
@@ -234,7 +262,7 @@
234
262
 
235
263
  Migration:
236
264
 
237
- - The site, MiniMongo, Graph and jarvis: no change at this time. The deprecated names work until the next MINOR.
265
+ - Apps: no change at this time. The deprecated names work until the next MINOR.
238
266
  - Before the next MINOR, replace `zodAdapter<T>(schema)` with `schema`. To keep the check of a schema against a type, write `const schema: z.ZodType<T> = ...`.
239
267
  - Before the next MINOR, replace `typedAnySchema<T>()` and `outputSchema<T>()` with `typed<T>()`.
240
268
  - A parse-only object (`{ parse }`) in `zodAdapter` becomes a Standard Schema with a `~standard` property.
@@ -371,7 +399,7 @@
371
399
 
372
400
  ### Patch Changes
373
401
 
374
- - b7a5167: Added: the option `aroundRemoteApply` of `HybridStorage` puts each apply of the remote changes inside a hook (graph W8). Graph uses it to start a span of a trace with the `traceparent` that the server sends with a change.
402
+ - b7a5167: Added: the option `aroundRemoteApply` of `HybridStorage` puts each apply of the remote changes inside a hook. An app uses it to start a span of a trace with the `traceparent` that the server sends with a change.
375
403
 
376
404
  - `aroundRemoteApply(hint, apply, context)` starts `apply` one time and gives back its promise. `context.cause` is `"delta"`, `"hint"`, `"gap"` or `"pull"`. `context.hints` has the hints of the apply.
377
405
  - A delta and a hint of the feed give their hint. A gap and a pull of the storage (the timer of `pullInterval`, `syncFromRemote()`, `sync()` and the first sync) give the hint `undefined`.
@@ -626,9 +654,9 @@
626
654
  Migration:
627
655
 
628
656
  - **Site.** No change. The site does not write collections.
629
- - **MiniMongo.** No change for a storage or a `HybridStorage` without `versionOf`. A `HybridStorage` with `versionOf` over `ApiStorage` needs a server storage with the same `versionOf` (for example `new InMemoryStorage({ versionOf })`), or the option `sendBaseVersions: false`.
630
- - **Graph.** Graph keeps last-write-wins first (D11): it gives no expected version, so nothing changes. `GraphDocsStorage` must refuse a write with an expected version until it tests one. Add the guard to its `set`, `delete` and `setBatch`, and run the contract suite: its first case tests the refusal. To adopt the version later, stamp a version field on the server, give the storage a `versionOf` that tests it in the write, and give the same `versionOf` to the `HybridStorage` of the client. Then add `createWithVersions` to the contract run.
631
- - **jarvis.** No change: jarvis gives no expected version. A storage of jarvis that implements `CollectionStorage` must refuse a write with an expected version, as for Graph.
657
+ - **An app with `HybridStorage`.** No change for a storage or a `HybridStorage` without `versionOf`. A `HybridStorage` with `versionOf` over `ApiStorage` needs a server storage with the same `versionOf` (for example `new InMemoryStorage({ versionOf })`), or the option `sendBaseVersions: false`.
658
+ - **An app with last-write-wins.** It gives no expected version, so nothing changes. Its own storage must refuse a write with an expected version until it tests one. Add the guard to its `set`, `delete` and `setBatch`, and run the contract suite: its first case tests the refusal. To adopt the version later, stamp a version field on the server, give the storage a `versionOf` that tests it in the write, and give the same `versionOf` to the `HybridStorage` of the client. Then add `createWithVersions` to the contract run.
659
+ - **An app without versions.** No change: it gives no expected version. A storage of the app that implements `CollectionStorage` must refuse a write with an expected version, as above.
632
660
  - **Other storages.** Each class that implements `CollectionStorage` reads the new options of `set`, `delete` and `setBatch`. It tests them in the write, or it rejects the write with `preconditionNotSupported()`. It never ignores them.
633
661
 
634
662
  ### Patch Changes
@@ -795,14 +823,14 @@
795
823
  - The option `origin` of `HybridStorage` is the tag of the storage (a random `hybrid-…` tag by default). Each write of the storage to the local copy and to the remote has it. The write methods of `HybridStorage` and `ApiStorage` take `{ origin }`.
796
824
  - `capture: "observe"`: the storage observes the local copy and queues each change with another origin. Thus an application can write to the local copy directly, for example with the synchronous writes of a SignalDB collection. In `write-through` and `remote-only` mode, the storage sends a captured change at once.
797
825
  - The option `syncState` takes a `SyncStateStore`: `MemorySyncStateStore`, or `IndexedDbSyncStateStore` of `@procedures/store/indexeddb`. The store keeps the cursor and the base versions. A new storage with the same store and a durable local copy asks only for the changes since the cursor, and it finds the conflicts of the base versions. A failure of the store goes to the status (`operation: "store"`, `SyncStateError`), and the storage goes on.
798
- - The option `feed` takes a `RemoteChangeFeed`. A delta whose `since` is the cursor applies without a read of the remote. A gap, a lost event, a new subscription or another delta makes one pull. A delta that comes during a pull applies after the pull, so the pull does not erase it (the LiveLog rule of Graph).
826
+ - The option `feed` takes a `RemoteChangeFeed`. A delta whose `since` is the cursor applies without a read of the remote. A gap, a lost event, a new subscription or another delta makes one pull. A delta that comes during a pull applies after the pull, so the pull does not erase it.
799
827
  - In `write-through` mode, a pull or a delta does not write over an own write that waits for the remote, a set or a delete. The storage keeps the remote value of the id, and after the write it reads the id again: then the rules of a pull decide. A read of a missing id does not bring back an item whose own delete waits.
800
- - With `versionOf`, the storage keeps the versions of its own writes until their echoes come (option `echoWindow`, 30 seconds by default). The echo of an older own write does not write over a newer local write (the EchoLedger rule of Graph).
828
+ - With `versionOf`, the storage keeps the versions of its own writes until their echoes come (option `echoWindow`, 30 seconds by default). The echo of an older own write does not write over a newer local write.
801
829
  - `EventsChangeFeed` of `@procedures/store` is the feed of the change events. Its source is a `Client`, a `WebSocketTransport` or another transport. It treats the events as hints: a gap of the numbers, a lost event, a hint and each confirmation of the subscription call `onGap()`.
802
830
  - The core collection procedures publish `CollectionChangeEvent` `{ since, version, changed, deleted, origin }` on `ctx.bus`, under the topic `collections.<name>` (`collectionChangeTopic()`), while a subscriber listens. The changes come from `getChangesSince()` of the storage, and the events of one storage make a chain of versions. The option `changeEvents` of `createCollectionProcedures()` turns them off, or gives the topic of each call: the partition of CLI-17 goes there.
803
831
  - A value reaches a subscriber only through the checks of a read. By default, an event has the ids and the versions only, and `EventsChangeFeed` gives it to `onHint()`: `HybridStorage` reads the values with `changes`, and a hint with only deletes applies without a read. With `changeEvents: { values: true }`, an event also has `values`: each row `{ id, value }` passes the output filter of the host (`admitItem`) for each subscriber, and a refused id goes in `withheld`, so the storage pulls it.
804
832
  - `EventPublisher` refuses a topic whose reads the capability of the call does not permit (`FORBIDDEN`, `effects-exceeded`). Its new option `reads` gives the reads of a topic. The default, `collectionTopicReads()`, gives `collection:<name>` for `collections.<name>` and `collections.<name>.<partition>`. `ctx.admit` gives a procedure the output filter of its host for the data that it sends later.
805
- - The syncs of one `HybridStorage` run one at a time: the deltas and the hints of the feed, the pulls, the pushes, the reads after an own write and the mode switches. A remote delta and a full sync never run at the same time (Graph R18).
833
+ - The syncs of one `HybridStorage` run one at a time: the deltas and the hints of the feed, the pulls, the pushes, the reads after an own write and the mode switches. A remote delta and a full sync never run at the same time.
806
834
  - The write inputs of the collection procedures take an optional `origin` (1 to 256 characters). The procedure gives it to the storage and to the event. A server before this change ignores the field.
807
835
  - `Client.subscribe()` and `Client.events()` subscribe to the topics of the bus of the server through the middleware and the context of the client. A subscription has no deadline: the timeouts of the client do not apply to it, and only `metadata: { timeout }` in its options gives one.
808
836
  - New exports: `collectionChangeTopic`, `CollectionChangeEvent` and `CollectionChangeEventsOptions` from `@procedures/client`, `./browser` and `./procedures`. `EventsChangeFeed`, `MemorySyncStateStore`, `SyncStateError` and the types `RemoteChangeFeed`, `RemoteChangeListener`, `RemoteChanges`, `EventSubscriber`, `ChangeEventSource`, `EventsChangeFeedOptions`, `SyncState`, `SyncStateChange` and `SyncStateStore` from `@procedures/store`. `IndexedDbSyncStateStore` and `MemorySyncStateStore` from `@procedures/store/indexeddb`.
package/README.md CHANGED
@@ -205,7 +205,7 @@ const users = new HybridStorage(new ApiStorage(client, { collection: "users" }),
205
205
 
206
206
  ## The modes of `HybridStorage`
207
207
 
208
- `HybridStorage` keeps a local copy of a remote collection storage. It has four modes, and `setMode()` changes the mode while the storage operates.
208
+ `HybridStorage` keeps a local copy of a remote collection storage. It has five modes, and `setMode()` changes the mode while the storage operates.
209
209
 
210
210
  | Mode | Reads | Writes | Remote calls |
211
211
  |---|---|---|---|
@@ -213,6 +213,7 @@ const users = new HybridStorage(new ApiStorage(client, { collection: "users" }),
213
213
  | `write-through` | The local copy, and the remote on a miss | The local copy, then the remote | Yes |
214
214
  | `write-back` | The local copy, and the remote on a miss | The local copy and the queue. A timer sends the queue. | Yes |
215
215
  | `remote-only` | The remote | The remote | Yes. The storage keeps no local copy. |
216
+ | `read-only` | The local copy, and the remote on a miss | Refused with `ReadOnlyError` | Reads and pulls only |
216
217
 
217
218
  ```ts
218
219
  import { minutes } from "@procedures/client";
@@ -245,6 +246,8 @@ await users.setMode("write-through"); // The switch sends the queue, then syncs
245
246
  - In the modes `local-only` and `write-back`, each write goes in the queue.
246
247
  - In the mode `write-through`, a write that the remote cannot take goes in the queue, and the write throws `PendingWriteError`.
247
248
  - By default, a write that the remote cannot take is an error with `retryable: true`, an error without a `code`, or a `TIMEOUT`. The option `isUnavailable` changes it.
249
+ - The mode `read-only` refuses each write with `ReadOnlyError` at once. The local copy, the queue and the remote do not change.
250
+ - A switch to `read-only` sends the queue first, and a failed send keeps the old mode. A queue that a store loads or that `capture` adds in this mode waits for a mode that writes.
248
251
  - A full queue refuses the next write with `QueueFullError`. The limit is `maxOfflineOps`, 1000 by default.
249
252
  - A push keeps a write that the remote cannot take at this time (`isUnavailable` gives true). This write and the later writes stay in the queue.
250
253
  - A queued write that the remote refuses leaves the queue, and the push goes on with the next write.
@@ -293,7 +296,7 @@ const users = new HybridStorage(new ApiStorage(client, { collection: "users" }),
293
296
  - A pull reads a local item only when the result depends on it. A pull does not read the item of a queued write that wins.
294
297
  - With versions, the storage knows the version of each local item that it wrote or read. Then a pull reads a local item only for a conflict, or when the version is not known.
295
298
  - Without versions, a pull reads each local item without a queued write. It compares the JSON of the local item and the remote item.
296
- - The option `pullInterval` starts a periodic pull in the modes `write-through` and `write-back`. `close()` stops it.
299
+ - The option `pullInterval` starts a periodic pull in the modes `write-through`, `write-back` and `read-only`. `close()` stops it.
297
300
 
298
301
  ### The base versions on the server
299
302
 
@@ -356,7 +359,7 @@ const stop = users.onStatus((status) => {
356
359
  - The option `syncState` keeps the cursor and the base versions. A new storage with the same store asks only for the changes since the cursor.
357
360
  - `MemorySyncStateStore` keeps the state in memory. `IndexedDbSyncStateStore` of `@procedures/store/indexeddb` keeps it in a browser.
358
361
  - The storage uses a loaded cursor only with the option `local` and a local copy that has items. Give the state store with a durable local copy.
359
- - The option `feed` takes a push feed of the remote changes, for example `EventsChangeFeed`. The storage subscribes in the modes `write-through` and `write-back`.
362
+ - The option `feed` takes a push feed of the remote changes, for example `EventsChangeFeed`. The storage subscribes in the modes `write-through`, `read-only` and `write-back`.
360
363
  - A delta whose `since` is the cursor of the storage applies without a read of the remote.
361
364
  - A gap, a lost event, a new subscription or another delta makes the storage pull the changes since its cursor.
362
365
  - A delta that comes during a pull applies after the pull. Thus a pull does not erase a change that came during it.
@@ -393,7 +396,7 @@ const users = new HybridStorage(new ApiStorage(client, { collection: "users" }),
393
396
 
394
397
  ### The hook around a remote apply
395
398
 
396
- The option `aroundRemoteApply` puts each apply of the remote changes inside a hook (graph W8). For example, the hook starts a span of a trace with the `traceparent` of the server.
399
+ The option `aroundRemoteApply` puts each apply of the remote changes inside a hook. For example, the hook starts a span of a trace with the `traceparent` of the server.
397
400
 
398
401
  - The hook gets `hint`, `apply` and `context`. It starts `apply` one time and gives back its promise.
399
402
  - `context.cause` is `"delta"`, `"hint"`, `"gap"` or `"pull"`. `context.hints` has the hints of the apply.