@procedures/client 0.2.2 → 0.3.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 (299) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +61 -38
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/client/transport.d.ts.map +1 -1
  5. package/dist/adapters/http/client/transport.js +35 -3
  6. package/dist/adapters/http/client/transport.js.map +1 -1
  7. package/dist/adapters/http/client/types.d.ts +3 -2
  8. package/dist/adapters/http/client/types.d.ts.map +1 -1
  9. package/dist/adapters/http/server/transport.d.ts +2 -2
  10. package/dist/adapters/http/server/transport.d.ts.map +1 -1
  11. package/dist/adapters/http/server/transport.js +53 -20
  12. package/dist/adapters/http/server/transport.js.map +1 -1
  13. package/dist/adapters/http/shared/headers.d.ts +1 -1
  14. package/dist/adapters/http/shared/headers.d.ts.map +1 -1
  15. package/dist/adapters/http/shared/headers.js +7 -6
  16. package/dist/adapters/http/shared/headers.js.map +1 -1
  17. package/dist/adapters/local/client/transport.d.ts +3 -0
  18. package/dist/adapters/local/client/transport.d.ts.map +1 -1
  19. package/dist/adapters/local/client/transport.js +63 -4
  20. package/dist/adapters/local/client/transport.js.map +1 -1
  21. package/dist/adapters/local/client/types.d.ts +15 -0
  22. package/dist/adapters/local/client/types.d.ts.map +1 -1
  23. package/dist/adapters/local/client/types.js.map +1 -1
  24. package/dist/adapters/mock/client/transport.d.ts +3 -2
  25. package/dist/adapters/mock/client/transport.d.ts.map +1 -1
  26. package/dist/adapters/mock/client/transport.js +8 -5
  27. package/dist/adapters/mock/client/transport.js.map +1 -1
  28. package/dist/adapters/mock/client/types.d.ts +7 -6
  29. package/dist/adapters/mock/client/types.d.ts.map +1 -1
  30. package/dist/adapters/port/transport.d.ts +62 -0
  31. package/dist/adapters/port/transport.d.ts.map +1 -0
  32. package/dist/adapters/port/transport.js +94 -0
  33. package/dist/adapters/port/transport.js.map +1 -0
  34. package/dist/adapters/websocket/client/index.d.ts +1 -1
  35. package/dist/adapters/websocket/client/index.d.ts.map +1 -1
  36. package/dist/adapters/websocket/client/index.js.map +1 -1
  37. package/dist/adapters/websocket/client/transport.d.ts +47 -7
  38. package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
  39. package/dist/adapters/websocket/client/transport.js +213 -67
  40. package/dist/adapters/websocket/client/transport.js.map +1 -1
  41. package/dist/adapters/websocket/client/types.d.ts +70 -21
  42. package/dist/adapters/websocket/client/types.d.ts.map +1 -1
  43. package/dist/adapters/websocket/client/types.js.map +1 -1
  44. package/dist/adapters/websocket/server/transport.d.ts +65 -24
  45. package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
  46. package/dist/adapters/websocket/server/transport.js +192 -263
  47. package/dist/adapters/websocket/server/transport.js.map +1 -1
  48. package/dist/adapters/websocket/server/types.d.ts +20 -1
  49. package/dist/adapters/websocket/server/types.d.ts.map +1 -1
  50. package/dist/adapters/websocket/shared/rpc.d.ts +129 -0
  51. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -0
  52. package/dist/adapters/websocket/shared/rpc.js +425 -0
  53. package/dist/adapters/websocket/shared/rpc.js.map +1 -0
  54. package/dist/browser.d.ts +29 -7
  55. package/dist/browser.d.ts.map +1 -1
  56. package/dist/browser.js +26 -3
  57. package/dist/browser.js.map +1 -1
  58. package/dist/client/call-types.d.ts +10 -10
  59. package/dist/client/call-types.d.ts.map +1 -1
  60. package/dist/client/call-types.js +1 -6
  61. package/dist/client/call-types.js.map +1 -1
  62. package/dist/client/client.d.ts +6 -1
  63. package/dist/client/client.d.ts.map +1 -1
  64. package/dist/client/client.js +23 -9
  65. package/dist/client/client.js.map +1 -1
  66. package/dist/client/deadline.d.ts +38 -0
  67. package/dist/client/deadline.d.ts.map +1 -0
  68. package/dist/client/deadline.js +60 -0
  69. package/dist/client/deadline.js.map +1 -0
  70. package/dist/client/errors/factory.d.ts.map +1 -1
  71. package/dist/client/errors/factory.js +11 -7
  72. package/dist/client/errors/factory.js.map +1 -1
  73. package/dist/client/index.d.ts +9 -2
  74. package/dist/client/index.d.ts.map +1 -1
  75. package/dist/client/index.js +8 -2
  76. package/dist/client/index.js.map +1 -1
  77. package/dist/client/middleware/cache.d.ts +19 -5
  78. package/dist/client/middleware/cache.d.ts.map +1 -1
  79. package/dist/client/middleware/cache.js +106 -18
  80. package/dist/client/middleware/cache.js.map +1 -1
  81. package/dist/client/middleware/circuit-breaker.d.ts +28 -10
  82. package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
  83. package/dist/client/middleware/circuit-breaker.js +35 -24
  84. package/dist/client/middleware/circuit-breaker.js.map +1 -1
  85. package/dist/client/middleware/contexts.d.ts +16 -10
  86. package/dist/client/middleware/contexts.d.ts.map +1 -1
  87. package/dist/client/middleware/contexts.js.map +1 -1
  88. package/dist/client/middleware/items.d.ts +31 -2
  89. package/dist/client/middleware/items.d.ts.map +1 -1
  90. package/dist/client/middleware/items.js +86 -6
  91. package/dist/client/middleware/items.js.map +1 -1
  92. package/dist/client/middleware/lru.d.ts +2 -0
  93. package/dist/client/middleware/lru.d.ts.map +1 -1
  94. package/dist/client/middleware/lru.js +11 -0
  95. package/dist/client/middleware/lru.js.map +1 -1
  96. package/dist/client/middleware/rate-limit.d.ts +7 -6
  97. package/dist/client/middleware/rate-limit.d.ts.map +1 -1
  98. package/dist/client/middleware/rate-limit.js +9 -6
  99. package/dist/client/middleware/rate-limit.js.map +1 -1
  100. package/dist/client/middleware/resilience.d.ts +61 -0
  101. package/dist/client/middleware/resilience.d.ts.map +1 -0
  102. package/dist/client/middleware/resilience.js +59 -0
  103. package/dist/client/middleware/resilience.js.map +1 -0
  104. package/dist/client/middleware/retry.d.ts +45 -11
  105. package/dist/client/middleware/retry.d.ts.map +1 -1
  106. package/dist/client/middleware/retry.js +102 -22
  107. package/dist/client/middleware/retry.js.map +1 -1
  108. package/dist/client/middleware/timeout.d.ts +15 -12
  109. package/dist/client/middleware/timeout.d.ts.map +1 -1
  110. package/dist/client/middleware/timeout.js +46 -37
  111. package/dist/client/middleware/timeout.js.map +1 -1
  112. package/dist/client/middleware/tracing.d.ts.map +1 -1
  113. package/dist/client/middleware/tracing.js +11 -3
  114. package/dist/client/middleware/tracing.js.map +1 -1
  115. package/dist/client/types.d.ts +9 -1
  116. package/dist/client/types.d.ts.map +1 -1
  117. package/dist/client/types.js.map +1 -1
  118. package/dist/clock.d.ts +19 -0
  119. package/dist/clock.d.ts.map +1 -0
  120. package/dist/clock.js +18 -0
  121. package/dist/clock.js.map +1 -0
  122. package/dist/duration.d.ts +37 -0
  123. package/dist/duration.d.ts.map +1 -0
  124. package/dist/duration.js +44 -0
  125. package/dist/duration.js.map +1 -0
  126. package/dist/machines.d.ts +90 -0
  127. package/dist/machines.d.ts.map +1 -0
  128. package/dist/machines.js +127 -0
  129. package/dist/machines.js.map +1 -0
  130. package/dist/procedures/capability.d.ts +3 -6
  131. package/dist/procedures/capability.d.ts.map +1 -1
  132. package/dist/procedures/capability.js +11 -9
  133. package/dist/procedures/capability.js.map +1 -1
  134. package/dist/procedures/collection/index.d.ts +3 -3
  135. package/dist/procedures/collection/index.d.ts.map +1 -1
  136. package/dist/procedures/collection/index.js +1 -1
  137. package/dist/procedures/collection/index.js.map +1 -1
  138. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  139. package/dist/procedures/collection/procedures.js +49 -1
  140. package/dist/procedures/collection/procedures.js.map +1 -1
  141. package/dist/procedures/collection/schemas.d.ts +33 -0
  142. package/dist/procedures/collection/schemas.d.ts.map +1 -1
  143. package/dist/procedures/collection/schemas.js +68 -0
  144. package/dist/procedures/collection/schemas.js.map +1 -1
  145. package/dist/procedures/collection/storage.d.ts +37 -0
  146. package/dist/procedures/collection/storage.d.ts.map +1 -1
  147. package/dist/procedures/core/array.d.ts.map +1 -1
  148. package/dist/procedures/core/array.js +26 -15
  149. package/dist/procedures/core/array.js.map +1 -1
  150. package/dist/procedures/core/index.d.ts.map +1 -1
  151. package/dist/procedures/core/index.js +8 -0
  152. package/dist/procedures/core/index.js.map +1 -1
  153. package/dist/procedures/core/meta.d.ts.map +1 -1
  154. package/dist/procedures/core/meta.js +2 -1
  155. package/dist/procedures/core/meta.js.map +1 -1
  156. package/dist/procedures/core/object.d.ts.map +1 -1
  157. package/dist/procedures/core/object.js +42 -13
  158. package/dist/procedures/core/object.js.map +1 -1
  159. package/dist/procedures/core/operands.d.ts.map +1 -1
  160. package/dist/procedures/core/operands.js +6 -1
  161. package/dist/procedures/core/operands.js.map +1 -1
  162. package/dist/procedures/core/string.d.ts.map +1 -1
  163. package/dist/procedures/core/string.js +4 -2
  164. package/dist/procedures/core/string.js.map +1 -1
  165. package/dist/procedures/define-procedure.d.ts +19 -7
  166. package/dist/procedures/define-procedure.d.ts.map +1 -1
  167. package/dist/procedures/define-procedure.js +66 -21
  168. package/dist/procedures/define-procedure.js.map +1 -1
  169. package/dist/procedures/effects.d.ts +16 -0
  170. package/dist/procedures/effects.d.ts.map +1 -1
  171. package/dist/procedures/effects.js +33 -0
  172. package/dist/procedures/effects.js.map +1 -1
  173. package/dist/procedures/idempotency.d.ts +73 -0
  174. package/dist/procedures/idempotency.d.ts.map +1 -0
  175. package/dist/procedures/idempotency.js +131 -0
  176. package/dist/procedures/idempotency.js.map +1 -0
  177. package/dist/procedures/index.d.ts +7 -3
  178. package/dist/procedures/index.d.ts.map +1 -1
  179. package/dist/procedures/index.js +6 -2
  180. package/dist/procedures/index.js.map +1 -1
  181. package/dist/procedures/interceptors.d.ts +17 -4
  182. package/dist/procedures/interceptors.d.ts.map +1 -1
  183. package/dist/procedures/interceptors.js +18 -7
  184. package/dist/procedures/interceptors.js.map +1 -1
  185. package/dist/procedures/invoke.d.ts +15 -3
  186. package/dist/procedures/invoke.d.ts.map +1 -1
  187. package/dist/procedures/invoke.js +8 -3
  188. package/dist/procedures/invoke.js.map +1 -1
  189. package/dist/procedures/manifest.d.ts +83 -0
  190. package/dist/procedures/manifest.d.ts.map +1 -0
  191. package/dist/procedures/manifest.js +90 -0
  192. package/dist/procedures/manifest.js.map +1 -0
  193. package/dist/procedures/runtime-programs.d.ts +19 -0
  194. package/dist/procedures/runtime-programs.d.ts.map +1 -0
  195. package/dist/procedures/runtime-programs.js +24 -0
  196. package/dist/procedures/runtime-programs.js.map +1 -0
  197. package/dist/procedures/types.d.ts +20 -1
  198. package/dist/procedures/types.d.ts.map +1 -1
  199. package/dist/procedures/types.js.map +1 -1
  200. package/dist/protocol.d.ts +40 -0
  201. package/dist/protocol.d.ts.map +1 -0
  202. package/dist/protocol.js +58 -0
  203. package/dist/protocol.js.map +1 -0
  204. package/dist/server/host-policy.d.ts +4 -3
  205. package/dist/server/host-policy.d.ts.map +1 -1
  206. package/dist/server/host-policy.js +6 -3
  207. package/dist/server/host-policy.js.map +1 -1
  208. package/dist/server/host-procedures.d.ts +31 -0
  209. package/dist/server/host-procedures.d.ts.map +1 -0
  210. package/dist/server/host-procedures.js +75 -0
  211. package/dist/server/host-procedures.js.map +1 -0
  212. package/dist/server/index.d.ts +3 -3
  213. package/dist/server/index.d.ts.map +1 -1
  214. package/dist/server/index.js +1 -1
  215. package/dist/server/index.js.map +1 -1
  216. package/dist/server/procedure-server.d.ts +110 -4
  217. package/dist/server/procedure-server.d.ts.map +1 -1
  218. package/dist/server/procedure-server.js +269 -17
  219. package/dist/server/procedure-server.js.map +1 -1
  220. package/dist/server/request-auth.d.ts +19 -0
  221. package/dist/server/request-auth.d.ts.map +1 -0
  222. package/dist/server/request-auth.js +32 -0
  223. package/dist/server/request-auth.js.map +1 -0
  224. package/dist/server/types.d.ts +32 -0
  225. package/dist/server/types.d.ts.map +1 -1
  226. package/dist/server/types.js.map +1 -1
  227. package/dist/timers.d.ts +60 -0
  228. package/dist/timers.d.ts.map +1 -0
  229. package/dist/timers.js +33 -0
  230. package/dist/timers.js.map +1 -0
  231. package/dist/tracing/traceparent.d.ts +26 -0
  232. package/dist/tracing/traceparent.d.ts.map +1 -0
  233. package/dist/tracing/traceparent.js +53 -0
  234. package/dist/tracing/traceparent.js.map +1 -0
  235. package/package.json +7 -1
  236. package/src/adapters/http/client/transport.ts +31 -3
  237. package/src/adapters/http/client/types.ts +3 -2
  238. package/src/adapters/http/server/transport.ts +60 -24
  239. package/src/adapters/http/shared/headers.ts +7 -6
  240. package/src/adapters/local/client/transport.ts +70 -5
  241. package/src/adapters/local/client/types.ts +18 -0
  242. package/src/adapters/mock/client/transport.ts +9 -6
  243. package/src/adapters/mock/client/types.ts +7 -6
  244. package/src/adapters/port/transport.ts +124 -0
  245. package/src/adapters/websocket/client/index.ts +1 -0
  246. package/src/adapters/websocket/client/transport.ts +232 -73
  247. package/src/adapters/websocket/client/types.ts +80 -21
  248. package/src/adapters/websocket/server/transport.ts +199 -301
  249. package/src/adapters/websocket/server/types.ts +20 -1
  250. package/src/adapters/websocket/shared/rpc.ts +453 -0
  251. package/src/browser.ts +51 -3
  252. package/src/client/call-types.ts +10 -14
  253. package/src/client/client.ts +28 -9
  254. package/src/client/deadline.ts +64 -0
  255. package/src/client/errors/factory.ts +12 -8
  256. package/src/client/index.ts +21 -3
  257. package/src/client/middleware/cache.ts +120 -23
  258. package/src/client/middleware/circuit-breaker.ts +65 -35
  259. package/src/client/middleware/contexts.ts +17 -10
  260. package/src/client/middleware/items.ts +101 -6
  261. package/src/client/middleware/lru.ts +12 -0
  262. package/src/client/middleware/rate-limit.ts +9 -7
  263. package/src/client/middleware/resilience.ts +81 -0
  264. package/src/client/middleware/retry.ts +146 -34
  265. package/src/client/middleware/timeout.ts +55 -46
  266. package/src/client/middleware/tracing.ts +13 -4
  267. package/src/client/types.ts +9 -1
  268. package/src/clock.ts +21 -0
  269. package/src/duration.ts +56 -0
  270. package/src/machines.ts +186 -0
  271. package/src/procedures/capability.ts +11 -9
  272. package/src/procedures/collection/index.ts +9 -1
  273. package/src/procedures/collection/procedures.ts +57 -0
  274. package/src/procedures/collection/schemas.ts +110 -0
  275. package/src/procedures/collection/storage.ts +38 -0
  276. package/src/procedures/core/array.ts +27 -16
  277. package/src/procedures/core/index.ts +8 -0
  278. package/src/procedures/core/meta.ts +2 -1
  279. package/src/procedures/core/object.ts +48 -14
  280. package/src/procedures/core/operands.ts +7 -0
  281. package/src/procedures/core/string.ts +4 -2
  282. package/src/procedures/define-procedure.ts +82 -24
  283. package/src/procedures/effects.ts +35 -0
  284. package/src/procedures/idempotency.ts +152 -0
  285. package/src/procedures/index.ts +18 -2
  286. package/src/procedures/interceptors.ts +28 -11
  287. package/src/procedures/invoke.ts +19 -5
  288. package/src/procedures/manifest.ts +158 -0
  289. package/src/procedures/runtime-programs.ts +27 -0
  290. package/src/procedures/types.ts +20 -1
  291. package/src/protocol.ts +97 -0
  292. package/src/server/host-policy.ts +8 -5
  293. package/src/server/host-procedures.ts +102 -0
  294. package/src/server/index.ts +5 -2
  295. package/src/server/procedure-server.ts +324 -10
  296. package/src/server/request-auth.ts +35 -0
  297. package/src/server/types.ts +37 -0
  298. package/src/timers.ts +82 -0
  299. package/src/tracing/traceparent.ts +61 -0
@@ -8,6 +8,7 @@ import type { IncomingMessage } from "http";
8
8
  import type { Server as HttpServer } from "http";
9
9
  import type { WebSocket } from "ws";
10
10
  import type { BrowserGuardOptions } from "../../../server/browser-guard.js";
11
+ import type { Duration } from "../../../duration.js";
11
12
 
12
13
  /**
13
14
  * WebSocket authentication handler.
@@ -75,6 +76,21 @@ export interface WebSocketServerTransportOptions extends Omit<BrowserGuardOption
75
76
  * @default 1 MiB
76
77
  */
77
78
  highWaterMark?: number;
79
+
80
+ /**
81
+ * The heartbeat of the server: a WebSocket ping to each connection at each interval. A
82
+ * connection that misses `misses` pongs one after the other is closed, so the server finds a
83
+ * client that went silent (roadmap R3: before, only the client sent pings). Browsers and the
84
+ * `ws` client answer a ping without code. `false` turns it off.
85
+ * @default { interval: seconds(15), misses: 2 } (`TIMER_DEFAULTS.webSocketServer`)
86
+ */
87
+ heartbeat?: { interval?: Duration; misses?: number } | false;
88
+
89
+ /**
90
+ * The time until the first frame of a call that the server sends to a client (roadmap R24).
91
+ * @default seconds(30)
92
+ */
93
+ reverseRequestTimeout?: Duration;
78
94
  }
79
95
 
80
96
  /**
@@ -85,7 +101,10 @@ export interface WebSocketMessage<TReq = unknown> {
85
101
  id: string;
86
102
 
87
103
  /** Message type */
88
- type: "request" | "response" | "error" | "stream" | "cancel" | "credit" | "ping" | "pong" | "server-request" | "server-response" | "event";
104
+ type: "hello" | "request" | "response" | "error" | "stream" | "cancel" | "credit" | "ping" | "pong" | "server-request" | "server-response" | "event";
105
+
106
+ /** The protocol version of a `hello` frame (roadmap R25). */
107
+ protocol?: number;
89
108
 
90
109
  /** RPC method (for requests) */
91
110
  method?: {
@@ -0,0 +1,453 @@
1
+ /**
2
+ * The calls over one WebSocket in the two directions (roadmap R24).
3
+ *
4
+ * A WebSocket connection carries the calls of each side to the other side, with the same frames:
5
+ * a `request`, then a `response`, an `error`, or `stream` frames that end with `stream.done`. The
6
+ * reader of a stream gives `credit`, and the caller can `cancel`. `OutgoingCalls` is the side that
7
+ * sends calls. `IncomingCalls` is the side that serves them with a `RequestHandler`, so a call runs
8
+ * through `invokeProcedure` (validation, the expose rule, the capability, the deadline of
9
+ * `procedure-timeout`). The server serves the calls of the client, and the client can serve the
10
+ * calls of the server: before, the reverse calls had frames of their own (`server-request`,
11
+ * `server-response`), with one value, no stream, no cancel and no signal.
12
+ */
13
+
14
+ import type { Message, ResponseItem } from "../../../client/types.js";
15
+ import type { RequestHandler, ServerRequest } from "../../../server/types.js";
16
+ import type { WebSocketMessage } from "../server/types.js";
17
+ import { withoutInternalKeys } from "../../metadata.js";
18
+ import { DELIVERY_METADATA_KEY } from "../../../client/middleware/items.js";
19
+
20
+ /**
21
+ * A queue of response items that one reader takes in order: push, then end or fail. The reader
22
+ * waits for the next item without polling.
23
+ */
24
+ export class ItemQueue<T> {
25
+ private items: T[] = [];
26
+ private ended = false;
27
+ private failure: { error: Error } | undefined;
28
+ private wake: (() => void) | undefined;
29
+
30
+ push(item: T): void {
31
+ this.items.push(item);
32
+ this.notify();
33
+ }
34
+
35
+ end(): void {
36
+ this.ended = true;
37
+ this.notify();
38
+ }
39
+
40
+ fail(error: Error): void {
41
+ this.failure = { error };
42
+ this.notify();
43
+ }
44
+
45
+ private notify(): void {
46
+ const wake = this.wake;
47
+ this.wake = undefined;
48
+ wake?.();
49
+ }
50
+
51
+ async *[Symbol.asyncIterator](): AsyncGenerator<T, void, undefined> {
52
+ for (;;) {
53
+ if (this.items.length > 0) {
54
+ yield this.items.shift()!;
55
+ continue;
56
+ }
57
+ if (this.failure) throw this.failure.error;
58
+ if (this.ended) return;
59
+ await new Promise<void>((resolve) => {
60
+ this.wake = resolve;
61
+ });
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * The credit of a stream (deep dive TRN-3): the number of items that the reader has room for.
68
+ * The stream takes one for each item that it sends, and waits when it has none.
69
+ */
70
+ export class StreamCredit {
71
+ private wake: (() => void) | undefined;
72
+
73
+ constructor(private credit: number) {}
74
+
75
+ grant(n: number): void {
76
+ this.credit += n;
77
+ const wake = this.wake;
78
+ this.wake = undefined;
79
+ wake?.();
80
+ }
81
+
82
+ /** Take one item of credit. Returns false when the signal aborted the wait. */
83
+ async take(signal: AbortSignal): Promise<boolean> {
84
+ while (this.credit <= 0) {
85
+ if (signal.aborted) return false;
86
+ await new Promise<void>((resolve) => {
87
+ const onAbort = (): void => resolve();
88
+ signal.addEventListener("abort", onAbort, { once: true });
89
+ this.wake = () => {
90
+ signal.removeEventListener("abort", onAbort);
91
+ resolve();
92
+ };
93
+ });
94
+ }
95
+ if (signal.aborted) return false;
96
+ this.credit--;
97
+ return true;
98
+ }
99
+ }
100
+
101
+ /** The error of a failed stream or handler: its code (a string) and its retryable flag. */
102
+ export function errorStatus(error: unknown): { type: "error"; code: string; message: string; retryable: boolean } {
103
+ const code = (error as { code?: unknown } | undefined)?.code;
104
+ const retryable = (error as { retryable?: unknown } | undefined)?.retryable;
105
+ return {
106
+ type: "error",
107
+ code: typeof code === "string" ? code : "HANDLER_ERROR",
108
+ message: error instanceof Error ? error.message : String(error),
109
+ // Before, every stream error lost its retryable flag (deep dive TRN-7)
110
+ retryable: typeof retryable === "boolean" ? retryable : false,
111
+ };
112
+ }
113
+
114
+ /** A text of an error message of a frame: a string, or the JSON of another value. */
115
+ function textOf(value: unknown): string {
116
+ if (typeof value === "string") return value;
117
+ return value === undefined ? "Unknown error" : JSON.stringify(value);
118
+ }
119
+
120
+ /** The response item of a `response` or an `error` frame, for the caller's message id. */
121
+ export function itemOfFrame(frame: WebSocketMessage, messageId: string): ResponseItem<unknown> {
122
+ let status: ResponseItem<unknown>["status"];
123
+ if (frame.status && frame.status.type === "error") {
124
+ status = { type: "error", code: String(frame.status.code), message: textOf(frame.status.message), retryable: frame.status.retryable || false };
125
+ } else if (frame.status && frame.status.type === "success") {
126
+ status = { type: "success", code: Number(frame.status.code) };
127
+ } else if (frame.type === "error") {
128
+ status = {
129
+ type: "error",
130
+ code: String(frame.error?.code || "UNKNOWN_ERROR"),
131
+ message: textOf(frame.error?.message),
132
+ retryable: frame.error?.retryable || false,
133
+ };
134
+ } else {
135
+ status = { type: "success", code: 200 };
136
+ }
137
+ return { id: messageId, status, payload: frame.payload, metadata: frame.metadata || {} };
138
+ }
139
+
140
+ /** A call that this side sent and that waits for frames. */
141
+ interface PendingCall {
142
+ /** The id of the caller's message: the response items carry it */
143
+ messageId: string;
144
+ /** The response items of the call, in order */
145
+ queue: ItemQueue<ResponseItem<unknown>>;
146
+ /** The time limit until the first frame */
147
+ timeout?: ReturnType<typeof setTimeout> | undefined;
148
+ }
149
+
150
+ /** The options of `OutgoingCalls`. */
151
+ export interface OutgoingCallsOptions {
152
+ /** This function sends a frame on the socket. */
153
+ send: (frame: WebSocketMessage) => void;
154
+ /** True while the socket is open. */
155
+ isOpen: () => boolean;
156
+ /** The time until the first frame of a call, in milliseconds. 0 or less: no limit. */
157
+ requestTimeoutMs: number;
158
+ /** The number of stream items that the reader can hold. */
159
+ streamWindow: number;
160
+ /** The start of the wire id of each call: the two sides use different ones. */
161
+ idPrefix: string;
162
+ }
163
+
164
+ /** The calls that this side sends on one socket. */
165
+ export class OutgoingCalls {
166
+ private readonly pending = new Map<string, PendingCall>();
167
+ private seq = 0;
168
+
169
+ constructor(private readonly options: OutgoingCallsOptions) {}
170
+
171
+ /** The number of calls that wait for their frames. */
172
+ get size(): number {
173
+ return this.pending.size;
174
+ }
175
+
176
+ /** This function sends a call, and gives its response items. */
177
+ async *call<TReq, TRes>(message: Message<TReq>): AsyncGenerator<ResponseItem<TRes>, void, undefined> {
178
+ if (message.signal?.aborted) {
179
+ yield abortedItem<TRes>(message.id);
180
+ return;
181
+ }
182
+ if (!this.options.isOpen()) {
183
+ // The call did not leave: a retry can send it
184
+ yield {
185
+ id: message.id,
186
+ status: { type: "error", code: "CONNECTION_CLOSED", message: "The connection is closed", retryable: true },
187
+ payload: null as TRes,
188
+ metadata: { [DELIVERY_METADATA_KEY]: "not-sent" },
189
+ };
190
+ return;
191
+ }
192
+ const wireId = `${this.options.idPrefix}${message.id}~${++this.seq}`;
193
+ const window = this.options.streamWindow;
194
+ const queue = new ItemQueue<ResponseItem<unknown>>();
195
+ const pending: PendingCall = { messageId: message.id, queue };
196
+ const limited = Number.isFinite(this.options.requestTimeoutMs) && this.options.requestTimeoutMs > 0;
197
+ if (limited) pending.timeout = setTimeout(() => {
198
+ if (this.pending.get(wireId) !== pending) return;
199
+ this.pending.delete(wireId);
200
+ this.sendCancel(wireId);
201
+ queue.push({
202
+ id: message.id,
203
+ status: { type: "error", code: "TIMEOUT", message: `Request timeout after ${this.options.requestTimeoutMs}ms`, retryable: false },
204
+ payload: null,
205
+ metadata: { [DELIVERY_METADATA_KEY]: "indeterminate" },
206
+ });
207
+ queue.end();
208
+ }, this.options.requestTimeoutMs);
209
+ this.pending.set(wireId, pending);
210
+
211
+ const onAbort = (): void => {
212
+ if (this.pending.get(wireId) !== pending) return;
213
+ this.pending.delete(wireId);
214
+ if (pending.timeout) clearTimeout(pending.timeout);
215
+ this.sendCancel(wireId);
216
+ queue.push(abortedItem(message.id));
217
+ queue.end();
218
+ };
219
+ message.signal?.addEventListener("abort", onAbort, { once: true });
220
+
221
+ try {
222
+ this.options.send({
223
+ id: wireId,
224
+ type: "request",
225
+ method: message.method,
226
+ payload: message.payload,
227
+ // The internal metadata keys stay in this process (deep dive TRN-11)
228
+ metadata: withoutInternalKeys(message.metadata),
229
+ credit: window,
230
+ });
231
+ let taken = 0;
232
+ for await (const item of queue) {
233
+ yield item as ResponseItem<TRes>;
234
+ // The reader took the item: give the other side credit, in batches of half the window
235
+ if (++taken >= window / 2 && this.pending.get(wireId) === pending) {
236
+ this.sendCredit(wireId, taken);
237
+ taken = 0;
238
+ }
239
+ }
240
+ } finally {
241
+ message.signal?.removeEventListener("abort", onAbort);
242
+ if (pending.timeout) clearTimeout(pending.timeout);
243
+ if (this.pending.get(wireId) === pending) {
244
+ // The reader stopped before the end: the other side stops the stream
245
+ this.pending.delete(wireId);
246
+ this.sendCancel(wireId);
247
+ }
248
+ }
249
+ }
250
+
251
+ /**
252
+ * This function takes a `response`, `error` or `stream` frame of a call that this side sent.
253
+ * It gives false for another frame.
254
+ */
255
+ receive(frame: WebSocketMessage): boolean {
256
+ if (frame.type !== "response" && frame.type !== "error" && frame.type !== "stream") return false;
257
+ const pending = this.pending.get(frame.id);
258
+ if (!pending) return false;
259
+ // The first frame arrived: the time limit ends. A stream can stay open longer.
260
+ if (pending.timeout) {
261
+ clearTimeout(pending.timeout);
262
+ pending.timeout = undefined;
263
+ }
264
+ if (frame.type === "stream" && !frame.status) {
265
+ if (frame.stream?.done) {
266
+ this.pending.delete(frame.id);
267
+ pending.queue.end();
268
+ return true;
269
+ }
270
+ pending.queue.push({ id: pending.messageId, status: { type: "success", code: 200 }, payload: frame.payload, metadata: frame.metadata || {} });
271
+ return true;
272
+ }
273
+ this.pending.delete(frame.id);
274
+ pending.queue.push(itemOfFrame(frame, pending.messageId));
275
+ pending.queue.end();
276
+ return true;
277
+ }
278
+
279
+ /**
280
+ * This function fails each call that waits: the socket closed. The other side can have run
281
+ * them, so their outcome is indeterminate (R1).
282
+ */
283
+ failAll(reason: string): void {
284
+ for (const pending of this.pending.values()) {
285
+ if (pending.timeout) clearTimeout(pending.timeout);
286
+ pending.queue.fail(new Error(reason));
287
+ }
288
+ this.pending.clear();
289
+ }
290
+
291
+ private sendCancel(id: string): void {
292
+ if (this.options.isOpen()) this.options.send({ id, type: "cancel" });
293
+ }
294
+
295
+ private sendCredit(id: string, credit: number): void {
296
+ if (this.options.isOpen()) this.options.send({ id, type: "credit", credit });
297
+ }
298
+ }
299
+
300
+ /** The options of `IncomingCalls`. */
301
+ export interface IncomingCallsOptions {
302
+ /** The handler that serves each call: a `ProcedureServer`, a `Server`, or a function object. */
303
+ handler: RequestHandler;
304
+ /** This function sends a frame on the socket. */
305
+ send: (frame: WebSocketMessage) => void;
306
+ /** This function sends a frame and waits while the send buffer of the socket is full. */
307
+ sendWithBackpressure?: ((frame: WebSocketMessage, signal: AbortSignal) => Promise<void>) | undefined;
308
+ /** True while the socket is open. */
309
+ isOpen: () => boolean;
310
+ /** This function makes the server request of a `request` frame: the metadata, the credentials. */
311
+ requestOf: (frame: WebSocketMessage, signal: AbortSignal) => ServerRequest;
312
+ }
313
+
314
+ /** The calls that the other side sends on one socket, served by a handler. */
315
+ export class IncomingCalls {
316
+ /** The calls that run, by wire id: a cancel frame or a closed socket aborts them */
317
+ private readonly inflight = new Map<string, AbortController>();
318
+ /** The flow control of the streams that run, by wire id */
319
+ private readonly credits = new Map<string, StreamCredit>();
320
+
321
+ constructor(private readonly options: IncomingCallsOptions) {}
322
+
323
+ /** The number of calls that run. */
324
+ get size(): number {
325
+ return this.inflight.size;
326
+ }
327
+
328
+ /**
329
+ * This function takes a `request`, `cancel` or `credit` frame of the other side. It gives false
330
+ * for another frame.
331
+ */
332
+ receive(frame: WebSocketMessage): boolean {
333
+ if (frame.type === "cancel") {
334
+ this.inflight.get(frame.id)?.abort();
335
+ this.inflight.delete(frame.id);
336
+ return true;
337
+ }
338
+ if (frame.type === "credit") {
339
+ if (typeof frame.credit === "number" && frame.credit > 0) this.credits.get(frame.id)?.grant(frame.credit);
340
+ return true;
341
+ }
342
+ if (frame.type !== "request") return false;
343
+ if (!frame.id || !frame.method) {
344
+ this.sendError(frame.id || "unknown", "Invalid message format", "INVALID_REQUEST");
345
+ return true;
346
+ }
347
+ // A request id names one request while it runs. A second request with the id would take the
348
+ // first one's controller: its stream could not be cancelled (deep dive TRN-9).
349
+ if (this.inflight.has(frame.id)) {
350
+ this.sendError(frame.id, "A request with this id is still running", "DUPLICATE_REQUEST");
351
+ return true;
352
+ }
353
+ void this.serve(frame);
354
+ return true;
355
+ }
356
+
357
+ /** This function aborts each call that runs: the socket closed, or the side stops. */
358
+ abortAll(): void {
359
+ for (const controller of this.inflight.values()) controller.abort();
360
+ this.inflight.clear();
361
+ this.credits.clear();
362
+ }
363
+
364
+ private async serve(frame: WebSocketMessage): Promise<void> {
365
+ const controller = new AbortController();
366
+ this.inflight.set(frame.id, controller);
367
+ try {
368
+ const response = await this.options.handler.handle(this.options.requestOf(frame, controller.signal));
369
+ if (response.stream) {
370
+ const credit = typeof frame.credit === "number" && frame.credit > 0 ? new StreamCredit(frame.credit) : undefined;
371
+ if (credit) this.credits.set(frame.id, credit);
372
+ await this.sendStream(frame.id, response.stream, controller.signal, credit);
373
+ } else {
374
+ const out: WebSocketMessage = {
375
+ id: frame.id,
376
+ type: response.status.type === "success" ? "response" : "error",
377
+ payload: response.payload,
378
+ metadata: response.metadata,
379
+ status: response.status,
380
+ };
381
+ if (response.status.type === "error") {
382
+ out.error = { code: response.status.code, message: response.status.message || "Unknown error", retryable: response.status.retryable };
383
+ }
384
+ this.send(out);
385
+ }
386
+ } catch (error) {
387
+ this.sendError(frame.id, error instanceof Error ? error.message : "Internal server error", "INTERNAL_ERROR");
388
+ } finally {
389
+ // Delete only this request's entries (deep dive TRN-9)
390
+ if (this.inflight.get(frame.id) === controller) this.inflight.delete(frame.id);
391
+ this.credits.delete(frame.id);
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Send a stream (BUGS-2026-07 H5): one `stream` frame for each item, then a frame with
397
+ * `stream.done`. An error of the stream ends it with an `error` frame. When the signal aborts
398
+ * (a cancel frame, a closed socket), the loop stops, and the handler of the stream gets
399
+ * return(). The stream takes its next item only when the reader has credit for it.
400
+ */
401
+ private async sendStream(id: string, stream: AsyncIterable<unknown>, signal: AbortSignal, credit: StreamCredit | undefined): Promise<void> {
402
+ const iterator = stream[Symbol.asyncIterator]();
403
+ let done = false;
404
+ try {
405
+ for (;;) {
406
+ if (credit && !(await credit.take(signal))) return;
407
+ if (signal.aborted || !this.options.isOpen()) return;
408
+ const result = await iterator.next();
409
+ if (result.done) {
410
+ done = true;
411
+ break;
412
+ }
413
+ if (signal.aborted || !this.options.isOpen()) return;
414
+ const item: WebSocketMessage = { id, type: "stream", payload: result.value, stream: { done: false } };
415
+ if (this.options.sendWithBackpressure) await this.options.sendWithBackpressure(item, signal);
416
+ else this.send(item);
417
+ }
418
+ this.send({ id, type: "stream", stream: { done: true } });
419
+ } catch (error) {
420
+ done = true;
421
+ if (signal.aborted) return;
422
+ const status = errorStatus(error);
423
+ this.send({
424
+ id,
425
+ type: "error",
426
+ status,
427
+ error: { code: status.code, message: status.message, retryable: status.retryable },
428
+ stream: { done: true },
429
+ });
430
+ } finally {
431
+ // The loop stopped before the end: the handler gets return()
432
+ if (!done) await iterator.return?.();
433
+ }
434
+ }
435
+
436
+ private send(frame: WebSocketMessage): void {
437
+ if (this.options.isOpen()) this.options.send(frame);
438
+ }
439
+
440
+ private sendError(id: string, message: string, code: string): void {
441
+ this.send({
442
+ id,
443
+ type: "error",
444
+ status: { type: "error", code, message, retryable: false },
445
+ error: { code, message, retryable: false },
446
+ });
447
+ }
448
+ }
449
+
450
+ /** The error item of a call that its caller aborted. A retry does not repeat it. */
451
+ function abortedItem<TRes>(id: string): ResponseItem<TRes> {
452
+ return { id, status: { type: "error", code: "ABORTED", message: "Request was aborted", retryable: false }, payload: null as TRes, metadata: {} };
453
+ }
package/src/browser.ts CHANGED
@@ -75,8 +75,13 @@ export {
75
75
  // ============================================================================
76
76
  export { Server } from "./server/server.js";
77
77
  export type { ServerOptions } from "./server/server.js";
78
- export { ProcedureServer, createProcedureServer } from "./server/procedure-server.js";
79
- export type { ProcedureServerOptions, StorageConfig } from "./server/procedure-server.js";
78
+ export { ProcedureServer, createProcedureServer, STREAM_METADATA_KEY, isUnboundedStream, tokenMatches } from "./server/procedure-server.js";
79
+ // The credentials of a network request (roadmap R18)
80
+ export { credentialsOf, bearerOf, withoutSecretHeaders } from "./server/request-auth.js";
81
+ // The procedures of a host: host.health, host.info, host.metrics (roadmap R19)
82
+ export { hostProcedures } from "./server/host-procedures.js";
83
+ export type { HostSource } from "./server/host-procedures.js";
84
+ export type { ProcedureServerOptions, StorageConfig, StopOptions, UseStorageOptions } from "./server/procedure-server.js";
80
85
  export {
81
86
  HandlerNotFoundError,
82
87
  ServerError,
@@ -85,6 +90,9 @@ export { pathToMethod, methodToPath } from "./server/method.js";
85
90
  export type {
86
91
  ServerRequest,
87
92
  ServerResponse,
93
+ RequestHandler,
94
+ Credentials,
95
+ Principal,
88
96
  ServerHandler,
89
97
  ServerMiddleware,
90
98
  ServerContext,
@@ -191,6 +199,7 @@ export {
191
199
  // Meta-procedures for runtime definition
192
200
  defineProcedureProcedure,
193
201
  getProcedureProcedure,
202
+ manifestProcedure,
194
203
  listProceduresProcedure,
195
204
  deleteProcedureProcedure,
196
205
  metaProcedures,
@@ -222,6 +231,7 @@ export type {
222
231
  ProcedureError,
223
232
  RepositoryProvider,
224
233
  CollectionStorage,
234
+ StorageChanges,
225
235
  StorageMetadata,
226
236
  ProcedureModule,
227
237
  ModuleInput,
@@ -271,6 +281,7 @@ export type {
271
281
  InvokeOptions,
272
282
  ProcedureOutput,
273
283
  InvocationErrorCode,
284
+ ForbiddenReason,
274
285
  Invocation,
275
286
  InvocationChanges,
276
287
  InvocationNext,
@@ -294,7 +305,44 @@ export {
294
305
  explainInvocation,
295
306
  checkEffects,
296
307
  } from "./procedures/capability.js";
297
- export { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects } from "./procedures/effects.js";
308
+ export { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects, INFERRED_EFFECTS_METADATA_KEY, inferredEffectsOf, effectsValue } from "./procedures/effects.js";
309
+ // The clock of the timers, and the state machines as data (roadmap R35)
310
+ export { realClock } from "./clock.js";
311
+ export type { Cancel, Clock } from "./clock.js";
312
+ export { WEBSOCKET_CONNECTION_MACHINE, CIRCUIT_BREAKER_MACHINE, replayTransitions } from "./machines.js";
313
+ export type { MachineDocument, MachineTransition, MachineTransitionDef, MachineTimer, MachineDuration } from "./machines.js";
314
+ // The default times of the core, as data (roadmap R34)
315
+ export { TIMER_DEFAULTS } from "./timers.js";
316
+ export type { TimerDefaults } from "./timers.js";
317
+ // The transport over a message port: a Web Worker, a MessageChannel (roadmap R27)
318
+ export { PortTransport } from "./adapters/port/transport.js";
319
+ export type { PortLike, PortTransportOptions } from "./adapters/port/transport.js";
320
+ // The W3C trace context: one trace from the client to the handler (roadmap R16)
321
+ export { TRACEPARENT_METADATA_KEY, formatTraceparent, parseTraceparent, newTraceId, newSpanId, isW3cIds } from "./tracing/traceparent.js";
322
+ export type { TraceContext } from "./tracing/traceparent.js";
323
+ // Wire protocol v1: the version and the one list of error codes (roadmap R25)
324
+ export { PROTOCOL_VERSION, PROTOCOL_HEADER, ERROR_CODES, isProtocolErrorCode, httpStatusOf, protocolVersionOf } from "./protocol.js";
325
+ export type { ProtocolErrorCode, ErrorCodeInfo } from "./protocol.js";
326
+ // The deadline of a call: the remaining time goes as procedure-timeout (roadmap R10)
327
+ export { TIMEOUT_METADATA_KEY, DEADLINE_METADATA_KEY } from "./client/deadline.js";
328
+ // Typed durations: each public option that is a time takes a Duration (roadmap R11)
329
+ export { ms, seconds, minutes, hours, toMs, optionalMs, isDuration } from "./duration.js";
330
+ export type { Duration, TimeUnit } from "./duration.js";
331
+ // One manifest type (roadmap R17)
332
+ export { MANIFEST_FORMAT, describeProcedure as describeProcedureEntry, describeRegistry, exposedProcedures, hashOf } from "./procedures/manifest.js";
333
+ export type { ProcedureEntry, ProcedureManifest, CliDescription, DescribeOptions, DescribeRegistryOptions } from "./procedures/manifest.js";
334
+ // Idempotency of calls (roadmap R1)
335
+ export {
336
+ IDEMPOTENCY_KEY_METADATA,
337
+ MAX_IDEMPOTENCY_KEY_LENGTH,
338
+ IdempotencyCache,
339
+ idempotencyKeyOf,
340
+ idempotentIn,
341
+ effectsIn,
342
+ isRepeatable,
343
+ newIdempotencyKey,
344
+ } from "./procedures/idempotency.js";
345
+ export type { IdempotencyOptions } from "./procedures/idempotency.js";
298
346
  export type { ToolHints, CapabilityRefusal, CapabilityRefusalReason, InvocationExplanation, EffectsViolation } from "./procedures/capability.js";
299
347
  export type { CallPolicy, CallRefusal, HiddenCall } from "./procedures/policy.js";
300
348
  export { timeoutInterceptor, tracingInterceptor, metricsInterceptor } from "./procedures/interceptors.js";
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { ProcedurePath } from "../procedures/types.js";
9
+ import type { Duration } from "../duration.js";
9
10
  import type { RouteLeaf, OutputConfig } from "./consumption.js";
10
11
  import { isRouteLeafWithConfig, extractInput, extractOutputConfig } from "./consumption.js";
11
12
 
@@ -22,30 +23,30 @@ export { isRouteLeafWithConfig, extractInput, extractOutputConfig } from "./cons
22
23
  export interface RetryOverride {
23
24
  /** Maximum number of retry attempts */
24
25
  attempts?: number;
25
- /** Delay between retries in milliseconds */
26
- delay?: number;
26
+ /** Delay between retries */
27
+ delay?: Duration;
27
28
  /** Exponential backoff multiplier */
28
29
  backoffMultiplier?: number;
29
30
  /** Maximum delay between retries */
30
- maxDelay?: number;
31
+ maxDelay?: Duration;
31
32
  }
32
33
 
33
34
  /**
34
35
  * Per-call timeout configuration override.
35
36
  */
36
37
  export interface TimeoutOverride {
37
- /** Overall request timeout in milliseconds */
38
- ms?: number;
38
+ /** The overall timeout of the request */
39
+ overall?: Duration;
39
40
  /** Per-attempt timeout for retries */
40
- perAttempt?: number;
41
+ perAttempt?: Duration;
41
42
  }
42
43
 
43
44
  /**
44
45
  * Per-call cache configuration override.
45
46
  */
46
47
  export interface CacheOverride {
47
- /** Time-to-live in milliseconds */
48
- ttl?: number;
48
+ /** Time-to-live */
49
+ ttl?: Duration;
49
50
  /** Skip cache for this request */
50
51
  bypass?: boolean;
51
52
  /** Force cache refresh */
@@ -66,8 +67,7 @@ export interface MiddlewareOverrides {
66
67
 
67
68
  /**
68
69
  * This function converts the overrides of a route call to the context keys that the middleware
69
- * reads: `retry.attempts` becomes `retry.maxAttempts`, and `timeout.ms` becomes
70
- * `timeout.overall`. The other keys pass unchanged. (Before, the documented keys had no
70
+ * reads: `retry.attempts` becomes `retry.maxAttempts`. The other keys pass unchanged. (Before, the documented keys had no
71
71
  * effect: deep dive CORE-14.)
72
72
  *
73
73
  * @param overrides - The `middlewares` of a route call
@@ -84,10 +84,6 @@ export function toMiddlewareContext(overrides: MiddlewareOverrides | undefined):
84
84
  const { attempts, ...rest } = overrides.retry;
85
85
  context["retry"] = attempts === undefined ? rest : { ...rest, maxAttempts: attempts };
86
86
  }
87
- if (overrides.timeout) {
88
- const { ms, ...rest } = overrides.timeout;
89
- context["timeout"] = ms === undefined ? rest : { ...rest, overall: ms };
90
- }
91
87
  return context;
92
88
  }
93
89