@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
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The W3C trace context (roadmap R16): `traceparent` is `00-<trace id>-<span id>-<flags>`, with a
3
+ * trace id of 32 hex digits and a span id of 16. The tracing middleware of a client sends it in
4
+ * the metadata of a call (and HTTP as the `traceparent` header). The tracing interceptor of a
5
+ * server continues that trace, and gives it to the nested calls, so one trace goes from the
6
+ * client to the last handler. Before, the client and the server made two traces.
7
+ */
8
+ /** The metadata key of the W3C trace context of a call. */
9
+ export declare const TRACEPARENT_METADATA_KEY = "traceparent";
10
+ /** A trace context: the trace, the span of the caller, and the sampled flag. */
11
+ export interface TraceContext {
12
+ readonly traceId: string;
13
+ readonly spanId: string;
14
+ readonly sampled: boolean;
15
+ }
16
+ /** This function makes a new trace id: 32 random hex digits. */
17
+ export declare function newTraceId(): string;
18
+ /** This function makes a new span id: 16 random hex digits. */
19
+ export declare function newSpanId(): string;
20
+ /** True when the ids have the W3C form. */
21
+ export declare function isW3cIds(traceId: string, spanId: string): boolean;
22
+ /** The `traceparent` of a trace and a span. */
23
+ export declare function formatTraceparent(traceId: string, spanId: string, sampled?: boolean): string;
24
+ /** The trace context of a `traceparent` value, or undefined when it is not valid. */
25
+ export declare function parseTraceparent(value: unknown): TraceContext | undefined;
26
+ //# sourceMappingURL=traceparent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traceparent.d.ts","sourceRoot":"","sources":["../../src/tracing/traceparent.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,gBAAgB,CAAC;AAMtD,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAUD,gEAAgE;AAChE,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,IAAI,MAAM,CAGlC;AAED,2CAA2C;AAC3C,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,MAAM,CAEzF;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAOzE"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The W3C trace context (roadmap R16): `traceparent` is `00-<trace id>-<span id>-<flags>`, with a
3
+ * trace id of 32 hex digits and a span id of 16. The tracing middleware of a client sends it in
4
+ * the metadata of a call (and HTTP as the `traceparent` header). The tracing interceptor of a
5
+ * server continues that trace, and gives it to the nested calls, so one trace goes from the
6
+ * client to the last handler. Before, the client and the server made two traces.
7
+ */
8
+ /** The metadata key of the W3C trace context of a call. */
9
+ export const TRACEPARENT_METADATA_KEY = "traceparent";
10
+ const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
11
+ const ZERO_TRACE = "0".repeat(32);
12
+ const ZERO_SPAN = "0".repeat(16);
13
+ function randomHex(bytes) {
14
+ const values = new Uint8Array(bytes);
15
+ const crypto = globalThis.crypto;
16
+ if (crypto?.getRandomValues)
17
+ crypto.getRandomValues(values);
18
+ else
19
+ for (let index = 0; index < bytes; index++)
20
+ values[index] = Math.floor(Math.random() * 256);
21
+ return Array.from(values, (value) => value.toString(16).padStart(2, "0")).join("");
22
+ }
23
+ /** This function makes a new trace id: 32 random hex digits. */
24
+ export function newTraceId() {
25
+ const id = randomHex(16);
26
+ return id === ZERO_TRACE ? newTraceId() : id;
27
+ }
28
+ /** This function makes a new span id: 16 random hex digits. */
29
+ export function newSpanId() {
30
+ const id = randomHex(8);
31
+ return id === ZERO_SPAN ? newSpanId() : id;
32
+ }
33
+ /** True when the ids have the W3C form. */
34
+ export function isW3cIds(traceId, spanId) {
35
+ return /^[0-9a-f]{32}$/.test(traceId) && /^[0-9a-f]{16}$/.test(spanId) && traceId !== ZERO_TRACE && spanId !== ZERO_SPAN;
36
+ }
37
+ /** The `traceparent` of a trace and a span. */
38
+ export function formatTraceparent(traceId, spanId, sampled = true) {
39
+ return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
40
+ }
41
+ /** The trace context of a `traceparent` value, or undefined when it is not valid. */
42
+ export function parseTraceparent(value) {
43
+ if (typeof value !== "string")
44
+ return undefined;
45
+ const match = TRACEPARENT.exec(value.trim().toLowerCase());
46
+ if (!match)
47
+ return undefined;
48
+ const [, traceId, spanId, flags] = match;
49
+ if (traceId === ZERO_TRACE || spanId === ZERO_SPAN)
50
+ return undefined;
51
+ return { traceId: traceId, spanId: spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
52
+ }
53
+ //# sourceMappingURL=traceparent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traceparent.js","sourceRoot":"","sources":["../../src/tracing/traceparent.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAEtD,MAAM,WAAW,GAAG,kDAAkD,CAAC;AACvE,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAClC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AASjC,SAAS,SAAS,CAAC,KAAa;IAC9B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,MAAM,GAAI,UAAmF,CAAC,MAAM,CAAC;IAC3G,IAAI,MAAM,EAAE,eAAe;QAAE,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;;QACvD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE;YAAE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IACjG,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACzB,OAAO,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,SAAS;IACvB,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,MAAc;IACtD,OAAO,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,KAAK,UAAU,IAAI,MAAM,KAAK,SAAS,CAAC;AAC3H,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc,EAAE,OAAO,GAAG,IAAI;IAC/E,OAAO,MAAM,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;IACzC,IAAI,OAAO,KAAK,UAAU,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACrE,OAAO,EAAE,OAAO,EAAE,OAAQ,EAAE,MAAM,EAAE,MAAO,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3F,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/client",
4
- "version": "0.2.2",
4
+ "version": "0.3.0",
5
5
  "description": "Procedures as data for TypeScript: define typed procedures once and call them in process, over HTTP, WebSocket, the CLI or MCP. Streaming, middleware, retries, caching and a JSON program format.",
6
6
  "keywords": [
7
7
  "rpc",
@@ -90,14 +90,20 @@
90
90
  "provenance": true
91
91
  },
92
92
  "dependencies": {
93
+ "@mark1russell7/algebra": "^0.5.0",
94
+ "@mark1russell7/quantity": "^0.2.0",
95
+ "@mark1russell7/service": "^0.4.0",
93
96
  "ws": "^8.18.3",
94
97
  "zod": "^4.6.5"
95
98
  },
96
99
  "devDependencies": {
97
100
  "@mark1russell7/cue": "^1.0.0",
101
+ "@stryker-mutator/core": "10.0.0",
102
+ "@stryker-mutator/vitest-runner": "10.0.0",
98
103
  "@types/express": "^5.0.0",
99
104
  "@types/node": "^22.0.0",
100
105
  "@types/ws": "^8.18.1",
106
+ "fast-check": "^4.10.2",
101
107
  "typescript": "^7.0.2",
102
108
  "vitest": "^5.0.3"
103
109
  },
@@ -12,6 +12,21 @@ import type {
12
12
  } from "../../../client/types.js";
13
13
 
14
14
  import type { HttpTransportOptions } from "./types.js";
15
+ import { DELIVERY_METADATA_KEY, RETRY_AFTER_METADATA_KEY, isNotSentError } from "../../../client/middleware/items.js";
16
+
17
+ /**
18
+ * The wait that an error response asks for, in milliseconds: `retryAfterMs` of the body of the
19
+ * server transport, or else the Retry-After header (seconds, or an HTTP date).
20
+ */
21
+ function retryAfterMsOf(header: string | null, body: unknown): number | undefined {
22
+ const fromBody = (body as { retryAfterMs?: unknown } | null | undefined)?.retryAfterMs;
23
+ if (typeof fromBody === "number" && Number.isFinite(fromBody) && fromBody >= 0) return fromBody;
24
+ if (header === null || header.trim() === "") return undefined;
25
+ const seconds = Number(header);
26
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
27
+ const date = Date.parse(header);
28
+ return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
29
+ }
15
30
 
16
31
  /** The media type of a stream response: one JSON value on each line. */
17
32
  const NDJSON = "application/x-ndjson";
@@ -32,6 +47,8 @@ import {
32
47
  createAbortError,
33
48
  createErrorFromException,
34
49
  } from "../shared/index.js";
50
+ import { optionalMs } from "../../../duration.js";
51
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "../../../protocol.js";
35
52
 
36
53
  /**
37
54
  * HTTP Client Transport
@@ -74,7 +91,7 @@ export class HttpTransport implements Transport {
74
91
  this.headerConverter =
75
92
  options.headerConverter || createDefaultHeaderConverter();
76
93
  this.defaultHeaders = options.defaultHeaders || {};
77
- this.timeout = options.timeout;
94
+ this.timeout = optionalMs(options.timeout, "timeout");
78
95
  }
79
96
 
80
97
  /**
@@ -123,6 +140,8 @@ export class HttpTransport implements Transport {
123
140
  [HTTPHeaders.CONTENT_TYPE]: "application/json",
124
141
  // A streaming procedure answers with NDJSON; other procedures with JSON
125
142
  Accept: `${NDJSON}, application/json`,
143
+ // The protocol version of this client (roadmap R25)
144
+ [PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
126
145
  ...this.defaultHeaders,
127
146
  ...metadataHeaders,
128
147
  },
@@ -153,6 +172,13 @@ export class HttpTransport implements Transport {
153
172
  // the client kept only the HTTP status ("HTTP 500").
154
173
  const status = this.httpStatusToUniversal(response.status as HTTPStatus, responseBody);
155
174
  finished = true;
175
+ if (status.type === "error") {
176
+ const retryAfter = retryAfterMsOf(response.headers.get("Retry-After"), responseBody);
177
+ if (retryAfter !== undefined) metadata[RETRY_AFTER_METADATA_KEY] = retryAfter;
178
+ // The reason of a FORBIDDEN error (roadmap R25)
179
+ const reason = (responseBody as { reason?: unknown } | null | undefined)?.reason;
180
+ if (typeof reason === "string") metadata["reason"] = reason;
181
+ }
156
182
 
157
183
  // Yield single response item
158
184
  yield {
@@ -183,7 +209,9 @@ export class HttpTransport implements Transport {
183
209
  retryable: errorDetails.retryable,
184
210
  },
185
211
  payload: null as TRes,
186
- metadata: {},
212
+ // A refused connection sent nothing. After a reset or a timeout, the server can have
213
+ // the request, so only an idempotent call can go again (R1).
214
+ metadata: { [DELIVERY_METADATA_KEY]: isNotSentError(error) ? "not-sent" : "indeterminate" },
187
215
  };
188
216
  } finally {
189
217
  if (timeoutId) clearTimeout(timeoutId);
@@ -251,7 +279,7 @@ export class HttpTransport implements Transport {
251
279
  id,
252
280
  status: { type: "error", code: "STREAM_ENDED", message: "The stream ended without its end line", retryable: true },
253
281
  payload: null as TRes,
254
- metadata,
282
+ metadata: { ...metadata, [DELIVERY_METADATA_KEY]: "indeterminate" },
255
283
  };
256
284
  } finally {
257
285
  reader.releaseLock();
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { UrlStrategy, HttpMethodStrategy, HeaderConverter } from "../shared/index.js";
9
+ import type { Duration } from "../../../duration.js";
9
10
 
10
11
  /**
11
12
  * HTTP Client Transport Configuration
@@ -28,6 +29,6 @@ export interface HttpTransportOptions {
28
29
  /** Default headers applied to all requests */
29
30
  defaultHeaders?: Record<string, string>;
30
31
 
31
- /** Timeout for fetch requests (milliseconds) */
32
- timeout?: number;
32
+ /** The time to the first item of a response, for example `seconds(30)` */
33
+ timeout?: Duration;
33
34
  }
@@ -10,16 +10,23 @@ import { createServer } from "http";
10
10
  import type { Server as HttpServer } from "http";
11
11
  import type { ServerTransport, ServerRequest, ServerResponse } from "../../../server/types.js";
12
12
  import type { Metadata } from "../../../client/types.js";
13
- import type { Server } from "../../../server/index.js";
13
+ import type { RequestHandler } from "../../../server/types.js";
14
14
  import { HTTPMethod, HTTPHeaders, decodeMetadataHeader, METADATA_HEADER_LIMIT } from "../shared/index.js";
15
15
  import { ERROR_REGISTRY } from "../../../client/errors/index.js";
16
16
  import type { HttpServerTransportOptions } from "./types.js";
17
17
  import { createPatternServerUrlStrategy } from "./strategies.js";
18
18
  import { checkBrowserRequest, type BrowserGuardOptions } from "../../../server/browser-guard.js";
19
+ import { bearerOf, withoutSecretHeaders } from "../../../server/request-auth.js";
20
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION, httpStatusOf, protocolVersionOf } from "../../../protocol.js";
19
21
 
20
22
  /** The media type of a stream response: one JSON value on each line. */
21
23
  const NDJSON = "application/x-ndjson";
22
24
 
25
+ /** True when a stream response can run without an end (`STREAM_METADATA_KEY` of procedure-server.ts). */
26
+ function isUnboundedStream(response: { metadata?: Record<string, unknown> | undefined }): boolean {
27
+ return response.metadata?.["stream"] === "unbounded";
28
+ }
29
+
23
30
  /** The metadata keys that the transport sets: a query parameter or a header cannot replace them. */
24
31
  const RESERVED_METADATA = new Set(["headers", "query", "params"]);
25
32
 
@@ -64,24 +71,6 @@ function write(res: Response, chunk: string, signal: AbortSignal): Promise<void>
64
71
  });
65
72
  }
66
73
 
67
- /**
68
- * The HTTP status of the error codes of a procedure invocation (they are not in the error
69
- * registry, so before, a validation error was a 500).
70
- */
71
- const INVOCATION_HTTP_STATUS: Record<string, number> = {
72
- VALIDATION_ERROR: 400,
73
- NOT_EXPOSED: 403,
74
- NOT_DECLARED: 403,
75
- FORBIDDEN: 403,
76
- NOT_FOUND: 404,
77
- NO_HANDLER: 404,
78
- ABORTED: 499,
79
- TIMEOUT: 504,
80
- OUTPUT_VALIDATION_ERROR: 500,
81
- NO_OUTPUT: 500,
82
- HANDLER_ERROR: 500,
83
- };
84
-
85
74
  /**
86
75
  * HTTP server transport adapter for Express.
87
76
  *
@@ -126,7 +115,7 @@ export class HttpServerTransport implements ServerTransport {
126
115
  corsOptions: HttpServerTransportOptions["corsOptions"] | undefined;
127
116
  httpServer: HttpServer | undefined;
128
117
  };
129
- private server: Server;
118
+ private server: RequestHandler;
130
119
  /** The controllers of the requests in progress: stop() aborts them (deep dive TRN-15) */
131
120
  private openRequests = new Set<AbortController>();
132
121
  private stopped = false;
@@ -134,7 +123,7 @@ export class HttpServerTransport implements ServerTransport {
134
123
  /** The browser checks of each procedure call (see `server/browser-guard.ts`) */
135
124
  private readonly guard: BrowserGuardOptions;
136
125
 
137
- constructor(server: Server, options: HttpServerTransportOptions = {}) {
126
+ constructor(server: RequestHandler, options: HttpServerTransportOptions = {}) {
138
127
  this.server = server;
139
128
  const basePath = options.basePath ?? "/api";
140
129
  this.options = {
@@ -198,7 +187,8 @@ export class HttpServerTransport implements ServerTransport {
198
187
  HTTPHeaders.TRACE_ID,
199
188
  HTTPHeaders.SPAN_ID,
200
189
  HTTPHeaders.PARENT_SPAN_ID,
201
- HTTPHeaders.TIMEOUT,
190
+ "traceparent",
191
+ PROTOCOL_HEADER,
202
192
  HTTPHeaders.METADATA,
203
193
  "collection",
204
194
  "collectionName",
@@ -256,6 +246,18 @@ export class HttpServerTransport implements ServerTransport {
256
246
  * Converts to unified RPC format, processes, and sends response.
257
247
  */
258
248
  private async handleHttpRequest(req: Request, res: Response): Promise<void> {
249
+ // The protocol version of this server, on each response (roadmap R25)
250
+ res.setHeader(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
251
+ const version = protocolVersionOf(req.headers[PROTOCOL_HEADER.toLowerCase()]);
252
+ if (version !== undefined && version > PROTOCOL_VERSION) {
253
+ res.status(400).json({
254
+ error: `This server speaks the protocol ${PROTOCOL_VERSION}, and the request uses ${version}`,
255
+ code: "PROTOCOL_VERSION",
256
+ retryable: false,
257
+ protocol: PROTOCOL_VERSION,
258
+ });
259
+ return;
260
+ }
259
261
  if (this.stopped) {
260
262
  res.status(503).json({ error: "Server stopped", code: "SERVER_STOPPED", retryable: true });
261
263
  return;
@@ -360,6 +362,26 @@ export class HttpServerTransport implements ServerTransport {
360
362
  ): Promise<void> {
361
363
  const ndjson = String(req.headers.accept ?? "").includes(NDJSON);
362
364
 
365
+ if (!ndjson && isUnboundedStream(response)) {
366
+ // The last item of a stream without an end never comes: refuse the call, and end the stream
367
+ // (roadmap R15: before, the request waited until a timeout)
368
+ void Promise.resolve(stream[Symbol.asyncIterator]().return?.()).catch(() => undefined);
369
+ this.serverResponseToHttp(
370
+ {
371
+ id: response.id,
372
+ status: {
373
+ type: "error",
374
+ code: "NOT_ACCEPTABLE",
375
+ message: `This procedure gives a stream that can run without an end. Send "Accept: ${NDJSON}" to read its items.`,
376
+ retryable: false,
377
+ },
378
+ metadata: {},
379
+ },
380
+ res
381
+ );
382
+ return;
383
+ }
384
+
363
385
  if (!ndjson) {
364
386
  let last: { value: unknown } | undefined;
365
387
  try {
@@ -424,11 +446,14 @@ export class HttpServerTransport implements ServerTransport {
424
446
  const id =
425
447
  (req.headers["x-request-id"] as string) ??
426
448
  `req-${Date.now()}-${Math.random()}`;
449
+ const bearer = bearerOf(req.headers["authorization"]);
427
450
 
428
451
  // Build metadata from headers and query params. The flattened query parameters cannot
429
452
  // replace headers, query or params (before, "?headers=x" replaced metadata.headers).
453
+ // The headers without the secrets: the credentials go in request.credentials, not in the
454
+ // metadata, so a handler and a nested call do not see them (roadmap R18)
430
455
  const metadata: Metadata = {
431
- headers: req.headers as Record<string, string>,
456
+ headers: withoutSecretHeaders(req.headers as Record<string, string>),
432
457
  query: req.query,
433
458
  params: req.params,
434
459
  };
@@ -505,6 +530,8 @@ export class HttpServerTransport implements ServerTransport {
505
530
  method,
506
531
  payload,
507
532
  metadata,
533
+ // A network request: ProcedureServer examines the token (roadmap R18)
534
+ credentials: bearer === undefined ? {} : { bearer },
508
535
  };
509
536
  }
510
537
 
@@ -524,7 +551,7 @@ export class HttpServerTransport implements ServerTransport {
524
551
  // HTTP status via the error registry rather than parseInt (which yields NaN -> 500, so every
525
552
  // error became a 500). See documentation/BUGS-2026-07.md (M7).
526
553
  const mapped =
527
- INVOCATION_HTTP_STATUS[status.code] ??
554
+ httpStatusOf(status.code) ??
528
555
  (ERROR_REGISTRY as Record<string, { httpStatus?: number }>)[status.code]?.httpStatus;
529
556
  httpStatusCode = mapped ?? (status.type === "success" ? 200 : 500);
530
557
  } else if (typeof status.code === "number") {
@@ -551,10 +578,19 @@ export class HttpServerTransport implements ServerTransport {
551
578
  }
552
579
  res.json(payload);
553
580
  } else {
581
+ // The wait that the error asks for: a Retry-After header in seconds, and the exact
582
+ // milliseconds in the body (R12)
583
+ const retryAfterMs = metadata["retry-after"];
584
+ const hasRetryAfter = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0;
585
+ if (hasRetryAfter) res.setHeader("Retry-After", String(Math.ceil(retryAfterMs / 1000)));
586
+ // The reason of a FORBIDDEN error travels on each face (roadmap R25)
587
+ const reason = typeof metadata["reason"] === "string" ? metadata["reason"] : undefined;
554
588
  res.json({
555
589
  error: status.message,
556
590
  code: status.code,
557
591
  retryable: status.retryable,
592
+ ...(hasRetryAfter ? { retryAfterMs } : {}),
593
+ ...(reason !== undefined ? { reason } : {}),
558
594
  });
559
595
  }
560
596
  }
@@ -95,7 +95,7 @@ export interface HeaderConverter {
95
95
  * Implements standard conventions for converting metadata to headers:
96
96
  * - Auth: Bearer token, API key
97
97
  * - Tracing: X-Trace-Id, X-Span-Id format
98
- * - Timeouts: X-Timeout header
98
+ * - The deadline: `procedure-timeout` in X-Metadata (no X-Timeout header)
99
99
  * - Custom fields: Pass through as-is
100
100
  */
101
101
  export class DefaultHeaderConverter implements HeaderConverter {
@@ -120,7 +120,10 @@ export class DefaultHeaderConverter implements HeaderConverter {
120
120
  headers[HTTPHeaders.API_KEY] = metadata.auth.apiKey;
121
121
  }
122
122
 
123
- // Distributed Tracing
123
+ // Distributed Tracing: the W3C trace context, and the older headers (roadmap R16)
124
+ if (typeof metadata["traceparent"] === "string") {
125
+ headers["traceparent"] = metadata["traceparent"];
126
+ }
124
127
  if (metadata.tracing?.traceId) {
125
128
  headers[HTTPHeaders.TRACE_ID] = metadata.tracing.traceId;
126
129
  }
@@ -131,10 +134,8 @@ export class DefaultHeaderConverter implements HeaderConverter {
131
134
  headers[HTTPHeaders.PARENT_SPAN_ID] = metadata.tracing.parentSpanId;
132
135
  }
133
136
 
134
- // Timeouts
135
- if (metadata.timeout?.overall) {
136
- headers[HTTPHeaders.TIMEOUT] = String(metadata.timeout.overall);
137
- }
137
+ // No X-Timeout: the remaining time of the deadline goes as procedure-timeout in X-Metadata,
138
+ // which the server uses (roadmap R10). The server ignored X-Timeout.
138
139
 
139
140
  const custom: Record<string, unknown> = {};
140
141
  for (const [key, value] of Object.entries(metadata)) {
@@ -9,9 +9,41 @@ import type { Handler, LocalTransportOptions } from "./types.js";
9
9
  import { methodKey } from "./types.js";
10
10
  import type { ProcedureRegistry } from "../../../procedures/registry.js";
11
11
  import type { AnyProcedure } from "../../../procedures/types.js";
12
- import { InvocationError, invokeProcedure, isAsyncIterable } from "../../../procedures/invoke.js";
12
+ import { InvocationError, invokeProcedure, isAsyncIterable, type Interceptor } from "../../../procedures/invoke.js";
13
+ import { TIMEOUT_METADATA_KEY } from "../../../client/deadline.js";
14
+ import { narrowCapability } from "../../../procedures/capability.js";
15
+ import type { Capability, ClearanceOrder } from "../../../procedures/types.js";
13
16
  import { methodToPath } from "../../../server/method.js";
14
17
 
18
+ /** The value of `untilAborted` when the signal aborts first. */
19
+ const ABORTED: unique symbol = Symbol("aborted");
20
+
21
+ /** The value of a promise, or ABORTED when the signal aborts first. The promise runs on. */
22
+ function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T | typeof ABORTED> {
23
+ if (!signal) return promise;
24
+ if (signal.aborted) {
25
+ promise.catch(() => undefined);
26
+ return Promise.resolve(ABORTED);
27
+ }
28
+ return new Promise((resolve, reject) => {
29
+ const onAbort = (): void => {
30
+ promise.catch(() => undefined);
31
+ resolve(ABORTED);
32
+ };
33
+ signal.addEventListener("abort", onAbort, { once: true });
34
+ promise.then(
35
+ (value) => {
36
+ signal.removeEventListener("abort", onAbort);
37
+ resolve(value);
38
+ },
39
+ (error: unknown) => {
40
+ signal.removeEventListener("abort", onAbort);
41
+ reject(error);
42
+ },
43
+ );
44
+ });
45
+ }
46
+
15
47
  function successItem<TRes>(id: string, payload: unknown): ResponseItem<TRes> {
16
48
  return { id, status: { type: "success", code: 200 }, payload: payload as TRes, metadata: {} };
17
49
  }
@@ -53,10 +85,16 @@ export class LocalTransport implements Transport {
53
85
  private readonly handlers = new Map<string, Handler>();
54
86
  private readonly throwOnMissing: boolean;
55
87
  private readonly registry: ProcedureRegistry | undefined;
88
+ private readonly interceptors: readonly Interceptor[] | undefined;
89
+ private readonly capability: Capability | undefined;
90
+ private readonly clearanceOrder: ClearanceOrder | undefined;
56
91
 
57
92
  constructor(options: LocalTransportOptions = {}) {
58
93
  this.throwOnMissing = options.throwOnMissing !== false;
59
94
  this.registry = options.registry;
95
+ this.interceptors = options.interceptors;
96
+ this.capability = options.capability;
97
+ this.clearanceOrder = options.clearanceOrder;
60
98
 
61
99
  // Register pre-configured handlers. A key without a version ("users.get") gets the
62
100
  // empty version, as methodKey() makes it: before, such a key was never found.
@@ -136,21 +174,44 @@ export class LocalTransport implements Transport {
136
174
  let values: AsyncIterable<unknown> | undefined;
137
175
  let value: unknown;
138
176
  if (procedure) {
139
- const output = await invokeProcedure(procedure, message.payload, {
177
+ // The interceptors, the capability and the deadline of the call, as on a server (R14)
178
+ const timeoutMs = message.metadata[TIMEOUT_METADATA_KEY];
179
+ const capability =
180
+ typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs >= 0
181
+ ? narrowCapability(this.capability, { timeoutMs }, this.clearanceOrder)
182
+ : this.capability;
183
+ const output = await untilAborted(invokeProcedure(procedure, message.payload, {
140
184
  registry: this.registry,
141
185
  metadata: message.metadata,
142
186
  signal: message.signal,
143
- });
187
+ ...(this.interceptors ? { interceptors: this.interceptors } : {}),
188
+ ...(capability ? { capability } : {}),
189
+ ...(this.clearanceOrder ? { clearanceOrder: this.clearanceOrder } : {}),
190
+ }), message.signal);
191
+ if (output === ABORTED) {
192
+ // The caller stops waiting at once, as over HTTP and WebSocket (the conformance suite
193
+ // of roadmap R18 found this: before, a local call waited for its handler)
194
+ yield errorItem<TRes>(message.id, "ABORTED", "Request was aborted");
195
+ return;
196
+ }
144
197
  if (output.kind === "stream") values = output.items;
145
198
  else value = output.value;
146
199
  } else {
147
200
  // Execute handler (may be sync, async or an async generator)
148
- const result = await handler!(message.payload, message);
201
+ const result = await untilAborted(Promise.resolve(handler!(message.payload, message)), message.signal);
202
+ if (result === ABORTED) {
203
+ yield errorItem<TRes>(message.id, "ABORTED", "Request was aborted");
204
+ return;
205
+ }
149
206
  if (isAsyncIterable(result)) values = result;
150
207
  else value = result;
151
208
  }
152
209
 
153
210
  if (values === undefined) {
211
+ if (message.signal?.aborted) {
212
+ yield errorItem<TRes>(message.id, "ABORTED", "Request was aborted");
213
+ return;
214
+ }
154
215
  yield successItem<TRes>(message.id, value);
155
216
  return;
156
217
  }
@@ -164,7 +225,10 @@ export class LocalTransport implements Transport {
164
225
  } catch (error) {
165
226
  // The handler threw, or the invocation failed (its code: VALIDATION_ERROR, ABORTED and so on)
166
227
  const code = error instanceof InvocationError ? error.code : "HANDLER_ERROR";
167
- yield errorItem<TRes>(message.id, code, error instanceof Error ? error.message : "Unknown error");
228
+ const item = errorItem<TRes>(message.id, code, error instanceof Error ? error.message : "Unknown error");
229
+ // The reason of a FORBIDDEN error travels on each face (roadmap R25)
230
+ if (error instanceof InvocationError && error.reason) item.metadata = { reason: error.reason };
231
+ yield item;
168
232
  }
169
233
  }
170
234
 
@@ -172,6 +236,7 @@ export class LocalTransport implements Transport {
172
236
  // No resources to clean up
173
237
  }
174
238
 
239
+
175
240
  /**
176
241
  * Clear all handlers.
177
242
  */
@@ -6,6 +6,8 @@
6
6
 
7
7
  import type { Message, Method } from "../../../client/types.js";
8
8
  import type { ProcedureRegistry } from "../../../procedures/registry.js";
9
+ import type { Interceptor } from "../../../procedures/invoke.js";
10
+ import type { Capability, ClearanceOrder } from "../../../procedures/types.js";
9
11
 
10
12
  /**
11
13
  * Handler function for a method.
@@ -47,4 +49,20 @@ export interface LocalTransportOptions {
47
49
  * The procedure runs through `invokeProcedure()`, as on a server.
48
50
  */
49
51
  registry?: ProcedureRegistry;
52
+
53
+ /**
54
+ * The interceptors of each procedure that the transport runs, as on a server: policy, tracing,
55
+ * timeouts, metrics (roadmap R14). Before, a local call had none.
56
+ */
57
+ interceptors?: readonly Interceptor[] | undefined;
58
+
59
+ /**
60
+ * The capability of each call. The remaining time that a call sends (`procedure-timeout`)
61
+ * makes its deadline earlier, as on a server: a `Client` with a timeout gives a local call a
62
+ * deadline too (roadmap R14).
63
+ */
64
+ capability?: Capability | undefined;
65
+
66
+ /** The order of the clearance values of the capability. */
67
+ clearanceOrder?: ClearanceOrder | undefined;
50
68
  }
@@ -11,6 +11,7 @@ import type {
11
11
  MockTransportOptions,
12
12
  CallHistoryEntry,
13
13
  } from "./types.js";
14
+ import { optionalMs } from "../../../duration.js";
14
15
 
15
16
  /**
16
17
  * Mock transport implementation for testing.
@@ -26,8 +27,8 @@ import type {
26
27
  * ```typescript
27
28
  * // Create mock transport with latency
28
29
  * const mock = new MockTransport({
29
- * minLatency: 10,
30
- * maxLatency: 50,
30
+ * minLatency: ms(10),
31
+ * maxLatency: ms(50),
31
32
  * });
32
33
  *
33
34
  * // Mock a specific response
@@ -57,14 +58,15 @@ import type {
57
58
  export class MockTransport implements Transport {
58
59
  readonly name = "mock";
59
60
 
60
- private options: Required<MockTransportOptions>;
61
+ /** The options, with the latencies in milliseconds */
62
+ private options: Required<Omit<MockTransportOptions, "minLatency" | "maxLatency">> & { minLatency: number; maxLatency: number };
61
63
  private responses = new Map<ResponseMatcher, MockResponse>();
62
64
  private history: CallHistoryEntry[] = [];
63
65
 
64
66
  constructor(options: MockTransportOptions = {}) {
65
67
  this.options = {
66
- minLatency: options.minLatency ?? 0,
67
- maxLatency: options.maxLatency ?? 0,
68
+ minLatency: optionalMs(options.minLatency, "minLatency") ?? 0,
69
+ maxLatency: optionalMs(options.maxLatency, "maxLatency") ?? 0,
68
70
  failureRate: options.failureRate ?? 0,
69
71
  defaultErrorMessage: options.defaultErrorMessage ?? "Mock transport error",
70
72
  trackHistory: options.trackHistory ?? true,
@@ -247,7 +249,8 @@ export class MockTransport implements Transport {
247
249
 
248
250
  // Apply custom delay if specified
249
251
  if (mockResponse.delay !== undefined) {
250
- await new Promise((resolve) => setTimeout(resolve, mockResponse.delay));
252
+ const delay = optionalMs(mockResponse.delay, "delay");
253
+ await new Promise((resolve) => setTimeout(resolve, delay));
251
254
  }
252
255
 
253
256
  // Handle error response