@procedures/client 0.18.0 → 0.19.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 (66) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +20 -2
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/server/ndjson.d.ts +3 -1
  5. package/dist/adapters/http/server/ndjson.d.ts.map +1 -1
  6. package/dist/adapters/http/server/ndjson.js +3 -1
  7. package/dist/adapters/http/server/ndjson.js.map +1 -1
  8. package/dist/adapters/port/transport.d.ts +10 -1
  9. package/dist/adapters/port/transport.d.ts.map +1 -1
  10. package/dist/adapters/port/transport.js +5 -1
  11. package/dist/adapters/port/transport.js.map +1 -1
  12. package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
  13. package/dist/adapters/websocket/client/transport.js +8 -2
  14. package/dist/adapters/websocket/client/transport.js.map +1 -1
  15. package/dist/adapters/websocket/client/types.d.ts +3 -0
  16. package/dist/adapters/websocket/client/types.d.ts.map +1 -1
  17. package/dist/adapters/websocket/client/types.js.map +1 -1
  18. package/dist/adapters/websocket/shared/rpc.d.ts +5 -1
  19. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
  20. package/dist/adapters/websocket/shared/rpc.js +5 -1
  21. package/dist/adapters/websocket/shared/rpc.js.map +1 -1
  22. package/dist/browser.d.ts +3 -3
  23. package/dist/browser.d.ts.map +1 -1
  24. package/dist/browser.js +4 -2
  25. package/dist/browser.js.map +1 -1
  26. package/dist/procedures/collection/index.d.ts +1 -0
  27. package/dist/procedures/collection/index.d.ts.map +1 -1
  28. package/dist/procedures/collection/index.js +2 -0
  29. package/dist/procedures/collection/index.js.map +1 -1
  30. package/dist/procedures/collection/partition.d.ts +48 -0
  31. package/dist/procedures/collection/partition.d.ts.map +1 -1
  32. package/dist/procedures/collection/partition.js +66 -0
  33. package/dist/procedures/collection/partition.js.map +1 -1
  34. package/dist/procedures/collection/topic.d.ts +2 -0
  35. package/dist/procedures/collection/topic.d.ts.map +1 -1
  36. package/dist/procedures/collection/topic.js.map +1 -1
  37. package/dist/procedures/index.d.ts +1 -1
  38. package/dist/procedures/index.d.ts.map +1 -1
  39. package/dist/procedures/index.js +2 -0
  40. package/dist/procedures/index.js.map +1 -1
  41. package/dist/server/procedure-server.d.ts +37 -9
  42. package/dist/server/procedure-server.d.ts.map +1 -1
  43. package/dist/server/procedure-server.js +44 -4
  44. package/dist/server/procedure-server.js.map +1 -1
  45. package/dist/server/request-auth.d.ts +16 -1
  46. package/dist/server/request-auth.d.ts.map +1 -1
  47. package/dist/server/request-auth.js +18 -0
  48. package/dist/server/request-auth.js.map +1 -1
  49. package/dist/server/types.d.ts +22 -6
  50. package/dist/server/types.d.ts.map +1 -1
  51. package/dist/server/types.js +6 -0
  52. package/dist/server/types.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/adapters/http/server/ndjson.ts +3 -1
  55. package/src/adapters/port/transport.ts +15 -2
  56. package/src/adapters/websocket/client/transport.ts +10 -3
  57. package/src/adapters/websocket/client/types.ts +3 -0
  58. package/src/adapters/websocket/shared/rpc.ts +5 -1
  59. package/src/browser.ts +4 -1
  60. package/src/procedures/collection/index.ts +2 -0
  61. package/src/procedures/collection/partition.ts +75 -0
  62. package/src/procedures/collection/topic.ts +2 -0
  63. package/src/procedures/index.ts +2 -0
  64. package/src/server/procedure-server.ts +58 -11
  65. package/src/server/request-auth.ts +20 -1
  66. package/src/server/types.ts +23 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # @procedures/client
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5cb934d: Breaking: a `ProcedureServer` with a token or a request hook (`principalOf`) refuses a request without credentials (CLI-1). The error code is `UNAUTHORIZED`, and the hook does not run for it. Before, such a request skipped the token check and the request hook. Thus a face that set no credentials, for example a custom transport, went around the authentication.
8
+
9
+ New: the local marker `LOCAL_CREDENTIALS`, with `isLocalCredentials()` and its key `LOCAL_MARKER`. A local face sets it. Only a local request skips the token and the request hook.
10
+
11
+ The server examines a request in one of three ways:
12
+
13
+ - A local request skips the token and the request hook.
14
+ - A request without credentials gets `UNAUTHORIZED` when the server has a token or a request hook.
15
+ - A request with credentials goes through the request hook and the token check, as before.
16
+
17
+ - The local faces are MCP over stdio, an SDK transport of `McpServerTransport` and `PortTransport`. The peer of stdio is the parent process. Only the holder of the other end of a port can send to it.
18
+ - A network face never sets the marker: HTTP, WebSocket and MCP over Streamable HTTP. The key of the marker is a symbol. JSON, structured clone, a header, a cookie and a query cannot carry it.
19
+ - The calls of a WebSocket server to the `serve` handler of its client are not local. Their credentials are empty, so a `serve` server with a token refuses them.
20
+ - `PortTransport` has the new option `credentials`. Give `{}` for a port that a part outside the application can reach, for example a `Window`.
21
+ - The nested calls of a procedure do not go through the token check. They keep the principal of their caller.
22
+
23
+ Migration:
24
+
25
+ - A custom transport or face that sends no credentials to a server with a token or a request hook now gets `UNAUTHORIZED`. Set the credentials of its request (`requestCredentials()`), or `LOCAL_CREDENTIALS` for a face in this process.
26
+ - Code that calls `server.handle()` of a server with a token or a request hook gives `credentials: LOCAL_CREDENTIALS`.
27
+ - The site: no change. Its servers have no token and no request hook, and its workers serve over `PortTransport`, which sets the marker.
28
+ - MiniMongo: no change. `scripts/dev.ts` starts a server without a token and without a request hook. Its HTTP and WebSocket faces set the credentials of each request.
29
+ - Graph: no change. The procedure host (`packages/server/graph-host.ts`) has a request hook (`graphPrincipalOf` of `packages/server/graph-procedures.ts`) and no token. All its faces send credentials: HTTP, WebSocket and MCP over Streamable HTTP (`mcpFace`). No code of Graph calls `handle()` directly. The MCP host (`packages/mcp/src/host.ts`) operates over stdio, which is local.
30
+ - jarvis: no change. The loopback server and the remote server of `packages/http/src/procedures.ts` have a request hook (`principalOf`), and the remote server has a token. `makeProcedureHandler` mounts `procedureHandler`, which sends the credentials of each request. A test that calls `handle()` of such a server gives `LOCAL_CREDENTIALS`, or credentials with the token.
31
+
32
+ ## 0.18.1
33
+
34
+ ### Patch Changes
35
+
36
+ - d69c8cb: A host that serves a plain collection `a.b` and a collection `a` with partitions fails at registration. To correct it, rename the plain collection. Before, the two had the same topic `collections.a.b`. Thus the readers of `a.b` got the events of the tenant `b` of `a`, a leak across tenants (roadmap CLI-57, decision D23).
37
+
38
+ - The refusal is the new export `CollectionTopicCollisionError`. It names the two collections, the partition and the topic.
39
+ - `useStorage`, `useStorages`, the option `collections` of a host and `usePartitions` do the check. The check finds the pair in each order, also when the registrations come at different times.
40
+ - The check also refuses a deeper name, for example `a.b.c` next to `a` with partitions: `b.c` is a partition of `a`.
41
+ - The check also refuses a storage of `a.b` for the calls without a partition while `a` has partitions, also when `a.b` has partitions. That storage publishes on `collections.a.b`.
42
+ - A name without a dot does not change. A dotted name without a partitioned prefix does not change. Two collections with partitions, for example `a` and `a.b`, do not change, because the partitions of `a.b` encode its name.
43
+
44
+ PATCH (decision D23): the change refuses only a configuration that leaks events across tenants, so no caller can rely on it. Each correct configuration behaves as before.
45
+
46
+ Migration steps:
47
+
48
+ - When a host fails with `CollectionTopicCollisionError`, rename the plain collection, for example `a.b` to `a_b`. A name without a dot has no topic of a partition.
49
+ - The site: no change is necessary. It serves no collection.
50
+ - MiniMongo: no change is necessary. Its server serves no collection.
51
+ - Graph: no change is necessary. Its host gives partitions to each served collection (`examples`), and no collection has a plain storage.
52
+ - jarvis: no change is necessary. Its host serves no collection.
53
+ - 0379278: Docs and tests: the rule of the retryable flag of an in-band end (CLI-58). The behavior does not change, thus this change is a PATCH of the client group (decision D13).
54
+
55
+ - Before the stream starts, an error of a handler without a retryable flag gets the flag of its code in `ERROR_CODES` (CLI-48). Thus `UNAVAILABLE` without a flag is retryable, and the HTTP face answers 503.
56
+ - After the stream starts, an error ends the stream in-band. The in-band end is the `error` line of NDJSON, the `error` frame of WebSocket or the `_meta` of the MCP result.
57
+ - An in-band end without a retryable flag gets `retryable: false`, also when the stream gave no item. The reader can have items, and a retry can send them again.
58
+ - A handler gives `retryable: true` when a retry is safe. Then the in-band end is retryable too.
59
+ - The retry middleware of the client does not retry after an item reached the reader. Thus after the first item, the flag only tells the application.
60
+ - This rule agrees with the rule of a cut (CLI-48): the flag is false when the call can have had its effect.
61
+ - `WIRE-PROTOCOL.md` gives the rule in the section "Error codes". Tests pin it on the wire of HTTP and WebSocket, and on the HTTP, WebSocket and MCP faces of the procedures host.
62
+
63
+ The site, MiniMongo, Graph and jarvis: no change.
64
+
3
65
  ## 0.18.0
4
66
 
5
67
  ### Minor Changes
package/README.md CHANGED
@@ -707,7 +707,7 @@ interface ProcedureServerOptions {
707
707
  storage?: StorageConfig;
708
708
  transports?: ServerTransport[];
709
709
  events?: EventPublisherOptions | EventPublisher; // the remote events: refer to "Remote events"
710
- token?: string; // the token that each network request presents
710
+ token?: string; // the token that each request presents, except a local request
711
711
  principalOf?: PrincipalOf; // the request hook: refer to "The request hook"
712
712
  authenticate?: (request: ServerRequest) => Principal | undefined | Promise<Principal | undefined>;
713
713
  }
@@ -735,9 +735,27 @@ const server = new ProcedureServer({
735
735
  - `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.
736
736
  - `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.
737
737
  - An empty object accepts the request without a principal. Undefined, or an error, refuses the request with `UNAUTHORIZED`.
738
- - `authenticate` operates after the hook and can read `request.principal`. A request of this process (no credentials) does not go through the hook.
738
+ - `authenticate` operates after the hook and can read `request.principal`. A local request does not go through the hook.
739
739
  - `hostPolicy({ principalOf })` gives the hook to each server of the policy.
740
740
 
741
+ #### Local requests
742
+
743
+ Each face sets the credentials of its requests, or the local marker `LOCAL_CREDENTIALS` (CLI-1). A local request comes from this application, so the token and the request hook do not apply to it.
744
+
745
+ - The local faces are MCP over stdio, an SDK transport of `McpServerTransport` and `PortTransport`. The peer of stdio is the parent process. Only the holder of the other end of a port can send to it.
746
+ - A network face never sets the marker: HTTP, WebSocket, and MCP over Streamable HTTP. The key of the marker is a symbol, so JSON, a header, a cookie or a query cannot carry it.
747
+ - A server with a token or a request hook refuses a request without credentials with `UNAUTHORIZED`. The hook does not operate for it. Before, such a request skipped the token and the hook.
748
+ - A call of the code of the application to `server.handle()` sets the marker itself:
749
+
750
+ ```typescript
751
+ import { LOCAL_CREDENTIALS } from "@procedures/client";
752
+
753
+ await server.handle({ id: "1", method, payload, metadata: {}, credentials: LOCAL_CREDENTIALS });
754
+ ```
755
+
756
+ - The nested calls of a procedure do not go through the token check again. They keep the principal of their caller.
757
+ - A `PortTransport` that a part outside the application can reach takes `credentials: {}`. Then the token applies. A `Window` is such a port.
758
+
741
759
  #### Cookies and CSRF
742
760
 
743
761
  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.