@procedures/client 0.2.3 → 0.4.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 (369) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +77 -44
  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 +54 -22
  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 +67 -6
  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/node.d.ts +30 -0
  38. package/dist/adapters/websocket/client/node.d.ts.map +1 -0
  39. package/dist/adapters/websocket/client/node.js +32 -0
  40. package/dist/adapters/websocket/client/node.js.map +1 -0
  41. package/dist/adapters/websocket/client/transport.d.ts +47 -7
  42. package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
  43. package/dist/adapters/websocket/client/transport.js +229 -75
  44. package/dist/adapters/websocket/client/transport.js.map +1 -1
  45. package/dist/adapters/websocket/client/types.d.ts +71 -21
  46. package/dist/adapters/websocket/client/types.d.ts.map +1 -1
  47. package/dist/adapters/websocket/client/types.js.map +1 -1
  48. package/dist/adapters/websocket/server/transport.d.ts +65 -24
  49. package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
  50. package/dist/adapters/websocket/server/transport.js +192 -263
  51. package/dist/adapters/websocket/server/transport.js.map +1 -1
  52. package/dist/adapters/websocket/server/types.d.ts +20 -1
  53. package/dist/adapters/websocket/server/types.d.ts.map +1 -1
  54. package/dist/adapters/websocket/shared/rpc.d.ts +129 -0
  55. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -0
  56. package/dist/adapters/websocket/shared/rpc.js +425 -0
  57. package/dist/adapters/websocket/shared/rpc.js.map +1 -0
  58. package/dist/browser.d.ts +29 -7
  59. package/dist/browser.d.ts.map +1 -1
  60. package/dist/browser.js +26 -3
  61. package/dist/browser.js.map +1 -1
  62. package/dist/client/call-types.d.ts +10 -10
  63. package/dist/client/call-types.d.ts.map +1 -1
  64. package/dist/client/call-types.js +1 -6
  65. package/dist/client/call-types.js.map +1 -1
  66. package/dist/client/client.d.ts +6 -1
  67. package/dist/client/client.d.ts.map +1 -1
  68. package/dist/client/client.js +23 -9
  69. package/dist/client/client.js.map +1 -1
  70. package/dist/client/deadline.d.ts +38 -0
  71. package/dist/client/deadline.d.ts.map +1 -0
  72. package/dist/client/deadline.js +60 -0
  73. package/dist/client/deadline.js.map +1 -0
  74. package/dist/client/errors/factory.d.ts.map +1 -1
  75. package/dist/client/errors/factory.js +11 -7
  76. package/dist/client/errors/factory.js.map +1 -1
  77. package/dist/client/errors/http-errors.d.ts.map +1 -1
  78. package/dist/client/errors/http-errors.js +6 -0
  79. package/dist/client/errors/http-errors.js.map +1 -1
  80. package/dist/client/errors/index.d.ts +1 -1
  81. package/dist/client/errors/index.js +1 -1
  82. package/dist/client/errors/registry.d.ts +2 -1
  83. package/dist/client/errors/registry.d.ts.map +1 -1
  84. package/dist/client/errors/registry.js +72 -22
  85. package/dist/client/errors/registry.js.map +1 -1
  86. package/dist/client/index.d.ts +9 -2
  87. package/dist/client/index.d.ts.map +1 -1
  88. package/dist/client/index.js +8 -2
  89. package/dist/client/index.js.map +1 -1
  90. package/dist/client/middleware/cache.d.ts +19 -5
  91. package/dist/client/middleware/cache.d.ts.map +1 -1
  92. package/dist/client/middleware/cache.js +106 -18
  93. package/dist/client/middleware/cache.js.map +1 -1
  94. package/dist/client/middleware/circuit-breaker.d.ts +28 -10
  95. package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
  96. package/dist/client/middleware/circuit-breaker.js +35 -24
  97. package/dist/client/middleware/circuit-breaker.js.map +1 -1
  98. package/dist/client/middleware/contexts.d.ts +16 -10
  99. package/dist/client/middleware/contexts.d.ts.map +1 -1
  100. package/dist/client/middleware/contexts.js.map +1 -1
  101. package/dist/client/middleware/items.d.ts +31 -2
  102. package/dist/client/middleware/items.d.ts.map +1 -1
  103. package/dist/client/middleware/items.js +86 -6
  104. package/dist/client/middleware/items.js.map +1 -1
  105. package/dist/client/middleware/lru.d.ts +2 -0
  106. package/dist/client/middleware/lru.d.ts.map +1 -1
  107. package/dist/client/middleware/lru.js +11 -0
  108. package/dist/client/middleware/lru.js.map +1 -1
  109. package/dist/client/middleware/rate-limit.d.ts +7 -6
  110. package/dist/client/middleware/rate-limit.d.ts.map +1 -1
  111. package/dist/client/middleware/rate-limit.js +9 -7
  112. package/dist/client/middleware/rate-limit.js.map +1 -1
  113. package/dist/client/middleware/resilience.d.ts +61 -0
  114. package/dist/client/middleware/resilience.d.ts.map +1 -0
  115. package/dist/client/middleware/resilience.js +59 -0
  116. package/dist/client/middleware/resilience.js.map +1 -0
  117. package/dist/client/middleware/retry.d.ts +45 -11
  118. package/dist/client/middleware/retry.d.ts.map +1 -1
  119. package/dist/client/middleware/retry.js +102 -22
  120. package/dist/client/middleware/retry.js.map +1 -1
  121. package/dist/client/middleware/timeout.d.ts +15 -12
  122. package/dist/client/middleware/timeout.d.ts.map +1 -1
  123. package/dist/client/middleware/timeout.js +48 -46
  124. package/dist/client/middleware/timeout.js.map +1 -1
  125. package/dist/client/middleware/tracing.d.ts.map +1 -1
  126. package/dist/client/middleware/tracing.js +11 -3
  127. package/dist/client/middleware/tracing.js.map +1 -1
  128. package/dist/client/types.d.ts +9 -1
  129. package/dist/client/types.d.ts.map +1 -1
  130. package/dist/client/types.js.map +1 -1
  131. package/dist/clock.d.ts +19 -0
  132. package/dist/clock.d.ts.map +1 -0
  133. package/dist/clock.js +18 -0
  134. package/dist/clock.js.map +1 -0
  135. package/dist/duration.d.ts +37 -0
  136. package/dist/duration.d.ts.map +1 -0
  137. package/dist/duration.js +44 -0
  138. package/dist/duration.js.map +1 -0
  139. package/dist/index.d.ts +4 -0
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +4 -0
  142. package/dist/index.js.map +1 -1
  143. package/dist/machines.d.ts +90 -0
  144. package/dist/machines.d.ts.map +1 -0
  145. package/dist/machines.js +127 -0
  146. package/dist/machines.js.map +1 -0
  147. package/dist/node.d.ts +11 -0
  148. package/dist/node.d.ts.map +1 -0
  149. package/dist/node.js +10 -0
  150. package/dist/node.js.map +1 -0
  151. package/dist/procedures/capability.d.ts +3 -6
  152. package/dist/procedures/capability.d.ts.map +1 -1
  153. package/dist/procedures/capability.js +7 -8
  154. package/dist/procedures/capability.js.map +1 -1
  155. package/dist/procedures/collection/index.d.ts +3 -3
  156. package/dist/procedures/collection/index.d.ts.map +1 -1
  157. package/dist/procedures/collection/index.js +1 -1
  158. package/dist/procedures/collection/index.js.map +1 -1
  159. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  160. package/dist/procedures/collection/procedures.js +49 -1
  161. package/dist/procedures/collection/procedures.js.map +1 -1
  162. package/dist/procedures/collection/schemas.d.ts +33 -0
  163. package/dist/procedures/collection/schemas.d.ts.map +1 -1
  164. package/dist/procedures/collection/schemas.js +68 -0
  165. package/dist/procedures/collection/schemas.js.map +1 -1
  166. package/dist/procedures/collection/storage.d.ts +37 -0
  167. package/dist/procedures/collection/storage.d.ts.map +1 -1
  168. package/dist/procedures/core/array.d.ts +1 -1
  169. package/dist/procedures/core/array.d.ts.map +1 -1
  170. package/dist/procedures/core/array.js +81 -29
  171. package/dist/procedures/core/array.js.map +1 -1
  172. package/dist/procedures/core/index.d.ts.map +1 -1
  173. package/dist/procedures/core/index.js +17 -2
  174. package/dist/procedures/core/index.js.map +1 -1
  175. package/dist/procedures/core/logic.js +1 -1
  176. package/dist/procedures/core/logic.js.map +1 -1
  177. package/dist/procedures/core/math.d.ts.map +1 -1
  178. package/dist/procedures/core/math.js +5 -1
  179. package/dist/procedures/core/math.js.map +1 -1
  180. package/dist/procedures/core/meta.d.ts.map +1 -1
  181. package/dist/procedures/core/meta.js +43 -8
  182. package/dist/procedures/core/meta.js.map +1 -1
  183. package/dist/procedures/core/object.d.ts.map +1 -1
  184. package/dist/procedures/core/object.js +42 -13
  185. package/dist/procedures/core/object.js.map +1 -1
  186. package/dist/procedures/core/operands.d.ts.map +1 -1
  187. package/dist/procedures/core/operands.js +6 -1
  188. package/dist/procedures/core/operands.js.map +1 -1
  189. package/dist/procedures/core/string.d.ts.map +1 -1
  190. package/dist/procedures/core/string.js +4 -2
  191. package/dist/procedures/core/string.js.map +1 -1
  192. package/dist/procedures/core/type.d.ts.map +1 -1
  193. package/dist/procedures/core/type.js +4 -2
  194. package/dist/procedures/core/type.js.map +1 -1
  195. package/dist/procedures/define-procedure.d.ts +19 -7
  196. package/dist/procedures/define-procedure.d.ts.map +1 -1
  197. package/dist/procedures/define-procedure.js +57 -17
  198. package/dist/procedures/define-procedure.js.map +1 -1
  199. package/dist/procedures/discovery.d.ts +4 -6
  200. package/dist/procedures/discovery.d.ts.map +1 -1
  201. package/dist/procedures/discovery.js +4 -6
  202. package/dist/procedures/discovery.js.map +1 -1
  203. package/dist/procedures/effects.d.ts +16 -0
  204. package/dist/procedures/effects.d.ts.map +1 -1
  205. package/dist/procedures/effects.js +33 -0
  206. package/dist/procedures/effects.js.map +1 -1
  207. package/dist/procedures/idempotency.d.ts +73 -0
  208. package/dist/procedures/idempotency.d.ts.map +1 -0
  209. package/dist/procedures/idempotency.js +131 -0
  210. package/dist/procedures/idempotency.js.map +1 -0
  211. package/dist/procedures/index.d.ts +7 -3
  212. package/dist/procedures/index.d.ts.map +1 -1
  213. package/dist/procedures/index.js +6 -2
  214. package/dist/procedures/index.js.map +1 -1
  215. package/dist/procedures/interceptors.d.ts +17 -4
  216. package/dist/procedures/interceptors.d.ts.map +1 -1
  217. package/dist/procedures/interceptors.js +18 -7
  218. package/dist/procedures/interceptors.js.map +1 -1
  219. package/dist/procedures/invoke.d.ts +15 -3
  220. package/dist/procedures/invoke.d.ts.map +1 -1
  221. package/dist/procedures/invoke.js +8 -3
  222. package/dist/procedures/invoke.js.map +1 -1
  223. package/dist/procedures/manifest.d.ts +83 -0
  224. package/dist/procedures/manifest.d.ts.map +1 -0
  225. package/dist/procedures/manifest.js +90 -0
  226. package/dist/procedures/manifest.js.map +1 -0
  227. package/dist/procedures/ref.d.ts +4 -2
  228. package/dist/procedures/ref.d.ts.map +1 -1
  229. package/dist/procedures/ref.js +2 -1
  230. package/dist/procedures/ref.js.map +1 -1
  231. package/dist/procedures/runtime-programs.d.ts +19 -0
  232. package/dist/procedures/runtime-programs.d.ts.map +1 -0
  233. package/dist/procedures/runtime-programs.js +24 -0
  234. package/dist/procedures/runtime-programs.js.map +1 -0
  235. package/dist/procedures/types.d.ts +16 -0
  236. package/dist/procedures/types.d.ts.map +1 -1
  237. package/dist/procedures/types.js.map +1 -1
  238. package/dist/protocol.d.ts +47 -0
  239. package/dist/protocol.d.ts.map +1 -0
  240. package/dist/protocol.js +68 -0
  241. package/dist/protocol.js.map +1 -0
  242. package/dist/server/host-policy.d.ts +4 -3
  243. package/dist/server/host-policy.d.ts.map +1 -1
  244. package/dist/server/host-policy.js +6 -3
  245. package/dist/server/host-policy.js.map +1 -1
  246. package/dist/server/host-procedures.d.ts +31 -0
  247. package/dist/server/host-procedures.d.ts.map +1 -0
  248. package/dist/server/host-procedures.js +75 -0
  249. package/dist/server/host-procedures.js.map +1 -0
  250. package/dist/server/index.d.ts +3 -3
  251. package/dist/server/index.d.ts.map +1 -1
  252. package/dist/server/index.js +1 -1
  253. package/dist/server/index.js.map +1 -1
  254. package/dist/server/procedure-server.d.ts +112 -5
  255. package/dist/server/procedure-server.d.ts.map +1 -1
  256. package/dist/server/procedure-server.js +273 -19
  257. package/dist/server/procedure-server.js.map +1 -1
  258. package/dist/server/request-auth.d.ts +19 -0
  259. package/dist/server/request-auth.d.ts.map +1 -0
  260. package/dist/server/request-auth.js +32 -0
  261. package/dist/server/request-auth.js.map +1 -0
  262. package/dist/server/types.d.ts +32 -0
  263. package/dist/server/types.d.ts.map +1 -1
  264. package/dist/server/types.js.map +1 -1
  265. package/dist/timers.d.ts +60 -0
  266. package/dist/timers.d.ts.map +1 -0
  267. package/dist/timers.js +33 -0
  268. package/dist/timers.js.map +1 -0
  269. package/dist/tracing/traceparent.d.ts +26 -0
  270. package/dist/tracing/traceparent.d.ts.map +1 -0
  271. package/dist/tracing/traceparent.js +53 -0
  272. package/dist/tracing/traceparent.js.map +1 -0
  273. package/package.json +11 -4
  274. package/src/adapters/http/client/transport.ts +31 -3
  275. package/src/adapters/http/client/types.ts +3 -2
  276. package/src/adapters/http/server/transport.ts +61 -26
  277. package/src/adapters/http/shared/headers.ts +7 -6
  278. package/src/adapters/local/client/transport.ts +74 -7
  279. package/src/adapters/local/client/types.ts +18 -0
  280. package/src/adapters/mock/client/transport.ts +9 -6
  281. package/src/adapters/mock/client/types.ts +7 -6
  282. package/src/adapters/port/transport.ts +124 -0
  283. package/src/adapters/websocket/client/index.ts +1 -0
  284. package/src/adapters/websocket/client/node.ts +34 -0
  285. package/src/adapters/websocket/client/transport.ts +251 -81
  286. package/src/adapters/websocket/client/types.ts +81 -21
  287. package/src/adapters/websocket/server/transport.ts +199 -301
  288. package/src/adapters/websocket/server/types.ts +20 -1
  289. package/src/adapters/websocket/shared/rpc.ts +453 -0
  290. package/src/browser.ts +51 -3
  291. package/src/client/call-types.ts +10 -14
  292. package/src/client/client.ts +28 -9
  293. package/src/client/deadline.ts +64 -0
  294. package/src/client/errors/README.md +2 -0
  295. package/src/client/errors/factory.ts +12 -8
  296. package/src/client/errors/http-errors.ts +5 -0
  297. package/src/client/errors/index.ts +1 -1
  298. package/src/client/errors/registry.ts +79 -22
  299. package/src/client/index.ts +21 -3
  300. package/src/client/middleware/cache.ts +120 -23
  301. package/src/client/middleware/circuit-breaker.ts +65 -35
  302. package/src/client/middleware/contexts.ts +17 -10
  303. package/src/client/middleware/items.ts +101 -6
  304. package/src/client/middleware/lru.ts +12 -0
  305. package/src/client/middleware/rate-limit.ts +9 -9
  306. package/src/client/middleware/resilience.ts +81 -0
  307. package/src/client/middleware/retry.ts +146 -34
  308. package/src/client/middleware/timeout.ts +57 -55
  309. package/src/client/middleware/tracing.ts +13 -4
  310. package/src/client/types.ts +9 -1
  311. package/src/clock.ts +21 -0
  312. package/src/duration.ts +56 -0
  313. package/src/index.ts +4 -0
  314. package/src/machines.ts +186 -0
  315. package/src/node.ts +11 -0
  316. package/src/procedures/capability.ts +7 -8
  317. package/src/procedures/collection/index.ts +9 -1
  318. package/src/procedures/collection/procedures.ts +57 -0
  319. package/src/procedures/collection/schemas.ts +110 -0
  320. package/src/procedures/collection/storage.ts +38 -0
  321. package/src/procedures/core/array.ts +79 -31
  322. package/src/procedures/core/index.ts +17 -2
  323. package/src/procedures/core/logic.ts +1 -1
  324. package/src/procedures/core/math.ts +5 -1
  325. package/src/procedures/core/meta.ts +45 -8
  326. package/src/procedures/core/object.ts +48 -14
  327. package/src/procedures/core/operands.ts +7 -0
  328. package/src/procedures/core/string.ts +4 -2
  329. package/src/procedures/core/type.ts +4 -2
  330. package/src/procedures/define-procedure.ts +73 -20
  331. package/src/procedures/discovery.ts +4 -6
  332. package/src/procedures/effects.ts +35 -0
  333. package/src/procedures/idempotency.ts +152 -0
  334. package/src/procedures/index.ts +18 -2
  335. package/src/procedures/interceptors.ts +28 -11
  336. package/src/procedures/invoke.ts +19 -5
  337. package/src/procedures/manifest.ts +158 -0
  338. package/src/procedures/ref.ts +4 -2
  339. package/src/procedures/runtime-programs.ts +27 -0
  340. package/src/procedures/types.ts +16 -0
  341. package/src/protocol.ts +108 -0
  342. package/src/server/host-policy.ts +8 -5
  343. package/src/server/host-procedures.ts +102 -0
  344. package/src/server/index.ts +5 -2
  345. package/src/server/procedure-server.ts +328 -12
  346. package/src/server/request-auth.ts +35 -0
  347. package/src/server/types.ts +37 -0
  348. package/src/timers.ts +82 -0
  349. package/src/tracing/traceparent.ts +61 -0
  350. package/dist/procedures/cli/announce.d.ts +0 -67
  351. package/dist/procedures/cli/announce.d.ts.map +0 -1
  352. package/dist/procedures/cli/announce.js +0 -392
  353. package/dist/procedures/cli/announce.js.map +0 -1
  354. package/dist/procedures/cli/discover.d.ts +0 -66
  355. package/dist/procedures/cli/discover.d.ts.map +0 -1
  356. package/dist/procedures/cli/discover.js +0 -310
  357. package/dist/procedures/cli/discover.js.map +0 -1
  358. package/dist/procedures/cli/index.d.ts +0 -13
  359. package/dist/procedures/cli/index.d.ts.map +0 -1
  360. package/dist/procedures/cli/index.js +0 -203
  361. package/dist/procedures/cli/index.js.map +0 -1
  362. package/dist/procedures/cli/init.d.ts +0 -17
  363. package/dist/procedures/cli/init.d.ts.map +0 -1
  364. package/dist/procedures/cli/init.js +0 -336
  365. package/dist/procedures/cli/init.js.map +0 -1
  366. package/src/procedures/cli/announce.ts +0 -470
  367. package/src/procedures/cli/discover.ts +0 -390
  368. package/src/procedures/cli/index.ts +0 -231
  369. package/src/procedures/cli/init.ts +0 -370
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Idempotency of calls (roadmap R1).
3
+ *
4
+ * The retry middleware gives each call one idempotency key, and each try of the call sends the
5
+ * same key. `ProcedureServer` keeps the result of a keyed call to a procedure that is not
6
+ * repeatable. A second try with the same key gets that result, and the handler does not run
7
+ * again. So a retry cannot do a write two times.
8
+ */
9
+
10
+ import type { Method } from "../client/types.js";
11
+ import { methodToPath } from "../server/method.js";
12
+ import type { AnyProcedure, Effects, ProcedurePath } from "./types.js";
13
+ import { optionalMs, type Duration } from "../duration.js";
14
+ import { TIMER_DEFAULTS } from "../timers.js";
15
+
16
+ // This file imports no module of the invocation, so the retry middleware stays small in a browser
17
+
18
+ /** The metadata key of the idempotency key of a call. Each try of one call sends the same key. */
19
+ export const IDEMPOTENCY_KEY_METADATA = "idempotency-key";
20
+
21
+ /** The longest idempotency key that a server accepts. A longer key is ignored. */
22
+ export const MAX_IDEMPOTENCY_KEY_LENGTH = 200;
23
+
24
+ /**
25
+ * True when a second call with the same input changes nothing more: the procedure is idempotent,
26
+ * or it changes nothing (`writes: []`). Undeclared effects are not repeatable.
27
+ */
28
+ export function isRepeatable(effects: Effects | undefined): boolean {
29
+ if (effects === undefined) return false;
30
+ if (effects.idempotent === true) return true;
31
+ return effects.writes !== undefined && effects.writes.length === 0;
32
+ }
33
+
34
+ /**
35
+ * This function gives the `idempotent` option of the retry middleware from the procedures that
36
+ * the client knows: true for a repeatable procedure, false for another one, and undefined for a
37
+ * path that the registry does not have.
38
+ */
39
+ export function idempotentIn(registry: { get(path: ProcedurePath): AnyProcedure | undefined }): (method: Method) => boolean | undefined {
40
+ return (method) => {
41
+ const procedure = registry.get(methodToPath(method));
42
+ if (procedure === undefined) return undefined;
43
+ const effects = (procedure.metadata as { effects?: Effects } | undefined)?.effects;
44
+ return isRepeatable(effects !== null && typeof effects === "object" ? effects : undefined);
45
+ };
46
+ }
47
+
48
+ /**
49
+ * This function gives the effects of the procedures that the client knows, by method: the
50
+ * `effects` option of the cache middleware (roadmap R13). A path that the registry does not have
51
+ * gives undefined.
52
+ */
53
+ export function effectsIn(registry: { get(path: ProcedurePath): AnyProcedure | undefined }): (method: Method) => Effects | undefined {
54
+ return (method) => {
55
+ const effects = (registry.get(methodToPath(method))?.metadata as { effects?: Effects } | undefined)?.effects;
56
+ return effects !== null && typeof effects === "object" ? effects : undefined;
57
+ };
58
+ }
59
+
60
+ /** This function makes a new idempotency key: a random UUID. */
61
+ export function newIdempotencyKey(): string {
62
+ const random = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto?.randomUUID;
63
+ if (random) return random.call(globalThis.crypto);
64
+ // An older runtime without randomUUID: 128 random bits from Math.random
65
+ let key = "";
66
+ for (let index = 0; index < 4; index++) key += Math.floor(Math.random() * 0x100000000).toString(16).padStart(8, "0");
67
+ return key;
68
+ }
69
+
70
+ /** The idempotency key of a call, from its metadata, or undefined. */
71
+ export function idempotencyKeyOf(metadata: Record<string, unknown> | undefined): string | undefined {
72
+ const key = metadata?.[IDEMPOTENCY_KEY_METADATA];
73
+ return typeof key === "string" && key.length > 0 && key.length <= MAX_IDEMPOTENCY_KEY_LENGTH ? key : undefined;
74
+ }
75
+
76
+ /** The options of the idempotency cache of a server. */
77
+ export interface IdempotencyOptions {
78
+ /** How long the server keeps a result after the call ends. The default is 10 minutes. */
79
+ readonly ttl?: Duration;
80
+ /** The most results that the server keeps. The oldest finished result goes first. The default is 1000. */
81
+ readonly maxEntries?: number;
82
+ /** The clock of the cache, in milliseconds. The default is `Date.now`. */
83
+ readonly now?: () => number;
84
+ }
85
+
86
+ interface Entry<T> {
87
+ readonly done: Promise<T>;
88
+ /** The end time of the call, or undefined while it runs. */
89
+ settledAt: number | undefined;
90
+ }
91
+
92
+ /**
93
+ * The results of keyed calls. The first call with a key runs. A call with the same key while the
94
+ * first runs waits for it. A later call gets the same result until the time to live ends.
95
+ */
96
+ export class IdempotencyCache<T> {
97
+ private readonly entries = new Map<string, Entry<T>>();
98
+ private readonly ttlMs: number;
99
+ private readonly maxEntries: number;
100
+ private readonly now: () => number;
101
+
102
+ constructor(options: IdempotencyOptions = {}) {
103
+ this.ttlMs = optionalMs(options.ttl, "ttl") ?? TIMER_DEFAULTS.idempotency.ttlMs;
104
+ this.maxEntries = options.maxEntries ?? 1000;
105
+ this.now = options.now ?? Date.now;
106
+ }
107
+
108
+ /** The number of results and calls that the cache holds. */
109
+ get size(): number {
110
+ return this.entries.size;
111
+ }
112
+
113
+ /**
114
+ * The result of the call with this key. `run` starts only for the first call. `keep` tells if
115
+ * the cache keeps a result: a result that is not kept lets the next call with the key run again.
116
+ */
117
+ run(key: string, run: () => Promise<T>, keep: (result: T) => boolean): { result: Promise<T>; first: boolean } {
118
+ this.evict();
119
+ const existing = this.entries.get(key);
120
+ if (existing) return { result: existing.done, first: false };
121
+ const entry: Entry<T> = {
122
+ done: run().then(
123
+ (result) => {
124
+ if (keep(result)) entry.settledAt = this.now();
125
+ else if (this.entries.get(key) === entry) this.entries.delete(key);
126
+ return result;
127
+ },
128
+ (error: unknown) => {
129
+ if (this.entries.get(key) === entry) this.entries.delete(key);
130
+ throw error;
131
+ },
132
+ ),
133
+ settledAt: undefined,
134
+ };
135
+ this.entries.set(key, entry);
136
+ return { result: entry.done, first: true };
137
+ }
138
+
139
+ /** This function removes the expired results, and the oldest finished results above the limit. */
140
+ private evict(): void {
141
+ const now = this.now();
142
+ for (const [key, entry] of this.entries) {
143
+ if (entry.settledAt !== undefined && now - entry.settledAt >= this.ttlMs) this.entries.delete(key);
144
+ }
145
+ if (this.entries.size < this.maxEntries) return;
146
+ // A Map keeps the order of insertion, so the first finished entries are the oldest
147
+ for (const [key, entry] of this.entries) {
148
+ if (this.entries.size < this.maxEntries) break;
149
+ if (entry.settledAt !== undefined) this.entries.delete(key);
150
+ }
151
+ }
152
+ }
@@ -54,7 +54,22 @@ export {
54
54
  explainInvocation,
55
55
  checkEffects,
56
56
  } from "./capability.js";
57
- export { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects } from "./effects.js";
57
+ export { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects, INFERRED_EFFECTS_METADATA_KEY, inferredEffectsOf, effectsValue } from "./effects.js";
58
+ // One manifest type (roadmap R17)
59
+ export { MANIFEST_FORMAT, describeProcedure as describeProcedureEntry, describeRegistry, exposedProcedures, hashOf } from "./manifest.js";
60
+ export type { ProcedureEntry, ProcedureManifest, CliDescription, DescribeOptions, DescribeRegistryOptions } from "./manifest.js";
61
+ // Idempotency of calls (roadmap R1)
62
+ export {
63
+ IDEMPOTENCY_KEY_METADATA,
64
+ MAX_IDEMPOTENCY_KEY_LENGTH,
65
+ IdempotencyCache,
66
+ idempotencyKeyOf,
67
+ idempotentIn,
68
+ effectsIn,
69
+ isRepeatable,
70
+ newIdempotencyKey,
71
+ } from "./idempotency.js";
72
+ export type { IdempotencyOptions } from "./idempotency.js";
58
73
  export type { ToolHints, CapabilityRefusal, CapabilityRefusalReason, InvocationExplanation, EffectsViolation } from "./capability.js";
59
74
 
60
75
  // Registry
@@ -73,7 +88,7 @@ export type { ProcedureDefinition, ProcedureStub } from "./define.js";
73
88
 
74
89
  // Collection procedures
75
90
  export { createCollectionProcedures } from "./collection/index.js";
76
- export type { CollectionStorage, StorageMetadata } from "./collection/index.js";
91
+ export type { CollectionStorage, StorageChanges, StorageMetadata } from "./collection/index.js";
77
92
 
78
93
  // Manual registration helpers
79
94
  export {
@@ -211,6 +226,7 @@ export type {
211
226
  export {
212
227
  defineProcedureProcedure,
213
228
  getProcedureProcedure,
229
+ manifestProcedure,
214
230
  listProceduresProcedure,
215
231
  deleteProcedureProcedure,
216
232
  metaProcedures,
@@ -8,14 +8,16 @@
8
8
 
9
9
  import { InvocationError, type Interceptor, type Invocation, type ProcedureOutput } from "./invoke.js";
10
10
  import { pathToKey, type ProcedurePath } from "./types.js";
11
+ import { toMs, type Duration } from "../duration.js";
12
+ import { TRACEPARENT_METADATA_KEY, formatTraceparent, newSpanId, newTraceId, parseTraceparent } from "../tracing/traceparent.js";
11
13
 
12
14
  // =============================================================================
13
15
  // Timeout
14
16
  // =============================================================================
15
17
 
16
18
  export interface TimeoutInterceptorOptions {
17
- /** The time limit in milliseconds. 0 or less: no limit. */
18
- ms: number;
19
+ /** The time limit, for example `minutes(10)`. 0 or less: no limit. */
20
+ limit: Duration;
19
21
  /**
20
22
  * The invocations to limit. The default is the calls of the host (depth 0): a nested call gets
21
23
  * the signal of its parent, so the limit of the parent ends it too.
@@ -76,6 +78,14 @@ async function* itemsUntil<T>(
76
78
  * result is not used. For a stream, the limit covers the whole stream.
77
79
  */
78
80
  export function timeoutInterceptor(options: TimeoutInterceptorOptions): Interceptor {
81
+ return timeoutAfterMs({ ...options, ms: toMs(options.limit, "limit") });
82
+ }
83
+
84
+ /**
85
+ * The timeout interceptor with a limit in milliseconds. The invocation uses it for the deadline
86
+ * of a capability, which is a time in milliseconds.
87
+ */
88
+ export function timeoutAfterMs(options: Omit<TimeoutInterceptorOptions, "limit"> & { ms: number }): Interceptor {
79
89
  const { ms } = options;
80
90
  const appliesTo = options.appliesTo ?? ((invocation: Invocation) => invocation.depth === 0);
81
91
  return async (invocation, next) => {
@@ -119,9 +129,14 @@ export function timeoutInterceptor(options: TimeoutInterceptorOptions): Intercep
119
129
  /** One invocation as a span of a trace. */
120
130
  export interface InvocationSpan {
121
131
  readonly id: string;
122
- /** The span of the invocation whose handler made this call. A call of a host has none. */
132
+ /**
133
+ * The span of the invocation whose handler made this call. A call of a host has the span of its
134
+ * remote caller, when the caller sent a `traceparent` (roadmap R16), or none.
135
+ */
123
136
  readonly parentId: string | undefined;
124
- /** The same for every span of one call of a host. */
137
+ /** True when the parent span is in another process: the caller of a host. */
138
+ readonly remoteParent?: boolean | undefined;
139
+ /** The same for every span of one call of a host, and of its caller when it sent a trace. */
125
140
  readonly traceId: string;
126
141
  readonly path: ProcedurePath;
127
142
  readonly key: string;
@@ -149,7 +164,6 @@ export interface TracingInterceptorOptions {
149
164
  now?: (() => number) | undefined;
150
165
  }
151
166
 
152
- let traceCount = 0;
153
167
 
154
168
  /**
155
169
  * This interceptor makes a span for each invocation, with the span of its parent. A host can
@@ -157,8 +171,6 @@ let traceCount = 0;
157
171
  */
158
172
  export function tracingInterceptor(options: TracingInterceptorOptions): Interceptor {
159
173
  const now = options.now ?? (() => performance.now());
160
- const prefix = `t${++traceCount}-${Math.random().toString(36).slice(2, 8)}`;
161
- let spanCount = 0;
162
174
  /** The open spans, by the id of their invocation */
163
175
  const open = new Map<number, InvocationSpan>();
164
176
 
@@ -179,11 +191,15 @@ export function tracingInterceptor(options: TracingInterceptorOptions): Intercep
179
191
 
180
192
  return async (invocation, next) => {
181
193
  const parent = invocation.parent ? open.get(invocation.parent.id) : undefined;
182
- const id = `${prefix}-${++spanCount}`;
194
+ // A call of a host continues the trace of its caller (W3C traceparent, roadmap R16)
195
+ const remote = parent ? undefined : parseTraceparent(invocation.metadata[TRACEPARENT_METADATA_KEY]);
196
+ // W3C span ids, so a trace backend can join the spans of the client and of the server
197
+ const id = newSpanId();
183
198
  const span: InvocationSpan = {
184
199
  id,
185
- parentId: parent?.id,
186
- traceId: parent?.traceId ?? id,
200
+ parentId: parent?.id ?? remote?.spanId,
201
+ ...(remote ? { remoteParent: true } : {}),
202
+ traceId: parent?.traceId ?? remote?.traceId ?? newTraceId(),
187
203
  path: invocation.path,
188
204
  key: pathToKey(invocation.path),
189
205
  depth: invocation.depth,
@@ -197,7 +213,8 @@ export function tracingInterceptor(options: TracingInterceptorOptions): Intercep
197
213
 
198
214
  let output: ProcedureOutput<unknown>;
199
215
  try {
200
- output = await next();
216
+ // The nested calls (and their remote calls) carry the span of this invocation (R16)
217
+ output = await next({ metadata: { ...invocation.metadata, [TRACEPARENT_METADATA_KEY]: formatTraceparent(span.traceId, id) } });
201
218
  } catch (error) {
202
219
  finish(span, invocation.id, error);
203
220
  throw error;
@@ -39,7 +39,7 @@ import { PROCEDURE_REGISTRY } from "./registry.js";
39
39
  import { nestedCallRefusal } from "./policy.js";
40
40
  import { scopeOf, withScope, type HydrationEvent } from "./ref.js";
41
41
  import { UNLIMITED_CAPABILITY, capabilityRefusal, examinesNestedCall, narrowCapability, type CapabilityRefusalReason } from "./capability.js";
42
- import { timeoutInterceptor } from "./interceptors.js";
42
+ import { timeoutAfterMs } from "./interceptors.js";
43
43
  import type { EventBus } from "../events/types.js";
44
44
  import { getGlobalEventBus, withEventBusSignal } from "../events/bus.js";
45
45
 
@@ -61,16 +61,29 @@ export type InvocationErrorCode =
61
61
  /** The capability of the call does not permit it (CAPABILITIES-2026-10). `reason` tells why. */
62
62
  | "FORBIDDEN";
63
63
 
64
+ /**
65
+ * The reason of a `FORBIDDEN` error. It is a refusal of the capability, or a refusal of a module
66
+ * that the host configured:
67
+ * - `outside-root`: the input has a path outside the root of the host (`createFsModule({ root })`
68
+ * of `@procedures/fs`).
69
+ * - `host-not-allowed`: the input has a URL whose host the host does not allow, or a redirect goes
70
+ * to such a host (`createHttpModule({ allow })` of `@procedures/http`).
71
+ */
72
+ export type ForbiddenReason = CapabilityRefusalReason | "outside-root" | "host-not-allowed";
73
+
64
74
  /** An error of the invocation itself. An error that the handler throws passes through unchanged. */
65
75
  export class InvocationError extends Error {
66
76
  override readonly name = "InvocationError";
67
77
  readonly code: InvocationErrorCode;
68
78
  readonly path: ProcedurePath;
69
79
  readonly retryable: boolean = false;
70
- /** The reason of a `FORBIDDEN` error: `effects-undeclared`, `effects-exceeded` or `clearance`. */
71
- readonly reason: CapabilityRefusalReason | undefined;
80
+ /**
81
+ * The reason of a `FORBIDDEN` error: `effects-undeclared`, `effects-exceeded`, `clearance`,
82
+ * `deadline`, `outside-root` or `host-not-allowed`.
83
+ */
84
+ readonly reason: ForbiddenReason | undefined;
72
85
 
73
- constructor(code: InvocationErrorCode, message: string, path: ProcedurePath, reason?: CapabilityRefusalReason) {
86
+ constructor(code: InvocationErrorCode, message: string, path: ProcedurePath, reason?: ForbiddenReason) {
74
87
  super(message);
75
88
  this.code = code;
76
89
  this.path = path;
@@ -304,7 +317,7 @@ export async function invokeProcedure<TOutput = unknown>(
304
317
  const deadline = invocation.capability.deadline;
305
318
  const ownDeadline = deadline !== undefined && options.parent?.capability.deadline !== deadline;
306
319
  const interceptors = ownDeadline
307
- ? [timeoutInterceptor({ ms: Math.max(0, deadline - Date.now()), appliesTo: () => true }), ...(options.interceptors ?? [])]
320
+ ? [timeoutAfterMs({ ms: Math.max(0, deadline - Date.now()), appliesTo: () => true }), ...(options.interceptors ?? [])]
308
321
  : (options.interceptors ?? []);
309
322
  if (interceptors.length === 0) return runInvocation<TOutput>(invocation, options);
310
323
 
@@ -468,6 +481,7 @@ export function createProcedureContext(
468
481
  resources: invocation && options.resources ? options.resources(invocation) : (EMPTY_RESOURCES as ResourcesOf),
469
482
  };
470
483
  if (options.signal) context.signal = options.signal;
484
+ if (options.expose) context.expose = options.expose;
471
485
  if (options.repository) context.repository = options.repository;
472
486
  if (options.onRef) context.onRef = options.onRef;
473
487
  // Every context has a bus. With a signal, the waits of the handler on the bus end when the
@@ -0,0 +1,158 @@
1
+ /**
2
+ * One manifest type (roadmap R17).
3
+ *
4
+ * Before, ten systems described procedures, with three manifest formats, three version schemes
5
+ * and no shared type: `procedure.get` gave booleans for the schemas, `manifest.generate` gave
6
+ * JSON Schemas but no effects, `_discovery.announce` gave no schemas, the MCP tool list gave the
7
+ * effects as hints, and none of them obeyed the expose rule of the host. This module is the one
8
+ * description: a function of a procedure, and a function of a registry with the expose rule.
9
+ * The introspection procedures, the compiler (R20) and the SDK generators read it.
10
+ */
11
+
12
+ import { pathToKey, type AnyProcedure, type Effects, type ProcedurePath } from "./types.js";
13
+ import { jsonSchemaOf, type JsonSchema } from "./schema.js";
14
+ import { isDataDriven } from "./ref.js";
15
+ import { isRuntimeDefined } from "./runtime-programs.js";
16
+
17
+ /** The format of a manifest. A change that is not compatible makes a new format. */
18
+ export const MANIFEST_FORMAT = "procedures.manifest/1";
19
+
20
+ /** The command-line metadata of a procedure: how `mark` reads its arguments and prints its result. */
21
+ export interface CliDescription {
22
+ readonly args?: readonly string[] | undefined;
23
+ readonly shorts?: Readonly<Record<string, string>> | undefined;
24
+ readonly output?: string | undefined;
25
+ readonly interactive?: boolean | undefined;
26
+ }
27
+
28
+ /** The description of one procedure. */
29
+ export interface ProcedureEntry {
30
+ readonly path: ProcedurePath;
31
+ readonly key: string;
32
+ readonly description?: string | undefined;
33
+ readonly tags?: readonly string[] | undefined;
34
+ /** The declared effects. Absent: not declared. */
35
+ readonly effects?: Effects | undefined;
36
+ /** The JSON Schema of the input (draft-07). */
37
+ readonly input?: JsonSchema | undefined;
38
+ /** The JSON Schema of the output (draft-07). */
39
+ readonly output?: JsonSchema | undefined;
40
+ /** The procedures that a procedure of code calls (`metadata.calls`). */
41
+ readonly calls?: readonly string[] | undefined;
42
+ /** True: the stream of the procedure can run without an end (R15). */
43
+ readonly unboundedStream?: boolean | undefined;
44
+ /** True: the procedure runs its input as a program (control flow, a defined procedure). */
45
+ readonly dataDriven: boolean;
46
+ /** True: `procedure.define` made it at run time. */
47
+ readonly runtime: boolean;
48
+ /** True: the procedure has a handler, so it can run here. */
49
+ readonly hasHandler: boolean;
50
+ readonly deprecated?: boolean | undefined;
51
+ readonly cli?: CliDescription | undefined;
52
+ }
53
+
54
+ /** The description of the procedures of a registry. */
55
+ export interface ProcedureManifest {
56
+ readonly format: typeof MANIFEST_FORMAT;
57
+ /** A hash of the entries: two manifests with the same hash describe the same procedures. */
58
+ readonly hash: string;
59
+ readonly procedures: readonly ProcedureEntry[];
60
+ }
61
+
62
+ /** The options of `describeProcedure` and `describeRegistry`. */
63
+ export interface DescribeOptions {
64
+ /** False leaves out the JSON Schemas. The default is true. */
65
+ readonly schemas?: boolean | undefined;
66
+ }
67
+
68
+ /** The options of `describeRegistry`. */
69
+ export interface DescribeRegistryOptions extends DescribeOptions {
70
+ /** The expose rule of the host: a procedure outside it is not in the manifest. */
71
+ readonly expose?: ((path: ProcedurePath) => boolean) | undefined;
72
+ /** More filtering: a procedure for which it gives false is not in the manifest. */
73
+ readonly include?: ((procedure: AnyProcedure) => boolean) | undefined;
74
+ /** True keeps the procedures without a handler (stubs of a remote host). The default is false. */
75
+ readonly withoutHandler?: boolean | undefined;
76
+ }
77
+
78
+ /** This function describes one procedure. */
79
+ export function describeProcedure(procedure: AnyProcedure, options: DescribeOptions = {}): ProcedureEntry {
80
+ const meta = (procedure.metadata ?? {}) as Record<string, unknown>;
81
+ const schemas = options.schemas !== false;
82
+ const effects = meta["effects"];
83
+ const calls = meta["calls"];
84
+ const cli: CliDescription = {
85
+ ...(Array.isArray(meta["args"]) ? { args: meta["args"] as string[] } : {}),
86
+ ...(meta["shorts"] && typeof meta["shorts"] === "object" ? { shorts: meta["shorts"] as Record<string, string> } : {}),
87
+ ...(typeof meta["output"] === "string" ? { output: meta["output"] } : {}),
88
+ ...(typeof meta["interactive"] === "boolean" ? { interactive: meta["interactive"] } : {}),
89
+ };
90
+ const input = schemas ? jsonSchemaOf(procedure.input, { io: "input" }) : undefined;
91
+ const output = schemas ? jsonSchemaOf(procedure.output, { io: "output" }) : undefined;
92
+ return {
93
+ path: procedure.path,
94
+ key: pathToKey(procedure.path),
95
+ ...(typeof meta["description"] === "string" ? { description: meta["description"] } : {}),
96
+ ...(Array.isArray(meta["tags"]) ? { tags: meta["tags"] as string[] } : {}),
97
+ ...(effects && typeof effects === "object" ? { effects: effects as Effects } : {}),
98
+ ...(input ? { input } : {}),
99
+ ...(output ? { output } : {}),
100
+ ...(Array.isArray(calls) ? { calls: calls as string[] } : {}),
101
+ ...((meta["stream"] as { unbounded?: unknown } | undefined)?.unbounded === true ? { unboundedStream: true } : {}),
102
+ dataDriven: isDataDriven(procedure),
103
+ runtime: isRuntimeDefined(procedure),
104
+ hasHandler: typeof procedure.handler === "function",
105
+ ...(meta["deprecated"] === true ? { deprecated: true } : {}),
106
+ ...(Object.keys(cli).length > 0 ? { cli } : {}),
107
+ };
108
+ }
109
+
110
+ /** The procedures of a registry, as the expose rule of the host shows them. */
111
+ export function exposedProcedures(
112
+ registry: { getAll(): AnyProcedure[] },
113
+ options: Pick<DescribeRegistryOptions, "expose" | "include" | "withoutHandler"> = {},
114
+ ): AnyProcedure[] {
115
+ return registry
116
+ .getAll()
117
+ .filter(
118
+ (procedure) =>
119
+ (options.withoutHandler === true || typeof procedure.handler === "function") &&
120
+ (!options.expose || options.expose(procedure.path)) &&
121
+ (!options.include || options.include(procedure)),
122
+ );
123
+ }
124
+
125
+ /**
126
+ * This function describes the procedures of a registry that the expose rule shows, in the order
127
+ * of their keys, with a hash of the description.
128
+ */
129
+ export function describeRegistry(registry: { getAll(): AnyProcedure[] }, options: DescribeRegistryOptions = {}): ProcedureManifest {
130
+ const procedures = exposedProcedures(registry, options)
131
+ .map((procedure) => describeProcedure(procedure, options))
132
+ .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
133
+ return { format: MANIFEST_FORMAT, hash: hashOf(procedures), procedures };
134
+ }
135
+
136
+ /** A stable JSON text: the keys of each object in order. */
137
+ function stableJson(value: unknown): string {
138
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
139
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
140
+ const object = value as Record<string, unknown>;
141
+ return `{${Object.keys(object)
142
+ .sort()
143
+ .filter((key) => object[key] !== undefined)
144
+ .map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`)
145
+ .join(",")}}`;
146
+ }
147
+
148
+ /** A 64-bit FNV-1a hash of the stable JSON of a value, as 16 hex digits. */
149
+ export function hashOf(value: unknown): string {
150
+ const text = stableJson(value);
151
+ let hash = 0xcbf29ce484222325n;
152
+ const prime = 0x100000001b3n;
153
+ for (let index = 0; index < text.length; index++) {
154
+ hash ^= BigInt(text.charCodeAt(index));
155
+ hash = (hash * prime) & 0xffffffffffffffffn;
156
+ }
157
+ return hash.toString(16).padStart(16, "0");
158
+ }
@@ -22,7 +22,8 @@
22
22
  * - `"$never"`: Never execute during hydration, pass as pure data (with its whole subtree)
23
23
  * - `"$parent"`: Defer to parent procedure (pass as data)
24
24
  * - `"someName"`: Defer to the runner that gives this name in its `contextStack`
25
- * (`dag.traverse` gives "dag.traverse", `core.catch` gives "catch")
25
+ * (`dag.traverse` gives "dag.traverse". `client.tryCatch` gives no name: it is control flow,
26
+ * and it runs its `try` and `catch` operands itself.)
26
27
  *
27
28
  * A deferred ref is data at every depth: hydration of the input that holds it does not run it.
28
29
  * The procedure that receives it runs it later (deep dive CORE-11).
@@ -681,7 +682,8 @@ export interface HydrateOptions {
681
682
 
682
683
  /**
683
684
  * The context names of the runner. A ref with `$when: "<name>"` runs only when this list has
684
- * the name. A runner such as `core.catch` gives its own name here when it hydrates its operand.
685
+ * the name. A runner such as `dag.traverse` gives its own name here when it hydrates its operand.
686
+ * (Control flow such as `client.tryCatch` runs its operands itself and gives no name.)
685
687
  */
686
688
  contextStack?: string[] | undefined;
687
689
 
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The handlers that `procedure.define` made, with the program (the aggregation) of each. The
3
+ * registry is the only store of the procedures: a procedure is runtime-defined when its handler
4
+ * is a key here. (Before, a separate map kept them, and `procedure.delete` removed only the map
5
+ * entry: deep dive CORE-3.) The program is kept here, not only in the metadata: a caller that
6
+ * gives its own metadata to `procedure.define` replaces the metadata.
7
+ *
8
+ * This module imports nothing, so the manifest (R17) and `procedure.define` can both use it
9
+ * without an import cycle.
10
+ */
11
+
12
+ const RUNTIME_PROGRAMS = new WeakMap<object, unknown>();
13
+
14
+ /** This function records the program of a handler that `procedure.define` made. */
15
+ export function markRuntimeProgram(handler: object, program: unknown): void {
16
+ RUNTIME_PROGRAMS.set(handler, program);
17
+ }
18
+
19
+ /** True for a procedure that `procedure.define` made. */
20
+ export function isRuntimeDefined(procedure: { handler?: unknown }): boolean {
21
+ return typeof procedure.handler === "function" && RUNTIME_PROGRAMS.has(procedure.handler);
22
+ }
23
+
24
+ /** The program of a handler that `procedure.define` made, or undefined. */
25
+ export function runtimeProgramOfHandler(handler: unknown): unknown {
26
+ return typeof handler === "function" ? RUNTIME_PROGRAMS.get(handler) : undefined;
27
+ }
@@ -75,6 +75,14 @@ export interface ProcedureMetadata {
75
75
  */
76
76
  effects?: Effects;
77
77
 
78
+ /**
79
+ * The stream of the procedure (roadmap R15). `unbounded: true`: the stream can run without an
80
+ * end (a `follow` of logs, the output of a server process). A face that collects a stream
81
+ * refuses such a call or stops it at a limit: HTTP without NDJSON refuses it, and MCP stops it
82
+ * at its stream limits.
83
+ */
84
+ stream?: { readonly unbounded?: boolean } | undefined;
85
+
78
86
  /** Custom metadata fields */
79
87
  [key: string]: unknown;
80
88
  }
@@ -260,6 +268,14 @@ export interface ProcedureContext {
260
268
  */
261
269
  registry?: ProcedureRegistryLike;
262
270
 
271
+ /**
272
+ * The expose rule of the host: the procedures that its callers reach. The introspection
273
+ * procedures (`procedure.list`, `procedure.get`, `procedure.manifest` and the others) show only
274
+ * these (roadmap R17: before, a handler could not know the rule, so an MCP tool listed the
275
+ * procedures that the host does not expose). Absent: every procedure.
276
+ */
277
+ expose?: ((path: ProcedurePath) => boolean) | undefined;
278
+
263
279
  /**
264
280
  * The hydration observer of the program that runs this procedure (`client.exec(ref, input,
265
281
  * { onRef })`). A procedure that hydrates refs itself (control flow, a runner) gives it to