@procedures/client 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 +93 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +4 -0
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/server/handler.d.ts.map +1 -1
- package/dist/adapters/http/server/handler.js +8 -3
- package/dist/adapters/http/server/handler.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +5 -0
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/browser.d.ts +4 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -0
- package/dist/browser.js.map +1 -1
- package/dist/client/client.js +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +13 -0
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/middleware/rate-limit.d.ts +17 -0
- package/dist/client/middleware/rate-limit.d.ts.map +1 -1
- package/dist/client/middleware/rate-limit.js +94 -39
- package/dist/client/middleware/rate-limit.js.map +1 -1
- package/dist/client/types.d.ts +6 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +7 -1
- package/dist/client/types.js.map +1 -1
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/events/publisher.d.ts +46 -1
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +95 -5
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/recheck.d.ts +84 -0
- package/dist/events/recheck.d.ts.map +1 -0
- package/dist/events/recheck.js +135 -0
- package/dist/events/recheck.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +3 -1
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +2 -0
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/precondition.d.ts +111 -0
- package/dist/procedures/collection/precondition.d.ts.map +1 -0
- package/dist/procedures/collection/precondition.js +153 -0
- package/dist/procedures/collection/precondition.js.map +1 -0
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +16 -3
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/query.d.ts +2 -0
- package/dist/procedures/collection/query.d.ts.map +1 -1
- package/dist/procedures/collection/query.js +3 -0
- package/dist/procedures/collection/query.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +16 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +26 -5
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +34 -6
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/collection/storage.js +3 -0
- package/dist/procedures/collection/storage.js.map +1 -1
- package/dist/procedures/index.d.ts +2 -2
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +1 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -1
- package/dist/server/host-policy.d.ts +17 -0
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +7 -0
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/limits.d.ts +122 -0
- package/dist/server/limits.d.ts.map +1 -0
- package/dist/server/limits.js +153 -0
- package/dist/server/limits.js.map +1 -0
- package/dist/server/procedure-server.d.ts +31 -1
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +43 -2
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +5 -1
- package/dist/server/request-auth.d.ts.map +1 -1
- package/dist/server/request-auth.js +19 -2
- package/dist/server/request-auth.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/http/client/transport.ts +3 -0
- package/src/adapters/http/server/handler.ts +8 -3
- package/src/adapters/local/client/transport.ts +4 -0
- package/src/browser.ts +20 -0
- package/src/client/client.ts +1 -1
- package/src/client/errors/registry.ts +14 -0
- package/src/client/middleware/rate-limit.ts +116 -41
- package/src/client/types.ts +8 -0
- package/src/events/index.ts +1 -0
- package/src/events/publisher.ts +116 -6
- package/src/events/recheck.ts +182 -0
- package/src/procedures/collection/index.ts +17 -0
- package/src/procedures/collection/precondition.ts +180 -0
- package/src/procedures/collection/procedures.ts +17 -4
- package/src/procedures/collection/query.ts +16 -0
- package/src/procedures/collection/schemas.ts +43 -6
- package/src/procedures/collection/storage.ts +36 -6
- package/src/procedures/index.ts +15 -0
- package/src/protocol.ts +5 -1
- package/src/server/host-policy.ts +22 -0
- package/src/server/limits.ts +254 -0
- package/src/server/procedure-server.ts +59 -3
- package/src/server/request-auth.ts +19 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
# @procedures/client
|
|
2
2
|
|
|
3
|
+
## 0.15.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c78da2d: The procedures server can limit the calls of each principal, tenant or route (server stack CLI-40). This change is additive. Without the new option, nothing changes. Thus it is a PATCH of the client group (decision D13).
|
|
8
|
+
|
|
9
|
+
The new behavior:
|
|
10
|
+
|
|
11
|
+
- `hostPolicy()` and `ProcedureServer` of `@procedures/client` have the new option `limits`. Each limit has a name, a key over the call, the calls that it covers, a limit policy, a store and a bound on its keys.
|
|
12
|
+
- The key is the principal, a claim of the principal (`{ claim: "tenant" }`), the route, a list of these, or a function of the call. A call without a value of its key is outside the limit.
|
|
13
|
+
- The option `covers` takes `{ writes: true }` for only the calls that can change data, `{ routes: [...] }` for some procedures, or a function of the call.
|
|
14
|
+
- Each limit is a keyed limiter of `@mark1russell7/service/limiter` (SYS-28) on the clock of the server. The option `limitStores` gives the stores that the replicas share.
|
|
15
|
+
- The server takes one unit of each limit that covers a call, after the authentication and before the run. This is one step for each face: HTTP, WebSocket, MCP and the calls in the process.
|
|
16
|
+
- A call above a limit gets the new error code `RATE_LIMITED` (retryable). Its metadata has the wait in `retry-after` (milliseconds) and the cause in `reason` (`limit`, `cap` or `store`). The call does not run.
|
|
17
|
+
- HTTP gives 429 with `Retry-After` in whole seconds. WebSocket gives the same code and metadata. MCP gives the code, `reason` and `retryAfterMs` in `_meta`.
|
|
18
|
+
- A circuit breaker does not count `RATE_LIMITED`, because the server is healthy.
|
|
19
|
+
- `createProcedureHost()` of `@procedures/peer` declares each limiter on its service with `svc.limiter()`. Thus `describe()` and `lint()` see it. `host.limiters` gives the limiters, and `ProcedureServer.limiters()` gives them too.
|
|
20
|
+
- The rate limit middleware of the client (`createRateLimitMiddleware`) uses the same keyed limiter. Its bucket, its queue and its statistics do not change. The new option `clock` gives the clock of the bucket and of the queue. The default is `Date.now()` and `setTimeout`, as before.
|
|
21
|
+
- The `Retry-After` header of the HTTP face rounds the wait up to whole seconds with the rule of `@mark1russell7/service`. A wait above a whole second only by an error of floating point does not add a second.
|
|
22
|
+
- The client packages use `@mark1russell7/service` 0.5.3 and `@mark1russell7/algebra` 0.7.2.
|
|
23
|
+
|
|
24
|
+
An example for the writes of each tenant:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
hostPolicy({
|
|
28
|
+
principalOf,
|
|
29
|
+
limits: [
|
|
30
|
+
{
|
|
31
|
+
name: "graph.writes",
|
|
32
|
+
key: { claim: "tenant" },
|
|
33
|
+
covers: { writes: true },
|
|
34
|
+
policy: { type: "sliding-window", limit: { value: 120, unit: "count" }, window: { value: 1, unit: "min" } },
|
|
35
|
+
store: { kind: "local" },
|
|
36
|
+
evict: { maxKeys: { value: 10000, unit: "count" } },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
- d99b2d5: New: an open subscription can get the checks of its admission again (roadmap CLI-44). Before, `EventPublisher` examined the rights of a subscriber only at the start of `events.subscribe`. A subscription of a collection change feed thus continued after the app revoked a right.
|
|
42
|
+
|
|
43
|
+
`rightsChanged(change)` of `EventPublisher` starts a check of each open subscription that the change touches. The change is the id of a principal, a test of the principal, or no value for each subscription. The new option `recheck` of `EventPublisherOptions` has `maxAge`, a check after a time on the clock of the publisher. It also has `rightsChanges`, a source of the changes of the app.
|
|
44
|
+
|
|
45
|
+
The check is the same function as at the start: the topic rule, the reads, `allow` and the partition. While it operates, the events of the subscription wait. A refusal ends the subscription with its error, for example `FORBIDDEN`. The events that waited do not reach the subscriber. The new option `clock` of `EventPublisherOptions` is the clock of `maxAge`, and a `ProcedureServer` gives its own clock. Without a call of `rightsChanged()` and without the option `recheck`, nothing changes.
|
|
46
|
+
- 534e4ea: Fixed: `bearerOf` also reads the scheme of an `Authorization` header in another case (CLI-60). RFC 7235 makes the scheme case-insensitive, with one or more spaces (`1*SP`) before the token. Before, `bearerOf` read only a header that starts with `Bearer ` and one space, so a correct `bearer <token>` got 401.
|
|
47
|
+
|
|
48
|
+
Each header that `bearerOf` read before gives the same token. A correct header in another case passes too, also with more spaces before the token.
|
|
49
|
+
|
|
50
|
+
- A header that starts with `Bearer ` gives the rest of the header, as before.
|
|
51
|
+
- A header with the scheme in another case, for example `bearer` or `BEARER`, gives the token after one or more spaces. Spaces or tabs can come after the token. The token has no whitespace (`token68`).
|
|
52
|
+
- A tab between the scheme and the token is not `1*SP`, so it gives no token.
|
|
53
|
+
- `Basic x`, `Bearer` with no space and an empty header give no token, as before.
|
|
54
|
+
- The token comparison (`tokenMatches`) does not change.
|
|
55
|
+
|
|
56
|
+
Each user of `bearerOf` gets the change. These users are `credentialsOf`, `requestCredentials`, `procedureHandler` and the WebSocket server. The WebSocket admission of `createProcedureHost()`, `guardMcpRequest` and the credentials of the MCP face also get it.
|
|
57
|
+
- 44fdd60: `@types/ws` is a dependency of `@procedures/client`, not a development dependency (CLI-61). The published declarations of the WebSocket transports name the types of `ws`, which is a dependency. Before, a project that checks the declarations of its libraries (`skipLibCheck: false`) and had no `@types/ws` failed to compile. Now each install gets the types with `ws`.
|
|
58
|
+
|
|
59
|
+
The types of `express` stay an optional part, as `express` is an optional peer dependency. A project that checks library declarations and imports the root of the client needs `@types/express` until the next breaking release of the server stack (CLI-6 step 2), which takes the Express types out of the root.
|
|
60
|
+
|
|
61
|
+
PATCH: a dependency of the package changes, and no API changes.
|
|
62
|
+
|
|
63
|
+
## 0.15.0
|
|
64
|
+
|
|
65
|
+
### Minor Changes
|
|
66
|
+
|
|
67
|
+
- 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).
|
|
68
|
+
|
|
69
|
+
New:
|
|
70
|
+
|
|
71
|
+
- `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.
|
|
72
|
+
- 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.
|
|
73
|
+
- `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`.
|
|
74
|
+
- 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.
|
|
75
|
+
- `ApiStorage` sends the expected versions to the collection procedures, and gives a `VERSION_CONFLICT` back as a `VersionConflictError`.
|
|
76
|
+
- `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.
|
|
77
|
+
- `ClientError.metadata` gives the metadata of the error response.
|
|
78
|
+
- The contract suite `@procedures/collections/testing` has the cases of the preconditions, and the option `createWithVersions`.
|
|
79
|
+
|
|
80
|
+
The changes of behavior:
|
|
81
|
+
|
|
82
|
+
- 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.
|
|
83
|
+
- 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.
|
|
84
|
+
- `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.
|
|
85
|
+
- `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.
|
|
86
|
+
- A write without an expected version writes as before: the last write wins.
|
|
87
|
+
|
|
88
|
+
Migration:
|
|
89
|
+
|
|
90
|
+
- **Site.** No change. The site does not write collections.
|
|
91
|
+
- **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`.
|
|
92
|
+
- **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.
|
|
93
|
+
- **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.
|
|
94
|
+
- **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.
|
|
95
|
+
|
|
3
96
|
## 0.14.1
|
|
4
97
|
|
|
5
98
|
### Patch Changes
|