@procedures/client 0.2.3 → 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 +65 -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 +7 -8
  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 +57 -17
  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 +16 -0
  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 +7 -8
  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 +73 -20
  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 +16 -0
  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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,70 @@
1
1
  # @procedures/client
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b2a3e3a: The bidirectional peer (roadmap R24). The calls over one WebSocket go in the two directions with the same frames: a request, then a response, an error or a stream with credit, and a cancel. The client serves the calls of the server with the new `serve` option of `WebSocketTransport` (for example a `ProcedureServer` with its registry), so a call gets validation, the expose rule, the capability, streaming, cancel, the signal and the deadline. The server sends them with `callConnection()`, or with a `Client` over `transportFor(connectionId)`, and a peer has `connections()` and `clientFor(connectionId)`. `callClient()` and `onServerRequest` stay as wrappers of the new calls, and the old `server-request` frames are no longer sent. The shared code of the two sides (`OutgoingCalls`, `IncomingCalls`) serves the calls of the client too.
8
+ - a8c47f1: The cache (roadmap R13). Two calls with the same key at the same time make one request: the second one waits for the first and gets a copy. With the new option `effects` (for example `effectsIn(registry)`), a write that succeeds deletes the cached entries of the methods that read one of its resources. `invalidateCache(middleware, matches?)` deletes entries by method. A response whose key changed during the call is not stored, so an auth layer inside the cache cannot give the response of one user to another. The idempotency key and the remaining time of a call are not part of the key.
9
+ - 381ce3a: Cancellation scopes and a graceful stop (roadmap R14). `ProcedureServer.stop({ grace })` refuses new calls with a retryable `UNAVAILABLE` and lets the calls in flight end until the grace time ends, then aborts them (`peer.stop({ grace })` passes it; `mark --server` and the `server` bin give 5 seconds). The signal of each call joins its request and the server, and `callsInFlight` counts the calls, streams included. `exec()` and `execStream()` take a `signal`. `LocalTransport` takes `interceptors`, `capability` and `clearanceOrder`, and gives a call the deadline that its client sends, so a client timeout ends a local handler too. `mark` aborts a command in its own process on the first Ctrl+C. A remote leaf of `route()` and `server.call` send the deadline of their caller, and `server.call` relays the signal.
10
+ - ae7f05f: The clock of the timers, and the state machines as data (roadmap R35). The retry, the circuit breaker and the WebSocket client take a `clock` option: a `Clock` gives the monotonic time and starts the timers, and `realClock` is the default. The shape is the `Clock` of `@mark1russell7/service`, so its `fakeClock` fits, and a test gets exact times. `WEBSOCKET_CONNECTION_MACHINE` and `CIRCUIT_BREAKER_MACHINE` are `mark1russell7.machine/1` documents with their knobs, events and timers. The WebSocket client and the circuit breaker report each change of their state to `onTransition`, and `replayTransitions()` replays the reports against a document. The heartbeat of the WebSocket client uses one timer for each interval in place of `setInterval`.
11
+ - 7873940: The collection mode switch (roadmap R28). `HybridStorage` has four modes: `local-only` (no remote call, each write waits in the queue), `write-through`, `write-back` and `remote-only` (no local copy). The option `mode` gives the first mode, and `writeStrategy` stays as a deprecated name of it. `setMode()` changes the mode while the storage operates. The switch waits for the operations that run, sends the queue to the remote first, and syncs the local copy when it leaves `local-only` or `remote-only`. A switch to `remote-only` drops the local copy only after the flush. When the remote fails, the mode stays and `setMode()` throws `ModeSwitchError`. A switch to `local-only` makes no remote call.
12
+
13
+ The queue is durable: the option `queueStore` takes a `QueueStore` (`load`, `append`, `replace`, `clear`). `MemoryQueueStore` is the default, and `FileQueueStore` of the new entry `@procedures/store/file-queue` keeps the queue in a JSON Lines file. A write ends only after the store kept it (`QueueStoreError` when it cannot). A new storage with the same store loads the queue, shows its writes and sends them. The modes `write-back` and `local-only` always use the queue: before, `write-back` with `offlineQueue: false` never sent a write.
14
+
15
+ A sync asks for the changes since the version of the last sync. Storages get the optional `getChangesSince(version)` (`InMemoryStorage` keeps a change log with tombstones, `ApiStorage` calls the new collection procedure `collections.<name>.changes`), and a remote without it gives all its entries. The option `versionOf` gives the version of an item: with versions, a conflict is a queued local write of an id whose remote version changed, and `conflictResolution` decides (`error` throws `ConflictError`). The option `pullInterval` starts a periodic pull. Storages get the optional `flush()`, and `ProcedureServer.useStorage(name, storage, { drain, close })` flushes the old storage before the change. `createSyncedRegistry({ type: "hybrid", mode })` takes the mode, and the synced registry has `setMode()` and `getMode()`.
16
+ - a70d445: `procedure.define` keeps the effects of its body (roadmap R22). `programCheckInterceptor()` checks the body of each definition, refuses a body with an error, and gives its effects to `procedure.define` in the internal metadata key `__inferredEffects`. Each transport removes `__` keys from a request, so a remote caller cannot claim effects. The defined procedure keeps them as `metadata.inferredEffects`, and `toolHints()` uses them: before, a defined procedure had no hints. The admission still examines each nested call. New exports: `INFERRED_EFFECTS_METADATA_KEY`, `inferredEffectsOf()`, `effectsValue()`.
17
+ - bc70357: The file procedures can have a root (roadmap R33). `createFsModule({ root })` gives the procedures of `fsModule`, and each path of each input must be in the root folder. A relative path starts at the root. Without a root, the procedures operate as before.
18
+
19
+ The procedures refuse a path that goes up with `..`, an absolute path outside the root and another drive. They also refuse a symlink or a junction that goes out of the root. The error has the code `FORBIDDEN` and the new reason `outside-root` (the type `ForbiddenReason` of `@procedures/client`). For a path that does not exist, the check uses the real path of the nearest existing folder. `registerFsProcedures(registry, { root })` takes the root too.
20
+ - 07cc998: The procedures of a host (roadmap R19). `hostProcedures(source)` gives `host.health` (the state, the uptime, the procedures and the calls in flight), `host.info` (the id, the start time, the endpoints and the runtime) and `host.metrics` (the counts of `metricsInterceptor` for each procedure). A peer pins them on its server, so the registry of its caller does not change, and counts its calls (option `metrics`, default true). `ProcedureServer.stopping` tells when a graceful stop runs.
21
+ - a6b01a9: The new package `@procedures/http` (roadmap R38) has four procedures: `http.request` (each method, a body of text, JSON or base64, and a response as text, JSON or base64), `http.json` (a GET or a POST of JSON), `http.ndjson` (a stream of the JSON values of the lines) and `http.sse` (a stream of server-sent events). `http.request` and `http.json` declare `reads: ["network"]` and `writes: ["network"]`. The two streams send only a GET, so they declare `writes: []`, and they declare `stream: { unbounded: true }`.
22
+
23
+ The default module reaches each public host and refuses loopback, private, link-local, shared, multicast and reserved addresses. `createHttpModule({ allow, publicHosts, maxRequestBytes, maxResponseBytes, resolve })` gives the procedures with more hosts: host names, wildcards, IP addresses and CIDR ranges, each with an optional port. The check resolves the host name, and the connection uses only the addresses that passed the check. Each redirect gets the same check. A refusal has the code `FORBIDDEN` and the new reason `host-not-allowed` (the type `ForbiddenReason` of `@procedures/client`).
24
+
25
+ A call stops at the earliest of its deadline, the abort of the caller and `timeoutMs`. A stream also stops after `idleTimeoutMs` without data. The body of a response has a limit of 10 MiB by default, and the limit counts the decoded bytes of a compressed body.
26
+ - c799cdd: `HybridStorage` cannot lose data (roadmap R2). A sync gets the ids of the remote items from the new `getEntries()` of a storage (the collection procedure `collections.<name>.entries`), or else from `keyOf`. A remote item without a key stops the sync with `MissingKeysError` before any change: before, such a list emptied the local cache. A write-through that the remote cannot take waits in the queue and throws `PendingWriteError`, and a timer sends the queue. A write that the remote refuses is undone in the local cache. A full queue refuses a write with `QueueFullError`: before, it dropped the oldest operation. A second try of a write replaces its queued operation. A write-through sends the older queued operations first. The reads wait for the first sync.
27
+ - 5b9264e: One deadline model (roadmap R10, REV-6). The overall timeout and the remaining time that a caller gives (`procedure-timeout`) set the deadline of a call, and each try sends its remaining time as `procedure-timeout`. So the server stops the handler when the client stops waiting: before, the timeouts of the client middleware did not reach the server. The timeout of one try is at most the remaining time. The client no longer sends `X-Timeout`, which the server ignored. The new preset `resilience({ overall, perAttempt, retry, breaker, cache, rateLimit })` puts the layers in the correct order, and `createCombinedTimeoutMiddleware` is now that preset (deprecated).
28
+ - 956e88b: One manifest type (roadmap R17). `describeRegistry(registry, { expose })` and `describeProcedure` (exported as `describeProcedureEntry`) describe each procedure: the path, the description, the tags, the effects, the JSON Schemas of the input and the output, the calls, an unbounded stream, the command-line metadata, and a hash of the manifest (`procedures.manifest/1`). The new procedure `procedure.manifest` gives it, and it is an MCP tool of the dev tools (63 tools). `procedure.get` gives the JSON Schemas and the effects. The context of a handler has the expose rule of its host (`ctx.expose`), and `procedure.get`, `procedure.list`, `procedure.manifest`, `procedure.export`, `client.lookup`, `mcp.list-tools`, `manifest.generate` and `_discovery.announce` show only the exposed procedures: before, an MCP tool could list the procedures that the host hides. `_discovery.announce` gives the JSON Schemas, and `manifest.generate`, `getProcedureInfos` and the manifest of `mark` use the one description.
29
+ - ff62281: One trace from the client to the handler (roadmap R16). The tracing middleware makes W3C ids by default and sends a W3C `traceparent` in the metadata (and as the HTTP header `traceparent`). The tracing interceptor of a server continues that trace: the span of a call of the host has the trace of the client and the span of the client as its parent (`remoteParent: true`). The nested calls of a handler carry its span, also to a remote server. The spans of the interceptor have W3C ids. Before, the client and the server made two traces that nothing joined.
30
+ - d81d0ed: The transport over a message port (roadmap R27). `PortTransport` sends calls over a Web Worker, a `MessageChannel` or a `worker_threads` port, in the two directions, with the frames of the WebSocket transport: streams with credit, cancel and errors. Its `serve` option takes a `RequestHandler`, for example a `ProcedureServer`. The site uses it for its two workers in place of two protocols of its own.
31
+ - 061fa22: The timers of the client come from `@mark1russell7/service` 0.4.0 and `@mark1russell7/algebra` 0.5.0 (roadmap R37). The two packages are dependencies of the client now.
32
+
33
+ - The clock is `platformClock()` of `@mark1russell7/service/clock`. The types `Clock` and `Cancel` come from there too, and `Cancel.unref` is read-only.
34
+ - The heartbeat of the WebSocket client is `heartbeat` of `@mark1russell7/service/heartbeat`. A ping whose send throws is a miss. Before, the error left the timer.
35
+ - `reconnectDelay` and the retry middleware use `backoffDelay` of `@mark1russell7/service/backoff`. The delays are the same.
36
+ - `replayTransitions` uses `replay` of `@mark1russell7/algebra/replay`. A jump message says "the runtime left", not "the code left".
37
+
38
+ The four entries import nothing else, and together they have 1.3 kB of gzip.
39
+ - cfb9d69: Retry obeys the effects (roadmap R1). After a try with an indeterminate outcome (a reset, a closed WebSocket, the timeout of one try), the retry middleware sends the call again only when it is idempotent: the `idempotent` option (`true`, or a function such as `idempotentIn(registry)`), or `retry: { idempotent: true }` in the context of the call. A refused connection and a retryable error of the server still go again. Each try of one call sends the same `idempotency-key`, and `ProcedureServer` runs a keyed call to a procedure that is not idempotent one time (option `idempotency`). The error items of the transports tell the delivery of a failed try (`deliveryOf`).
40
+ - 43f7de5: Retry and the circuit breaker (roadmap R12). The retry middleware has a retry budget (`budget`, default: 0.2 of the calls and at least 10 retries a second), so a failing service gets a fraction more requests and not `maxRetries` times more. The delay has full jitter by default (`jitter: 1`: a random time from 0 to the backoff, before ±10 percent). A server that asks for a wait gets it: `retryAfterMs` of a thrown error becomes the `Retry-After` header and the `retry-after` metadata, and the retry waits at least that long. `maxAttempts` in a context counts the tries, as its name says (before, it counted the retries). The circuit breaker counts only the failures of availability (`isAvailabilityFailure`: UNAVAILABLE, TIMEOUT, a stopped server, a gateway error, network errors): an abort or a validation error no longer opens it. Its windows use a monotonic clock. `ApiStorage` asks a retry middleware of its client for one try, so the two loops cannot multiply.
41
+ - b07bb50: The server-transport contract (roadmap R18). `RequestHandler` (`handle(request)`) is the contract of a server transport, and the HTTP and WebSocket transports take one. A `ServerRequest` has `credentials` (the bearer token of `Authorization`, or of the `token` query of a WebSocket) and `principal`. `ProcedureServer` takes `token` (a host policy gives its token) and examines it for each network request, and `authenticate` gives the principal. Before, only the Express middleware of the peer examined the token. The secret headers (`Authorization`, `Cookie`, `Proxy-Authorization`, `X-Api-Key`) no longer go into the metadata of a request, so a handler and a nested call do not see them. `LocalTransport` answers `ABORTED` at once when its caller aborts, as HTTP and WebSocket do. A conformance suite runs the same calls through Local, `handle()`, HTTP, WebSocket and MCP.
42
+ - 4a3bfa1: The timer law of systems for the client (roadmap R34). The new package `@procedures/systems` binds the times of the middleware, the transports and the server to the symbols of the law of `@mark1russell7/service`, and examines six rules of the client: `lintTimers()`, `bindingOf()`, `CLIENT_RULES` and the procedure `systems.timers`. The core exports its default times as data: `TIMER_DEFAULTS`. The middleware, the transports and the server read them. The law found two defaults that broke it, and they change: the WebSocket client pings each 20 s, not 30 s, so it finds a dead connection before a call on it times out. The WebSocket server pings each 15 s, not 30 s, so it finds a dead client before its call to the client times out.
43
+ - fb30718: Typed durations (roadmap R11). This change breaks the API. Each public option that is a time takes a `Duration` of `@mark1russell7/quantity`, made with `ms()`, `seconds()`, `minutes()` or `hours()` of `@procedures/client`. A plain number is a type error, and at run time the option throws a `TypeError` that names it. This covers the timeouts and `resilience()`, the retry delays, the cache, the circuit breaker, the rate limit, the HTTP, WebSocket and mock transports, the WebSocket server heartbeat, `timeoutInterceptor({ limit })` (was `ms`), `hostPolicy({ timeout })` (was `timeoutMs`), the idempotency cache (`ttl`, was `ttlMs`), `ApiStorage`, `HybridStorage`, the synced registry and `connect()` of the peer. The per-call overrides in a context (`timeout`, `retry.delay`, `cache.ttl`) and the `route()` overrides (`timeout.overall`, was `timeout.ms`) take durations too. The wire and the inputs of procedures keep milliseconds.
44
+
45
+ Migration: `createOverallTimeoutMiddleware({ overall: 5000 })` becomes `createOverallTimeoutMiddleware({ overall: seconds(5) })`.
46
+ - c5528e4: Streams that can run without an end (roadmap R15). A procedure declares `stream: { unbounded: true }` in its metadata (`docker.logs`, for `follow`, and `shell.stream` do). `ProcedureServer` marks its stream response, and the faces that collect a stream obey the mark: HTTP without NDJSON answers 406 `NOT_ACCEPTABLE` at once (before, the request waited until a timeout), and the MCP transport stops the stream at its `streamLimits` (default 100 items or 30 seconds) and says so in the result (before, `docker.logs` with `follow` ended only at the time limit of 10 minutes).
47
+ - fee5d9c: WebSocket time limits (roadmap R3). The handshake of the client has a time limit (`connectionTimeout`): an address that never answers no longer keeps the socket in `CONNECTING`. The reconnect delay has full jitter by default (`reconnect.jitter`), so the clients of a server that restarted do not come back at the same moments. The client drops a connection after `heartbeat.misses` pings without a pong (default 1, as before). The server pings each connection and closes one that misses 2 pongs (`WebSocketServerTransport` option `heartbeat`), so it finds a client that went silent.
48
+ - 21f7743: Wire protocol v1 (roadmap R25). `WIRE-PROTOCOL.md` specifies the HTTP and WebSocket formats. A WebSocket connection starts with a `hello` frame of each side (`getServerProtocol()` on the client), and HTTP sends the header `Procedures-Protocol: 1` on each request and response. A side refuses a newer version with `PROTOCOL_VERSION`. `ERROR_CODES` is the one list of error codes and HTTP statuses of all faces. The reason of a `FORBIDDEN` error travels on each face: in the HTTP error body, in the item metadata of the HTTP and local transports, and in the `_meta` of an MCP error result. `Method.version` is deprecated: the server ignores it. Golden tests in `packages/client/test/wire/` pin the frames.
49
+ - 31a0b46: The WebSocket client has the option `webSocket`: the class of its sockets. In Node, give the `WebSocket` of the `ws` package. A drop by the heartbeat or by the time limit of the handshake then ends the socket at once with `terminate()`.
50
+
51
+ The global `WebSocket` has only `close()`, which waits for the answer of the peer. The harness cells of roadmap R36 showed that a socket to a dead peer then stays open until the kernel stops it: 924.6 seconds with the default settings of Linux.
52
+
53
+ ### Patch Changes
54
+
55
+ - ddea529: The array procedures read only the own fields of an item (roadmap R29, as R6 for the object procedures). Before, a key such as `toString` read the function of the prototype: `client.count` counted each item, and `client.pluck` gave functions. This applies to `sort`, `unique`, `groupBy`, `filter`, `where`, `pluck`, `partition` and `count`.
56
+ - 0c627f8: The catalog test of the core procedures found four defects (roadmap R29). `client.replace` gives the replacement as text: before, the replacement of the first match changed `$$` to `$` and `$&` to the match. `client.groupBy` accepts a group with the name of a field of `Object.prototype`: before, a group `constructor` or `toString` failed. `client.arrPick` picks only own fields, as `client.pick` does: before, `toString` picked a function of the prototype. The input schema of `client.template` describes its `{{name}}` placeholders: before, it described `{name}`.
57
+ - 540f8d9: A `$never` operand of a control-flow procedure is data (roadmap R4). Before, only a step of `client.chain` obeyed `$never`: a `$never` branch of `client.conditional`, a try of `client.tryCatch`, a task of `client.parallel` and the `fn` of `client.map` started. A `fn` of `client.map` or `client.reduce` that is not one call is an error: before, it was ignored.
58
+ - b6daaec: The object procedures do not reach a prototype (roadmap R6). `client.set`, `client.get` and `client.has` refuse a path segment `__proto__`, `constructor` or `prototype`: before, `client.set` with the path `__proto__.x` changed the prototype of its result. `client.get`, `client.has` and `client.pick` read only own fields: before, `toString` gave a function of the prototype. `client.merge` and `client.omit` set own data properties.
59
+ - aa67cc7: The client uses `@mark1russell7/quantity` 0.2.0. Its root entry does not import zod. The entries of the client that use zod for their schemas still have it, but a bundle that takes only the Durations no longer gets zod.
60
+ - bd0b626: Four fixes that the tests of roadmap R30 found.
61
+
62
+ - An abort of the caller ends the wait of the timeout middlewares at once, also when the transport ignores the signal. The caller gets `ABORTED`. Before, the caller waited for the timeout and got `TIMEOUT`, and the circuit breaker counted a failure.
63
+ - `WebSocketTransport` starts its reconnects at once when a handshake fails on Node 22. The WebSocket of Node 22 gives an error event and no close event, so before, each try waited for `connectionTimeout`.
64
+ - `createErrorFromException` gives `TIMEOUT` for the reason of `AbortSignal.timeout()`, and `SERIALIZE_ERROR` for a circular structure. Before, it gave `ABORTED` and `PARSE_ERROR`.
65
+ - `isAvailabilityFailure` is true for `CONNECTION_CLOSED`, so the circuit breaker counts a closed connection.
66
+ - a6be873: The timeout middleware keeps the reason of an abort when the platform has no `AbortSignal.any`. Its own composition of the signals now gives the transport the reason of the signal that aborted, as `AbortSignal.any` does. Before, the transport got a new `AbortError` in place of the reason of the caller.
67
+
3
68
  ## 0.2.3
4
69
 
5
70
  ### Patch Changes
package/README.md CHANGED
@@ -250,11 +250,34 @@ new WebSocketTransport(options: WebSocketTransportOptions)
250
250
 
251
251
  interface WebSocketTransportOptions {
252
252
  url: string;
253
- protocols?: string[];
254
- reconnect?: boolean; // default: true
255
- reconnectDelay?: number; // default: 1000ms
256
- heartbeatInterval?: number; // default: 30000ms
253
+ connectionTimeout?: number; // default: 10000ms; a handshake that takes longer closes the socket
254
+ requestTimeout?: number; // default: 30000ms
255
+ streamWindow?: number; // default: 64 items
256
+ reconnect?: {
257
+ enabled?: boolean; // default: true
258
+ maxAttempts?: number; // default: Infinity
259
+ initialDelay?: number; // default: 1000ms
260
+ maxDelay?: number; // default: 30000ms
261
+ backoffMultiplier?: number; // default: 1.5
262
+ jitter?: number; // default: 1 (full jitter: a random delay from 0 to the backoff)
263
+ };
264
+ heartbeat?: {
265
+ enabled?: boolean; // default: true
266
+ interval?: number; // default: 30000ms
267
+ timeout?: number; // default: 5000ms
268
+ misses?: number; // default: 1; pings without a pong that drop the connection
269
+ };
257
270
  }
271
+
272
+ // The server pings each connection, and closes one that misses 2 pongs (option heartbeat)
273
+ new WebSocketServerTransport(server, { server: http, heartbeat: { interval: seconds(30), misses: 2 } });
274
+
275
+ // Calls in the two directions over one WebSocket (roadmap R24). The client serves its own
276
+ // procedures with a ProcedureServer; the server calls them with the same frames: streams with
277
+ // credit, cancel, the signal and the deadline.
278
+ const transport = new WebSocketTransport({ url, serve: new ProcedureServer({ registry: uiRegistry, autoRegister: true }) });
279
+ const toClient = new Client({ transport: wsServer.transportFor(connectionId) }); // or peer.clientFor(id)
280
+ await toClient.call({ service: "ui", operation: "notify" }, { text: "Build done" });
258
281
  ```
259
282
 
260
283
  #### `LocalTransport`
@@ -290,12 +313,17 @@ interface RetryOptions {
290
313
  retryDelay?: number; // default: 1000ms (the base delay)
291
314
  backoffMultiplier?: number; // default: 2
292
315
  maxDelay?: number; // default: no limit
293
- jitter?: number; // default: 0.1 (a fraction of the delay)
316
+ jitter?: number; // default: 1 (full jitter: a random delay from 0 to the backoff)
317
+ budget?: { ratio?: number; minPerSecond?: number } | false; // default: 0.2 of the calls, at least 10 a second
318
+ // After a try with an indeterminate outcome (a reset, the timeout of one try), only an
319
+ // idempotent call goes again: true, or a function such as idempotentIn(registry)
320
+ idempotent?: boolean | ((method: Method) => boolean | undefined);
294
321
  shouldRetry?: (item: ResponseItem<unknown>, attempt: number) => boolean;
295
322
  }
296
323
 
297
- // The overrides of one call, in its context or as route() overrides
298
- client.withContext({ retry: { maxAttempts: 5, delay: 200, backoffMultiplier: 3, maxDelay: 2000 } });
324
+ // A Retry-After of the server makes the delay at least that long.
325
+ // The overrides of one call, in its context or as route() overrides (maxAttempts counts the tries)
326
+ client.withContext({ retry: { maxAttempts: 5, delay: ms(200), backoffMultiplier: 3, maxDelay: seconds(2), idempotent: true } });
299
327
  ```
300
328
 
301
329
  #### Cache Middleware
@@ -311,21 +339,19 @@ interface CacheOptions {
311
339
  }
312
340
 
313
341
  // The overrides of one call, in its context or as route() overrides
314
- client.withContext({ cache: { ttl: 5000 } }); // the life of the stored entry
342
+ client.withContext({ cache: { ttl: seconds(5) } }); // the life of the stored entry
315
343
  client.withContext({ cache: { bypass: true } }); // no read, no write
316
344
  client.withContext({ cache: { refresh: true } }); // fetch, then replace the stored entry
317
345
  ```
318
346
 
319
- #### Timeout Middleware
347
+ #### Timeout Middleware and the resilience preset
320
348
  ```typescript
321
- createTimeoutMiddleware(options: TimeoutOptions)
322
- createOverallTimeoutMiddleware(timeout: number)
323
- createCombinedTimeoutMiddleware(options: { overall: number; perAttempt: number })
349
+ createOverallTimeoutMiddleware({ overall: seconds(10) }) // the whole call, retries included
350
+ createTimeoutMiddleware({ perAttempt: seconds(2) }) // each try
351
+ resilience({ overall, perAttempt, retry, breaker, cache, rateLimit }) // the layers in the correct order
324
352
 
325
- interface TimeoutOptions {
326
- overall?: number; // milliseconds
327
- perAttempt?: number; // milliseconds
328
- }
353
+ // Each time is a Duration of @mark1russell7/quantity: ms(500), seconds(2), minutes(1), hours(1).
354
+ // A plain number is a type error (roadmap R11). The wire keeps milliseconds (procedure-timeout).
329
355
  ```
330
356
 
331
357
  #### Auth Middleware
@@ -347,9 +373,12 @@ interface AuthOptions {
347
373
  createCircuitBreakerMiddleware(options?: CircuitBreakerOptions)
348
374
 
349
375
  interface CircuitBreakerOptions {
350
- threshold?: number; // default: 5 failures
351
- timeout?: number; // default: 60000ms
352
- resetTimeout?: number; // default: 30000ms
376
+ failureThreshold?: number; // default: 5 failures
377
+ failureWindow?: Duration; // default: seconds(10)
378
+ resetTimeout?: Duration; // default: seconds(30)
379
+ successThreshold?: number; // default: 2
380
+ halfOpenMaxRequests?: number;
381
+ isFailure?: (error: Error) => boolean;
353
382
  }
354
383
  ```
355
384
 
@@ -358,19 +387,10 @@ interface CircuitBreakerOptions {
358
387
  createRateLimitMiddleware(options?: RateLimitOptions)
359
388
 
360
389
  interface RateLimitOptions {
361
- maxTokens?: number; // default: 10
362
- refillRate?: number; // tokens per second, default: 1
363
- refillInterval?: number; // milliseconds, default: 1000
364
- }
365
- ```
366
-
367
- #### Batching Middleware
368
- ```typescript
369
- createBatchingMiddleware(options?: BatchingOptions)
370
-
371
- interface BatchingOptions {
372
- maxBatchSize?: number; // default: 10
373
- maxWaitTime?: number; // default: 10ms
390
+ maxRequests?: number; // default: 100
391
+ window?: Duration; // default: minutes(1)
392
+ strategy?: "reject" | "queue"; // default: "reject"
393
+ maxQueueSize?: number; // default: 100
374
394
  }
375
395
  ```
376
396
 
@@ -667,13 +687,16 @@ const error = createError("E_TIMEOUT", {
667
687
 
668
688
  ### Advanced Middleware Composition
669
689
  ```typescript
690
+ // The first use() is the outermost layer. The overall timeout covers the retries, and the
691
+ // timeout of each try sits inside the retry, so the retry can start the next try.
670
692
  const client = new Client({ transport: new HttpTransport({ baseUrl: "/api" }) })
671
- .use(createTimeoutMiddleware({ overall: 10000, perAttempt: 5000 }))
672
- .use(createRetryMiddleware({ maxAttempts: 3, initialDelay: 1000 }))
673
- .use(createCacheMiddleware({ methods: ["users.get"], capacity: 100, ttl: 60000 }))
674
- .use(createCircuitBreakerMiddleware({ threshold: 5, timeout: 60000 }))
675
- .use(createRateLimitMiddleware({ maxTokens: 10, refillRate: 1 }))
676
693
  .use(createTracingMiddleware())
694
+ .use(createOverallTimeoutMiddleware({ overall: seconds(10) }))
695
+ .use(createCacheMiddleware({ methods: ["users.get"], capacity: 100, ttl: minutes(1) }))
696
+ .use(createRetryMiddleware({ maxRetries: 3, retryDelay: seconds(1), idempotent: (method) => method.operation === "get" }))
697
+ .use(createCircuitBreakerMiddleware({ failureThreshold: 5, resetTimeout: minutes(1) }))
698
+ .use(createRateLimitMiddleware({ maxRequests: 10, window: seconds(1) }))
699
+ .use(createTimeoutMiddleware({ perAttempt: seconds(5) }))
677
700
  .use(createZodMiddleware());
678
701
 
679
702
  // Context inheritance
@@ -713,7 +736,7 @@ const result = await client.route({
713
736
  batch: { strategy: "all", maxConcurrency: 5 },
714
737
  middlewares: {
715
738
  retry: { attempts: 3 },
716
- timeout: { overall: 10000 },
739
+ timeout: { overall: seconds(10) },
717
740
  },
718
741
  route: {
719
742
  users: {