@procedures/store 0.14.1 → 0.15.1

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 CHANGED
@@ -1,5 +1,55 @@
1
1
  # @procedures/store
2
2
 
3
+ ## 0.15.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [c78da2d]
8
+ - Updated dependencies [d99b2d5]
9
+ - Updated dependencies [534e4ea]
10
+ - Updated dependencies [44fdd60]
11
+ - @procedures/client@0.15.1
12
+ - @procedures/collections@0.15.1
13
+
14
+ ## 0.15.0
15
+
16
+ ### Minor Changes
17
+
18
+ - c7198ca: Breaking (roadmap CLI-16, decision D11): the collection writes take an optional expected version. This is its own MINOR of the client group (D13).
19
+
20
+ New:
21
+
22
+ - `collections.<name>.set` and `delete` take `expectedVersion`, and each item of `setBatch` takes `expectedVersion`. The value is the version of the current item that the write expects. `null` expects no item.
23
+ - A conflict changes nothing. The call fails with the new protocol code `VERSION_CONFLICT` (HTTP 409, not retryable). The metadata key `conflicts` (and the field `conflicts` of the HTTP body) has the current item and its version for each id.
24
+ - `StorageItemWriteOptions.expectedVersion` (`set`, `delete`) and `StorageBatchWriteOptions.expectedVersions` (`setBatch`) in both copies of `CollectionStorage`. `VersionConflictError`, `versionConflictsOf()` and the other helpers come from `@procedures/client/query`, `@procedures/client` and `@procedures/collections`.
25
+ - The option `versionOf` of `InMemoryStorage`, `SqliteStorage` and `MongoDocumentStorage` gives the version of an item. The test and the write are one step: synchronous in memory, one transaction in SQLite, the transaction or a compare-and-set on `_seq` in MongoDB. `MongoStorage` refuses a precondition.
26
+ - `ApiStorage` sends the expected versions to the collection procedures, and gives a `VERSION_CONFLICT` back as a `VersionConflictError`.
27
+ - `HybridStorage` sends the base version of each queued write as its expected version. A conflict of the remote goes to `conflictResolution`, as a conflict of a pull. The new option `sendBaseVersions: false` stops it.
28
+ - `ClientError.metadata` gives the metadata of the error response.
29
+ - The contract suite `@procedures/collections/testing` has the cases of the preconditions, and the option `createWithVersions`.
30
+
31
+ The changes of behavior:
32
+
33
+ - A storage must test a precondition, or refuse the write. A storage without `versionOf` refuses a write with an expected version with a `TypeError` whose code is `PRECONDITION_NOT_SUPPORTED`. The contract suite tests it for each storage.
34
+ - A `HybridStorage` with `versionOf` now sends the base version of each queued write. A queued write over a change of another writer got through before. Now the remote refuses it with `VERSION_CONFLICT`, and `conflictResolution` decides: `remote` drops the queued write, `merge` sends the merged item, `local` sends the write again over the current version, and `error` throws `ConflictError` and keeps the queue. The remote must test the precondition: a server storage without `versionOf` refuses each push.
35
+ - `ApiStorage`: a write without an expected version writes as before, and gets no `VERSION_CONFLICT`. Only a caller that gives an expected version, or a `HybridStorage` with `versionOf` over it, can get one.
36
+ - `HybridStorage` with `versionOf`: a queued write that won before can now get the conflict path. The opt-out is `new HybridStorage(remote, { versionOf, sendBaseVersions: false })`: the push sends no expected version, and the last write wins, as before. The default is the new behavior.
37
+ - A write without an expected version writes as before: the last write wins.
38
+
39
+ Migration:
40
+
41
+ - **Site.** No change. The site does not write collections.
42
+ - **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`.
43
+ - **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.
44
+ - **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.
45
+ - **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.
46
+
47
+ ### Patch Changes
48
+
49
+ - Updated dependencies [c7198ca]
50
+ - @procedures/client@0.15.0
51
+ - @procedures/collections@0.15.0
52
+
3
53
  ## 0.14.1
4
54
 
5
55
  ### Patch Changes
package/README.md CHANGED
@@ -94,10 +94,13 @@ The contract suite of `@procedures/collections/testing` tests `ApiStorage`, and
94
94
  - **The queries.** `query(selector, { sort, skip, limit })` gives the result of `queryEntries()` of `@procedures/collections`. `ApiStorage.query()` sends the JSON selector to the procedure `collections.<name>.find`, so the server applies it.
95
95
  - **The patches.** `ApiStorage.patch(id, fields)` sends a patch to the procedure `collections.<name>.patch`. The server changes the item in one step. A patch is a JSON Merge Patch, or the operations `{ $set, $unset, $remove, $insert }`. An index of `$remove` or `$insert` is a position at the time of the patch. A client that works from an older read uses the version check of the collection, where the collection has one.
96
96
  - **The query policy.** The server applies the policy of its host to each `find`. The default policy does not permit `$regex`, and a `find` gets at most 1000 items.
97
+ - **The preconditions.** `set`, `delete` and `setBatch` take an expected version of the current item (roadmap CLI-16). `ApiStorage` sends it to the collection procedures, and the storage of the server tests it. A conflict rejects with `VersionConflictError`, with the current items. A storage that cannot test it rejects the write.
97
98
 
98
99
  Notes:
99
100
 
100
101
  - A custom `service` or a custom `get` operation gets the input `{ id }`, as before. Its `null` reads back as `undefined`.
102
+ - A custom `service` gets no expected version. `ApiStorage` rejects a write with one, so the precondition is not lost.
103
+ - `HybridStorage` gives the expected version of a caller to its local copy. The default local copy gets the option `versionOf` of the storage. In `write-through` mode, the remote write gets it too. In `remote-only` mode, only the remote tests it.
101
104
  - A server before the procedure `find` answers `NOT_FOUND`. Then `ApiStorage.query()` reads the entries and applies the query in the client, with the same result.
102
105
  - `ApiStorage.find(predicate)` reads each item, because a function cannot go over the network. Use `query()` with a selector to filter on the server.
103
106
  - `HybridStorage.query()` reads the local copy, or the remote in `remote-only` mode. Its option `queryPolicy` goes to the storage under it.
@@ -192,6 +195,21 @@ const users = new HybridStorage(new ApiStorage(client, { collection: "users" }),
192
195
  - Without versions, a pull reads each local item without a queued write. It compares the JSON of the local item and the remote item.
193
196
  - The option `pullInterval` starts a periodic pull in the modes `write-through` and `write-back`. `close()` stops it.
194
197
 
198
+ ### The base versions on the server
199
+
200
+ With `versionOf`, the push of a queued write sends its base version as the expected version (roadmap CLI-16, decision D11). The base version is the remote version of the id when the local write went in the queue. The server then refuses a write over a change of another writer.
201
+
202
+ - The remote answers `VERSION_CONFLICT` with the current item. The storage gives the conflict to `conflictResolution`, as a conflict of a pull.
203
+ - `remote`: the remote item replaces the local item, and the queued writes of the id go.
204
+ - `merge`: `mergeFn` gives the item. A new queued write sends it, with the current version as its base.
205
+ - `local`: the current version becomes the base, and the push sends the queued write again. It replaces the remote item.
206
+ - `error`: the push throws `ConflictError` with the ids. The queue and the remote keep their items, and the next push tries again.
207
+ - A remote item that is the value of the queued write is not a conflict. An earlier send reached the remote, and its answer was lost.
208
+ - An id without a known base version gets no expected version. Its queued write wins, as before.
209
+ - The remote must test the precondition: an `ApiStorage` of the collection procedures over a storage with the same `versionOf`. A remote that cannot test it refuses each push.
210
+ - The option `sendBaseVersions: false` stops the expected versions. Then a queued write replaces the remote item, as before CLI-16.
211
+ - A write of the mode `write-through` goes to the remote directly. It sends only the expected version of its caller.
212
+
195
213
  ### The clock, the backoff and the status
196
214
 
197
215
  The timers and the retries run on a clock: `Clock` of `@mark1russell7/service/clock`. A test gives `fakeClock` of `@mark1russell7/service/testing`, and the times are then exact.