@procedures/client 0.8.0 → 0.9.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 +78 -0
- package/README.md +95 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts +4 -0
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +11 -1
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/client/types.d.ts +19 -0
- package/dist/adapters/http/client/types.d.ts.map +1 -1
- package/dist/adapters/http/server/cors.d.ts +64 -0
- package/dist/adapters/http/server/cors.d.ts.map +1 -0
- package/dist/adapters/http/server/cors.js +113 -0
- package/dist/adapters/http/server/cors.js.map +1 -0
- package/dist/adapters/http/server/handler.d.ts +136 -0
- package/dist/adapters/http/server/handler.d.ts.map +1 -0
- package/dist/adapters/http/server/handler.js +495 -0
- package/dist/adapters/http/server/handler.js.map +1 -0
- package/dist/adapters/http/server/strategies.d.ts +14 -5
- package/dist/adapters/http/server/strategies.d.ts.map +1 -1
- package/dist/adapters/http/server/strategies.js.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +30 -46
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +48 -432
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/server/types.d.ts +13 -0
- package/dist/adapters/http/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +4 -12
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +46 -1
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +152 -15
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +10 -3
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/server/upgrade-router.d.ts +28 -0
- package/dist/adapters/websocket/server/upgrade-router.d.ts.map +1 -0
- package/dist/adapters/websocket/server/upgrade-router.js +62 -0
- package/dist/adapters/websocket/server/upgrade-router.js.map +1 -0
- package/dist/adapters/websocket/server/upgrade.d.ts +68 -0
- package/dist/adapters/websocket/server/upgrade.d.ts.map +1 -0
- package/dist/adapters/websocket/server/upgrade.js +49 -0
- package/dist/adapters/websocket/server/upgrade.js.map +1 -0
- package/dist/adapters/websocket/shared/rpc.d.ts +2 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.js +8 -2
- package/dist/adapters/websocket/shared/rpc.js.map +1 -1
- package/dist/browser.d.ts +12 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +13 -2
- package/dist/browser.js.map +1 -1
- package/dist/client/client.d.ts +43 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +69 -1
- package/dist/client/client.js.map +1 -1
- package/dist/events/publisher.d.ts +25 -1
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +66 -9
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/rows.d.ts +31 -0
- package/dist/events/rows.d.ts.map +1 -0
- package/dist/events/rows.js +39 -0
- package/dist/events/rows.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +14 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +9 -0
- package/dist/node.js.map +1 -1
- package/dist/procedures/capability.d.ts +6 -0
- package/dist/procedures/capability.d.ts.map +1 -1
- package/dist/procedures/capability.js +25 -0
- package/dist/procedures/capability.js.map +1 -1
- package/dist/procedures/collection/changes.d.ts +94 -0
- package/dist/procedures/collection/changes.d.ts.map +1 -0
- package/dist/procedures/collection/changes.js +182 -0
- package/dist/procedures/collection/changes.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +10 -3
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +8 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/partition.d.ts +67 -0
- package/dist/procedures/collection/partition.d.ts.map +1 -0
- package/dist/procedures/collection/partition.js +82 -0
- package/dist/procedures/collection/partition.js.map +1 -0
- package/dist/procedures/collection/procedures.d.ts +39 -2
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +147 -44
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/query.d.ts +246 -0
- package/dist/procedures/collection/query.d.ts.map +1 -0
- package/dist/procedures/collection/query.js +854 -0
- package/dist/procedures/collection/query.js.map +1 -0
- package/dist/procedures/collection/schemas.d.ts +50 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +144 -6
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +58 -6
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/collection/storage.js +23 -0
- package/dist/procedures/collection/storage.js.map +1 -1
- package/dist/procedures/elicit.d.ts +190 -0
- package/dist/procedures/elicit.d.ts.map +1 -0
- package/dist/procedures/elicit.js +127 -0
- package/dist/procedures/elicit.js.map +1 -0
- package/dist/procedures/hints.d.ts +43 -0
- package/dist/procedures/hints.d.ts.map +1 -0
- package/dist/procedures/hints.js +74 -0
- package/dist/procedures/hints.js.map +1 -0
- package/dist/procedures/index.d.ts +9 -2
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +5 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/invoke.d.ts +24 -2
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +14 -1
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/progress.d.ts +24 -0
- package/dist/procedures/progress.d.ts.map +1 -0
- package/dist/procedures/progress.js +11 -0
- package/dist/procedures/progress.js.map +1 -0
- package/dist/procedures/registry.d.ts.map +1 -1
- package/dist/procedures/registry.js +5 -0
- package/dist/procedures/registry.js.map +1 -1
- package/dist/procedures/types.d.ts +55 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/procedures/untrusted.d.ts +28 -0
- package/dist/procedures/untrusted.d.ts.map +1 -0
- package/dist/procedures/untrusted.js +32 -0
- package/dist/procedures/untrusted.js.map +1 -0
- package/dist/server/browser-guard.d.ts +12 -2
- package/dist/server/browser-guard.d.ts.map +1 -1
- package/dist/server/browser-guard.js +43 -6
- package/dist/server/browser-guard.js.map +1 -1
- package/dist/server/host-policy.d.ts +10 -1
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +5 -0
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +68 -7
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +124 -24
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +51 -4
- package/dist/server/request-auth.d.ts.map +1 -1
- package/dist/server/request-auth.js +116 -5
- package/dist/server/request-auth.js.map +1 -1
- package/dist/server/types.d.ts +73 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/package.json +7 -3
- package/src/adapters/http/client/transport.ts +11 -1
- package/src/adapters/http/client/types.ts +22 -0
- package/src/adapters/http/server/cors.ts +144 -0
- package/src/adapters/http/server/handler.ts +624 -0
- package/src/adapters/http/server/strategies.ts +16 -6
- package/src/adapters/http/server/transport.ts +55 -478
- package/src/adapters/http/server/types.ts +15 -0
- package/src/adapters/websocket/client/transport.ts +4 -12
- package/src/adapters/websocket/server/transport.ts +166 -16
- package/src/adapters/websocket/server/types.ts +11 -3
- package/src/adapters/websocket/server/upgrade-router.ts +77 -0
- package/src/adapters/websocket/server/upgrade.ts +88 -0
- package/src/adapters/websocket/shared/rpc.ts +9 -2
- package/src/browser.ts +85 -2
- package/src/client/client.ts +83 -1
- package/src/events/publisher.ts +75 -11
- package/src/events/rows.ts +48 -0
- package/src/index.ts +1 -1
- package/src/node.ts +21 -0
- package/src/procedures/capability.ts +21 -0
- package/src/procedures/collection/changes.ts +245 -0
- package/src/procedures/collection/index.ts +57 -1
- package/src/procedures/collection/partition.ts +123 -0
- package/src/procedures/collection/procedures.ts +206 -51
- package/src/procedures/collection/query.ts +1009 -0
- package/src/procedures/collection/schemas.ts +196 -7
- package/src/procedures/collection/storage.ts +78 -6
- package/src/procedures/elicit.ts +320 -0
- package/src/procedures/hints.ts +91 -0
- package/src/procedures/index.ts +83 -2
- package/src/procedures/invoke.ts +33 -2
- package/src/procedures/progress.ts +25 -0
- package/src/procedures/registry.ts +4 -0
- package/src/procedures/types.ts +65 -0
- package/src/procedures/untrusted.ts +50 -0
- package/src/server/browser-guard.ts +48 -6
- package/src/server/host-policy.ts +13 -1
- package/src/server/index.ts +3 -0
- package/src/server/procedure-server.ts +147 -26
- package/src/server/request-auth.ts +128 -6
- package/src/server/types.ts +82 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# @procedures/client
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- dc03903: The WebSocket client ignores a raw frame of the type `event`, as a frame of an unknown type. Before, it gave such a frame to `onEvent`, for a server outside this repository. No side of wire protocol v1 sends that frame (the type is reserved). `onEvent` now gets only the events of the subscriptions of the transport (`events.subscribe`). MINOR: a breaking change of behavior (the API is before 1.0.0).
|
|
8
|
+
|
|
9
|
+
Migration: a server that sends raw `event` frames sends its events through `events.subscribe` instead (EVENTS.md). The client then gets them in `onEvent`, with the id of the subscription.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 8bb469c: The partitions of a collection (roadmap CLI-17). One `collections.<name>.*` surface serves several partitions, for example one for each tenant. `ProcedureServer.usePartitions(name, { storageOf, resolve })` registers the storage of each partition and the resolver. The resolver gives the partition of each call from its context, so the tenant comes from `ctx.principal`. The input can ask for a partition, and the resolver can refuse the call (`FORBIDDEN`, reason `partition-not-allowed`).
|
|
14
|
+
|
|
15
|
+
`RepositoryProvider` has the new optional methods `getPartitionStorage`, `hasPartitions` and `resolvePartition`. A repository without them refuses a partition. The events of a partition go to `collections.<name>.<partition>`.
|
|
16
|
+
|
|
17
|
+
A subscriber of `EventPublisher` names the partition that it asks for, and the publisher resolves it with the same resolver. So a subscriber cannot name the topic of another tenant. A change of behavior for safety: a topic `collections.<name>.<partition>` of a collection without partitions is refused. Before, each subscriber with the read of the collection got it. New exports: `PARTITION_PATTERN`, `collectionTopicPartition`, `CollectionPartitions`, `PartitionInput` and `PartitionResolver`.
|
|
18
|
+
- dca28c1: The packages accept `@mark1russell7/service` 0.5.1 and later 0.5 versions, and `@procedures/client` accepts `@mark1russell7/algebra` 0.7.1. Before, the range `^0.4.1` stopped below 0.5. An application on service 0.5 then got a second copy of service inside the client.
|
|
19
|
+
|
|
20
|
+
- The client uses only the `/backoff`, `/heartbeat` and `/testing` entries of service. The code of `/backoff`, `/heartbeat` and the clock is the same in 0.4.1 and 0.5.1. `/testing` only gets new exports.
|
|
21
|
+
- The public API of the client does not change.
|
|
22
|
+
|
|
23
|
+
## 0.8.1
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- 2d3f235: The procedure faces of Node without a framework, a request hook for credentials, and the credentials mode of `fetch`. Each change is additive: the old behavior stays.
|
|
28
|
+
|
|
29
|
+
- `@procedures/client/node`: new `procedureHandler(server, options)` gives a request handler of `node:http`, `(req, res) => boolean`. It takes each request under `basePath` (default `/api`) and gives `false` for each other request, so the routes of the application share the listener. It speaks the wire of `HttpServerTransport` with the error codes of wire v1, and has CORS, `secretHeaders`, `secretQuery`, a Connect middleware and the stop parts `start`, `stopIntake`, `drain`, `inFlight` and `close`.
|
|
30
|
+
- `@procedures/client/node`: new `upgradeHandler(server, options)` gives an upgrade handler for the "no server" mode of `ws`. Two endpoints of different paths share one listener. The entry also exports `WebSocketServerTransport`, `defaultServerUrlStrategy` and `rpcServerUrlStrategy`.
|
|
31
|
+
- `HttpServerTransport` is a thin Express shim over `procedureHandler`, and keeps its former codes. `WebSocketServerTransport` takes only the upgrades of its path: two transports on one server now both work, and the option `server` is optional.
|
|
32
|
+
- New option `principalOf` of `ProcedureServer` and `hostPolicy`: a request hook that reads the cookies, the headers and the query of a network request. It gives a bearer token for the token check, a principal and a narrowing of the capability. The procedure reads the principal as `ctx.principal`. New `requestCredentials` and `cookiesOf`.
|
|
33
|
+
- A `Cookie` or `Proxy-Authorization` header no longer goes into the root of the metadata of an HTTP request. The secret headers stay in the credentials only.
|
|
34
|
+
- CSRF: ambient credentials (cookies) are trusted only from a named origin. With `principalOf`, a face accepts a browser request only from its own origin or a named origin. On `procedureHandler` and `upgradeHandler`, a request with a `Cookie` header gets the same check: `"*"` does not permit another origin for it. `principalOf` with the origin `"*"` and no named `allowedOrigins` is refused at construction. `HttpServerTransport` and `WebSocketServerTransport` keep their former checks for a server that reads no cookies. With an `authenticate` function, a request with a cookie needs a named origin. The new server option `ambientCredentials` (and `ProcedureServer.ambientCredentials`) tells the faces how the server reads cookies. New `hasAmbientCredentials(headers)`, and `checkBrowserRequest` takes an optional `ambient` flag. A server whose `authenticate` reads no cookie keeps the former check with `ambientCredentials: false`.
|
|
35
|
+
- `HttpTransport`: new options `credentials` (for example `"include"` for a cookie session on another origin), `requestInit` and `fetch`.
|
|
36
|
+
- 1aa9ef7: The collection storages get a JSON query language with a query policy, change batches and patches in one step, and the sync of `@procedures/store` runs on the service clock and backoff (server stack CLI-20, CLI-14, CLI-13, CLI-34).
|
|
37
|
+
|
|
38
|
+
- The query language is in the core, in the new entry `@procedures/client/query`, which imports nothing else. It is the one copy (CLI-34). `@procedures/collections` now depends on `@procedures/client` for it, and exports the same names again.
|
|
39
|
+
- The selector is JSON: a subset of the query selectors of MongoDB. `queryEntries()` is the reference, and `selectorProblems()`, `queryOptionsProblems()`, `compileSelector()`, `matchesSelector()` and `compareStorageValues()` are exported.
|
|
40
|
+
- A query policy (`StorageQueryPolicy`) limits the operators, the length and the flags of a `$regex`, the clauses and the depth of a selector, the lists of `$in`, `$nin` and `$all`, and the limit. The default policy does not permit `$regex`. A pattern that repeats a repetition, for example `(a+)+`, is always an error.
|
|
41
|
+
- `CollectionStorage` of `@procedures/collections` has the optional `query(selector, { sort, skip, limit }, policy)`, `observe(observer)` and `patch(id, fields)`. A caller tests for each one. A storage without them still follows the contract.
|
|
42
|
+
- `observe()` gives one batch of `set`, `delete` and `clear` changes for each write that changes the storage, before the write resolves. The write methods take `{ origin }`, and each change has it.
|
|
43
|
+
- `patch()` takes a JSON Merge Patch (RFC 7386), or the operations `{ $set, $unset }`, which can set `null` and nested field paths. A patch that mixes the two forms is an error. `applyPatch()`, `mergePatch()` and `patchProblems()` are exported.
|
|
44
|
+
- `InMemoryStorage` has the three methods, and the options `queryPolicy` and `onObserverError`. `StorageListeners` is the set of listeners that the storages share.
|
|
45
|
+
- The contract suite of `@procedures/collections/testing` tests the selectors against `queryEntries()`, with fixed and random selectors, the refusal of `$regex` by default, and, with `createWithQueryPolicy`, the opt-in of `$regex` and each limit of a policy. It also tests the two forms of a patch, the patches at the same time, and the batches with their origins.
|
|
46
|
+
- The core collection procedures have `collections.<name>.find` (it reads) and `collections.<name>.patch` (it reads and writes, idempotent). `find` refuses a selector or a limit that the policy does not permit with `VALIDATION_ERROR` and the reason, and a `find` without a limit gets at most `DEFAULT_FIND_LIMIT` (1000) items. It gives the selector and the policy to `query()` of the storage, or runs it on the entries. `patch` uses `patch()` of the storage, or a read and a write, one patch of an id at a time.
|
|
47
|
+
- `createCollectionProcedures(name, { queryPolicy })` and `ProcedureServer.registerCollectionProcedures(name, { queryPolicy })` take the policy of the host.
|
|
48
|
+
- The `CollectionStorage` of the core has the same optional methods and the write options. Its new types are exported from the root, `./browser` and `./procedures`.
|
|
49
|
+
- `ApiStorage` has `query()` (the procedure `find`, or the entries of an older server) and `patch()` (the procedure `patch`). Its option `queryPolicy` adds limits of the client.
|
|
50
|
+
- `ApiStorage` waits `backoffDelay` of `@mark1russell7/service/backoff` between its tries, with full jitter, on the option `clock`. The timeout uses the clock too. The new options are `retryFactor`, `maxRetryDelay`, `retryJitter`, `clock` and `random`. An error with `retryAfterMs` gets at least that wait.
|
|
51
|
+
- `HybridStorage` has `query()` and the option `queryPolicy`. Its timers run on the option `clock`, and the delay after a failure grows with the option `backoff`. `getStatus()` and `onStatus()` give the state of the sync and its failures. The storage writes nothing to the console.
|
|
52
|
+
- `SyncedProcedureRegistry` takes the option `clock` for its auto-sync. Its status has the failures of the auto-sync and of the write-through writes, and `onStatus()` sends them.
|
|
53
|
+
- `SqliteStorage` has `query()`, `patch()` and the option `queryPolicy`. A patch runs in one transaction.
|
|
54
|
+
- `@procedures/store` depends on `@mark1russell7/service`, and `@procedures/collections` depends on `@procedures/client`.
|
|
55
|
+
- 2523b1b: One MCP host factory, `createMcpHost()`, and the questions of a procedure to the person at the caller (CLI-10, CLI-12). PATCH: new exports and new optional options before 1.0.0. The old exports and their behavior stay.
|
|
56
|
+
|
|
57
|
+
- `@procedures/mcp-server`: `createMcpHost(options)` builds the registry (or the modules), the tool surface, the read-only narrowing, the host policy, the `ProcedureServer` and one `McpServerTransport` for each face (stdio, Streamable HTTP, SDK transports). The stdio face stops the host when its input ends, and `host.closed` resolves. `mcp.serve` and the dev-tools server use the factory. Thus `mcp.serve` gets its options from `hostPolicy()`, and it takes `readOnly` and `instructions`.
|
|
58
|
+
- `@procedures/client`: `ctx.elicit` asks the person at the caller for input or a confirmation. The helpers `elicitInput()` and `elicitConfirmation()` give the action `unavailable` with a reason when no one can answer. `elicitWithPolicy()` adds the consents of the operator (`operatorConsent`), and `ask: false` turns the questions off. The operator answers only when the face cannot ask. A failed question of a face that can ask (a timeout, a lost connection) gives `unavailable`, and an abort gives `cancel`. `ServerRequest` and `InvokeOptions` get `elicit` and `progress`.
|
|
59
|
+
- `@procedures/client`: `ctx.progress` tells the caller the progress of the call of the host. `metadata.untrusted` declares the output fields with text from outside the host, and `metadata.title` gives the title of a tool. `metadata.toolHints` can make a tool look more dangerous than its effects (`readOnlyHint: false`, `idempotentHint: false`, `destructiveHint: true`, `openWorldHint: true`). The registry refuses each other value, and `toolHintsOf()` gives the hints with the override.
|
|
60
|
+
- `@procedures/mcp-server`: `McpServerTransport` answers `ctx.elicit` with MCP elicitation (form and URL), over stdio and Streamable HTTP. It sends `ctx.progress` as `notifications/progress`, and it gives the instructions of the server. An untrusted field goes in envelopes with a random id, and its text cannot close its envelope. The option `content` can give other text blocks for a result. The option `json: "compact"` gives the JSON of the default blocks on one line. The option `toolName` gives the name of each tool, so a server that moves to the host keeps its tool names. A call finds its procedure by the tool, not by the decoded name. Each tool call over Streamable HTTP carries the credentials of its HTTP request, so the request hook `principalOf` and the token check of the server apply to it. A server that reads cookies gets the CSRF rule on this face too.
|
|
61
|
+
- `@procedures/mcp`: a tool gets `title` from `metadata.title`. In the output schema, each untrusted field is a string.
|
|
62
|
+
- `@procedures/mcp-dev-tools`: `createDevToolsHost()` gives the MCP host as `mcp`. `dist/server.js` ends its process when the client closes the input of stdio. The tool list stays the same.
|
|
63
|
+
- c559b00: `HybridStorage` captures the writes of the application to its local copy, keeps its sync state after a reload and takes a push feed of the remote changes. Each write of the core collection procedures publishes a change event (server stack CLI-21, CLI-15, decision D9).
|
|
64
|
+
|
|
65
|
+
- `HybridStorage.observe(observer)` gives one batch of changes for each change of the storage. A write of the storage gives its batch with the origin of its options. A pull and a delta of the feed give their changes with the origin of the option `syncOrigin` (`"sync"` by default). In `remote-only` mode, the batches tell the writes of the storage to the remote.
|
|
66
|
+
- 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 }`.
|
|
67
|
+
- `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.
|
|
68
|
+
- 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.
|
|
69
|
+
- 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).
|
|
70
|
+
- 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.
|
|
71
|
+
- 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).
|
|
72
|
+
- `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()`.
|
|
73
|
+
- 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.
|
|
74
|
+
- 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.
|
|
75
|
+
- `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.
|
|
76
|
+
- 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).
|
|
77
|
+
- 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.
|
|
78
|
+
- `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.
|
|
79
|
+
- 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`.
|
|
80
|
+
|
|
3
81
|
## 0.8.0
|
|
4
82
|
|
|
5
83
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -250,12 +250,22 @@ new HttpTransport(options: HttpTransportOptions)
|
|
|
250
250
|
interface HttpTransportOptions {
|
|
251
251
|
baseUrl: string;
|
|
252
252
|
urlStrategy?: UrlStrategy; // default: defaultUrlPattern
|
|
253
|
-
httpMethodStrategy?: HttpMethodStrategy; // default:
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
httpMethodStrategy?: HttpMethodStrategy; // default: postOnlyStrategy
|
|
254
|
+
headerConverter?: HeaderConverter;
|
|
255
|
+
defaultHeaders?: Record<string, string>;
|
|
256
|
+
timeout?: Duration; // the time to the first item
|
|
257
|
+
credentials?: "include" | "same-origin" | "omit"; // the credentials mode of fetch
|
|
258
|
+
requestInit?: Omit<RequestInit, "method" | "headers" | "body" | "signal">;
|
|
259
|
+
fetch?: typeof fetch; // default: globalThis.fetch
|
|
256
260
|
}
|
|
257
261
|
```
|
|
258
262
|
|
|
263
|
+
An application with a cookie session on another origin sets `credentials: "include"`. The browser then sends the cookies of the server with each call. The server must permit the origin of the page with credentials (CORS). Refer to "Procedure handler without a framework".
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const client = new Client(new HttpTransport({ baseUrl: "https://api.example.com/api", credentials: "include" }));
|
|
267
|
+
```
|
|
268
|
+
|
|
259
269
|
#### `WebSocketTransport`
|
|
260
270
|
```typescript
|
|
261
271
|
new WebSocketTransport(options: WebSocketTransportOptions)
|
|
@@ -630,18 +640,56 @@ createServer((req, res) => {
|
|
|
630
640
|
|
|
631
641
|
The promise of `writeNdjsonStream` resolves when the response ends. A closed connection or an abort of `signal` stops the stream, and the iterator of the items gets `return()`.
|
|
632
642
|
|
|
643
|
+
#### Procedure handler without a framework
|
|
644
|
+
|
|
645
|
+
`procedureHandler(server, options)` of `@procedures/client/node` gives a request handler of `node:http`: `(req, res) => boolean`. The handler takes each request under `basePath` and gives `true`. For each other request, it gives `false` and does not touch the request. Thus the routes of the application share the listener with the procedures.
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
import { createServer } from "node:http";
|
|
649
|
+
import { ProcedureServer } from "@procedures/client";
|
|
650
|
+
import { procedureHandler, upgradeHandler } from "@procedures/client/node";
|
|
651
|
+
|
|
652
|
+
const server = new ProcedureServer({ registry, token });
|
|
653
|
+
server.registerFromRegistry();
|
|
654
|
+
const procedures = procedureHandler(server, { basePath: "/api/proc" });
|
|
655
|
+
const sockets = upgradeHandler(server, { path: "/ws" });
|
|
656
|
+
|
|
657
|
+
const http = createServer((req, res) => {
|
|
658
|
+
if (procedures(req, res)) return;
|
|
659
|
+
appRoutes(req, res); // for example /api/chat
|
|
660
|
+
});
|
|
661
|
+
http.on("upgrade", (req, socket, head) => {
|
|
662
|
+
if (!sockets(req, socket, head)) socket.destroy();
|
|
663
|
+
});
|
|
664
|
+
http.listen(4820);
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
- The handler speaks the wire of `HttpServerTransport` ([WIRE-PROTOCOL.md](../documentation/WIRE-PROTOCOL.md)). This includes the URL of the method, NDJSON streams with `seq` and keep-alive lines, idempotency keys, deadlines, events and the browser checks.
|
|
668
|
+
- The error codes are the codes of wire v1. A stopped handler answers `UNAVAILABLE` (503, retryable). A refused browser request answers `FORBIDDEN` (403) with a reason: `origin-not-allowed`, `host-header-not-allowed`, `method-not-allowed` or `media-type-not-allowed`.
|
|
669
|
+
- An `OPTIONS` request gets 204 and starts no procedure.
|
|
670
|
+
- `cors: { origin: ["https://app.example"], credentials: true }` lets a page of that origin call with its cookies. A list or a function of origins sends `Access-Control-Allow-Credentials: true` by default.
|
|
671
|
+
- `secretHeaders` names more headers that carry a secret, for example `X-Jarvis-Token`. `secretQuery` names the query parameters that carry a secret. Its default is `["token"]`. These values go into the credentials of the request, not into its metadata.
|
|
672
|
+
- `handler.middleware` mounts the handler in Connect or Express. A request outside `basePath` goes to `next`.
|
|
673
|
+
- The handler has the stop parts of a service transport: `start()`, `stopIntake()`, `drain(deadline, signal)`, `inFlight()` and `close()`. It does not make a server and does not listen.
|
|
674
|
+
|
|
675
|
+
`upgradeHandler(server, options)` gives an upgrade handler for the "no server" mode of `ws`: `(req, socket, head) => boolean`. It takes each upgrade of its `path`. Two handlers of different paths share one listener. `handler.transport` gives the API of `WebSocketServerTransport`: `broadcast`, `callConnection`, `subscribe` and the others. The upgrade handler has the same stop parts. After `stopIntake()`, a new upgrade gets 503, and a new call gets `UNAVAILABLE`.
|
|
676
|
+
|
|
633
677
|
#### WebSocket Server Transport
|
|
634
678
|
```typescript
|
|
635
|
-
new WebSocketServerTransport(server: Server, options
|
|
679
|
+
new WebSocketServerTransport(server: Server, options?: WebSocketServerTransportOptions)
|
|
636
680
|
|
|
637
681
|
interface WebSocketServerTransportOptions {
|
|
638
|
-
|
|
639
|
-
path?: string;
|
|
640
|
-
|
|
682
|
+
server?: HttpServer; // the server whose upgrades of `path` the transport takes
|
|
683
|
+
path?: string; // default: "/ws"
|
|
684
|
+
authenticate?: WebSocketAuthHandler;
|
|
641
685
|
onConnection?: WebSocketConnectionHandler;
|
|
686
|
+
secretHeaders?: readonly string[];
|
|
687
|
+
heartbeat?: { interval?: Duration; misses?: number } | false;
|
|
642
688
|
}
|
|
643
689
|
```
|
|
644
690
|
|
|
691
|
+
The transport takes only the upgrades of its path. Thus two transports of different paths share one server. An upgrade that no transport and no other listener takes gets 400. Without `server`, give each upgrade to `transport.upgrade(req, socket, head)`, or use `upgradeHandler`.
|
|
692
|
+
|
|
645
693
|
#### Procedure Server
|
|
646
694
|
```typescript
|
|
647
695
|
const server = createProcedureServer(options?: ProcedureServerOptions)
|
|
@@ -651,9 +699,48 @@ interface ProcedureServerOptions {
|
|
|
651
699
|
storage?: StorageConfig;
|
|
652
700
|
transports?: ServerTransport[];
|
|
653
701
|
events?: EventPublisherOptions | EventPublisher; // the remote events: refer to "Remote events"
|
|
702
|
+
token?: string; // the token that each network request presents
|
|
703
|
+
principalOf?: PrincipalOf; // the request hook: refer to "The request hook"
|
|
704
|
+
authenticate?: (request: ServerRequest) => Principal | undefined | Promise<Principal | undefined>;
|
|
654
705
|
}
|
|
655
706
|
```
|
|
656
707
|
|
|
708
|
+
#### The request hook
|
|
709
|
+
|
|
710
|
+
`principalOf` reads the credentials of each network request and gives its identity. The credentials have the bearer token, the cookies, each header and the query of the request. The secret headers and the secret query parameters are in the credentials only, not in the metadata.
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
const server = new ProcedureServer({
|
|
714
|
+
token,
|
|
715
|
+
clearanceOrder: ladder(["public", "guest", "intimate"]),
|
|
716
|
+
capability: { clearance: "intimate" },
|
|
717
|
+
principalOf: ({ cookies = {}, headers = {}, query = {} }) => ({
|
|
718
|
+
// The token of a cookie, of an application header or of the query
|
|
719
|
+
bearer: cookies["jarvis_token"] ?? headers["x-jarvis-token"] ?? query["token"],
|
|
720
|
+
// A guest switch can only make the capability narrower
|
|
721
|
+
...(cookies["jarvis_audience"] ? { narrowing: { clearance: cookies["jarvis_audience"] } } : {}),
|
|
722
|
+
}),
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
- `bearer`: the server compares this token with its `token`. Without it, the server compares the bearer token of the `Authorization` header.
|
|
727
|
+
- `principal`: the principal of the request, for example the user of a session cookie. The procedure reads it as `ctx.principal`, and its nested calls get the same principal.
|
|
728
|
+
- `narrowing`: a narrower capability that the request asks for. The server narrows the capability of the call with it. A request for more has no effect.
|
|
729
|
+
- An empty object accepts the request without a principal. Undefined, or an error, refuses the request with `UNAUTHORIZED`.
|
|
730
|
+
- `authenticate` operates after the hook and can read `request.principal`. A request of this process (no credentials) does not go through the hook.
|
|
731
|
+
- `hostPolicy({ principalOf })` gives the hook to each server of the policy.
|
|
732
|
+
|
|
733
|
+
#### Cookies and CSRF
|
|
734
|
+
|
|
735
|
+
A browser sends the cookies of a server by itself, also for a request that a page of another site starts. Thus a server that reads cookies trusts a request only from a named origin.
|
|
736
|
+
|
|
737
|
+
- With `principalOf`, the server reads the cookies of each request. A face then accepts a browser request only from its own origin or from a named origin.
|
|
738
|
+
- A request with a `Cookie` header gets the same check on `procedureHandler` and `upgradeHandler`. `"*"` does not permit another origin for it. A list or a function of origins does.
|
|
739
|
+
- `principalOf` with the origin `"*"` and no named `allowedOrigins` is refused at construction. The error names the fix.
|
|
740
|
+
- A request without an `Origin` header and without `Sec-Fetch-Site` comes from a program, not from a page. It passes.
|
|
741
|
+
- `HttpServerTransport` and `WebSocketServerTransport` keep their former checks for a server that reads no cookies. With `authenticate`, a request with a cookie needs a named origin. The server option `ambientCredentials: false` turns this off for an `authenticate` function that reads only the bearer token.
|
|
742
|
+
- Give a session cookie `SameSite=Lax` or `SameSite=Strict`, and `HttpOnly`. A cookie with `SameSite=None` goes with each request of another site, so only the origin check stops such a request.
|
|
743
|
+
|
|
657
744
|
### Error System
|
|
658
745
|
|
|
659
746
|
```typescript
|
|
@@ -698,7 +785,7 @@ import { ... } from "@procedures/client/events";
|
|
|
698
785
|
|---|---|
|
|
699
786
|
| `@procedures/client` | Node. The browser entry and the server transports. Its `WebSocketTransport` uses the global `WebSocket`. |
|
|
700
787
|
| `@procedures/client/browser` | A browser bundle. It does not import `http` or `ws`. |
|
|
701
|
-
| `@procedures/client/node` | Node. A `WebSocketTransport` whose sockets are of the `ws` package: a drop ends a socket at once. `writeNdjsonStream`: the NDJSON stream response of `HttpServerTransport`, for a `ServerResponse` of `node:http`. |
|
|
788
|
+
| `@procedures/client/node` | Node. A `WebSocketTransport` whose sockets are of the `ws` package: a drop ends a socket at once. `writeNdjsonStream`: the NDJSON stream response of `HttpServerTransport`, for a `ServerResponse` of `node:http`. `procedureHandler` and `upgradeHandler`: the procedure faces of HTTP and WebSocket, without a framework. |
|
|
702
789
|
| `@procedures/client/register` | It registers the core procedures in the global registry. |
|
|
703
790
|
|
|
704
791
|
## Procedure Discovery
|