@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
@@ -7,6 +7,18 @@
7
7
  import type { Transport, Message, ResponseItem } from "../../../client/types.js";
8
8
  import type { WebSocketTransportOptions } from "./types.js";
9
9
  import { WebSocketState } from "./types.js";
10
+ /**
11
+ * The delay before a reconnect try (from 1): an exponential backoff up to `maxDelay`, with a
12
+ * random part. With `jitter: 1` (full jitter, the default), the delay is a random time from 0 to
13
+ * the backoff, so the clients of a server that restarted do not come back at the same moments
14
+ * (roadmap R3). The backoff is `backoffDelay` of `@mark1russell7/service/backoff` (roadmap R37).
15
+ */
16
+ export declare function reconnectDelay(attempt: number, options: {
17
+ initialDelay: number;
18
+ backoffMultiplier: number;
19
+ maxDelay: number;
20
+ jitter: number;
21
+ }, random?: () => number): number;
10
22
  export declare class WebSocketTransport implements Transport {
11
23
  readonly name = "websocket";
12
24
  private ws;
@@ -26,14 +38,30 @@ export declare class WebSocketTransport implements Transport {
26
38
  private closing;
27
39
  private reconnectAttempts;
28
40
  private reconnectTimer;
29
- private heartbeatTimer;
30
- private heartbeatTimeout;
41
+ /** The heartbeat of the connection: `heartbeat` of `@mark1russell7/service/heartbeat` (roadmap R37) */
42
+ private heartbeat;
43
+ /** The clock of each timer of the transport (roadmap R35) */
44
+ private readonly clock;
45
+ /** The class of the sockets, or undefined for the global `WebSocket` (finding of roadmap R36) */
46
+ private readonly webSocketClass;
47
+ /** The observer of the changes of the state (roadmap R35) */
48
+ private readonly transitionHandler;
49
+ /** The protocol version of the server, from its hello, or undefined (an older server) (R25) */
50
+ private serverProtocol;
51
+ /** The calls that the server sends on the current socket, served by `serve` (roadmap R24) */
52
+ private incoming;
53
+ /** The time limit of the handshake of the socket that opens now */
54
+ private handshakeTimer;
31
55
  constructor(options: WebSocketTransportOptions);
32
- /** Change the state, and tell the callers that wait for a connection. */
56
+ /**
57
+ * Change the state, and tell the callers that wait for a connection. `on` is the event of
58
+ * `WEBSOCKET_CONNECTION_MACHINE`: a change goes to `onTransition` (roadmap R35).
59
+ */
33
60
  private setState;
34
61
  /**
35
62
  * Connect to WebSocket server.
36
63
  */
64
+ /** `on` is the event of the machine: the first connect, or the end of a backoff. */
37
65
  private connect;
38
66
  /**
39
67
  * Leave the current socket without waiting for its close handshake, and handle the close
@@ -52,16 +80,23 @@ export declare class WebSocketTransport implements Transport {
52
80
  */
53
81
  private scheduleReconnect;
54
82
  /**
55
- * Start heartbeat to keep connection alive.
83
+ * Start the heartbeat of the connection: a ping each interval on the clock, one ping at a time.
84
+ * A ping without a pong at its timeout is a miss. The connection drops after `misses` misses in
85
+ * sequence. A ping whose send throws is a miss too.
56
86
  */
57
87
  private startHeartbeat;
58
- /**
59
- * Stop heartbeat.
60
- */
88
+ /** Stop the time limit of the handshake. */
89
+ private clearHandshakeTimer;
90
+ /** Stop the heartbeat: its ticks, its timeout and its count of misses. */
61
91
  private stopHeartbeat;
62
92
  /**
63
93
  * Handle incoming message.
64
94
  */
95
+ /**
96
+ * The calls of the server on a socket, served by the `serve` option (or by `onServerRequest`, or
97
+ * with `NOT_FOUND`), with the same frames as the calls of the client (roadmap R24).
98
+ */
99
+ private servedCallsOf;
65
100
  private handleMessage;
66
101
  /**
67
102
  * Send a request and yield its response items as they arrive: one item for a request/response
@@ -94,6 +129,11 @@ export declare class WebSocketTransport implements Transport {
94
129
  * Close WebSocket connection. The requests in progress fail.
95
130
  */
96
131
  close(): Promise<void>;
132
+ /**
133
+ * The protocol version of the server: from its hello, or undefined before the hello or for a
134
+ * server older than protocol v1 (roadmap R25).
135
+ */
136
+ getServerProtocol(): number | undefined;
97
137
  /**
98
138
  * Get current connection state.
99
139
  */
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/adapters/websocket/client/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,KAAK,EAAE,yBAAyB,EAAwD,MAAM,YAAY,CAAC;AAClH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAsF5C,qBAAa,kBAAmB,YAAW,SAAS;IAClD,QAAQ,CAAC,IAAI,eAAe;IAE5B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAuBb;IACF,2CAA2C;IAC3C,OAAO,CAAC,eAAe,CAA0C;IACjE;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAK;IACpB,2FAA2F;IAC3F,OAAO,CAAC,cAAc,CAAyB;IAC/C,0FAA0F;IAC1F,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAAwD;IAC9E,OAAO,CAAC,cAAc,CAAwD;IAC9E,OAAO,CAAC,gBAAgB,CAAwD;IAEhF,YAAY,OAAO,EAAE,yBAAyB,EA4B7C;IAED,yEAAyE;IACzE,OAAO,CAAC,QAAQ;IAKhB;;OAEG;IACH,OAAO,CAAC,OAAO;IAiEf;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAiBtB,yEAAyE;IACzE,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAqBnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAuBtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB;;OAEG;IACH,OAAO,CAAC,aAAa;IA8HrB;;;;;;;;;;;OAWG;IACI,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CA2EjF;IAED,qFAAqF;IACrF,OAAO,CAAC,UAAU;IAOlB,wEAAwE;IACxE,OAAO,CAAC,UAAU;IAOlB;;;;OAIG;YACW,iBAAiB;IA4B/B;;OAEG;YACW,mBAAmB;IA8CjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA0B3B;IAED;;OAEG;IACH,QAAQ,IAAI,cAAc,CAEzB;IAED;;OAEG;IACH,WAAW,IAAI,OAAO,CAErB;CACF"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/adapters/websocket/client/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,KAAK,EAAE,yBAAyB,EAAwE,MAAM,YAAY,CAAC;AAClI,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA8I5C;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC9F,MAAM,GAAE,MAAM,MAAoB,GACjC,MAAM,CAaR;AAED,qBAAa,kBAAmB,YAAW,SAAS;IAClD,QAAQ,CAAC,IAAI,eAAe;IAE5B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CA0Bb;IACF,2CAA2C;IAC3C,OAAO,CAAC,eAAe,CAA0C;IACjE;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAK;IACpB,2FAA2F;IAC3F,OAAO,CAAC,cAAc,CAAyB;IAC/C,0FAA0F;IAC1F,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAAiC;IACvD,uGAAuG;IACvG,OAAO,CAAC,SAAS,CAAoC;IACrD,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,iGAAiG;IACjG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwD;IAC1F,+FAA+F;IAC/F,OAAO,CAAC,cAAc,CAAqB;IAC3C,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAA4B;IAC5C,mEAAmE;IACnE,OAAO,CAAC,cAAc,CAAiC;IAEvD,YAAY,OAAO,EAAE,yBAAyB,EAkC7C;IAED;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;OAEG;IACH,oFAAoF;IACpF,OAAO,CAAC,OAAO;IAwFf;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAkBtB,yEAAyE;IACzE,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAwBnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAsBtB,4CAA4C;IAC5C,OAAO,CAAC,mBAAmB;IAO3B,0EAA0E;IAC1E,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACH;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;IAsIrB;;;;;;;;;;;OAWG;IACI,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CA2EjF;IAED,qFAAqF;IACrF,OAAO,CAAC,UAAU;IAOlB,wEAAwE;IACxE,OAAO,CAAC,UAAU;IAOlB;;;;OAIG;YACW,iBAAiB;IA4B/B;;OAEG;YACW,mBAAmB;IA8CjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA0B3B;IAED;;;OAGG;IACH,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED;;OAEG;IACH,QAAQ,IAAI,cAAc,CAEzB;IAED;;OAEG;IACH,WAAW,IAAI,OAAO,CAErB;CACF"}
@@ -6,6 +6,15 @@
6
6
  */
7
7
  import { WebSocketState } from "./types.js";
8
8
  import { withoutInternalKeys } from "../../metadata.js";
9
+ import { IncomingCalls } from "../shared/rpc.js";
10
+ import { methodToPath } from "../../../server/method.js";
11
+ import { optionalMs } from "../../../duration.js";
12
+ import { PROTOCOL_VERSION, protocolVersionOf } from "../../../protocol.js";
13
+ import { TIMER_DEFAULTS } from "../../../timers.js";
14
+ import { realClock } from "../../../clock.js";
15
+ import { heartbeat } from "@mark1russell7/service/heartbeat";
16
+ import { backoffDelay } from "@mark1russell7/service/backoff";
17
+ import { WEBSOCKET_CONNECTION_MACHINE } from "../../../machines.js";
9
18
  /**
10
19
  * Pending request waiting for response.
11
20
  */
@@ -68,12 +77,68 @@ class ItemQueue {
68
77
  * const client = new Client({ transport });
69
78
  * ```
70
79
  */
80
+ /** The `readyState` of an open socket. The global `WebSocket` and the `ws` package use the same number. */
81
+ const OPEN = 1;
82
+ /**
83
+ * The error handler of a socket that the transport left. A `WebSocket` of the `ws` package that
84
+ * closes during its handshake emits an error. Without a handler, that error ends the process.
85
+ */
86
+ const ignoreError = () => undefined;
87
+ /** This function removes the handlers of the transport from a socket, and keeps `ignoreError`. */
88
+ function detach(ws) {
89
+ ws.onopen = null;
90
+ ws.onmessage = null;
91
+ ws.onclose = null;
92
+ ws.onerror = ignoreError;
93
+ }
71
94
  function errorItem(id, code, message, retryable) {
72
95
  return { id, status: { type: "error", code, message, retryable }, payload: null, metadata: {} };
73
96
  }
74
97
  function abortedItem(id) {
75
98
  return errorItem(id, "ABORTED", "Request was aborted", false);
76
99
  }
100
+ /**
101
+ * The handler of the calls of the server without a `serve` option: the older `onServerRequest`
102
+ * function (it gets the path and the input), or else a `NOT_FOUND` answer.
103
+ */
104
+ function legacyHandler(onServerRequest) {
105
+ return {
106
+ async handle(request) {
107
+ if (!onServerRequest) {
108
+ return {
109
+ id: request.id,
110
+ status: { type: "error", code: "NOT_FOUND", message: "This client serves no procedures", retryable: false },
111
+ metadata: {},
112
+ };
113
+ }
114
+ try {
115
+ const payload = await onServerRequest(methodToPath(request.method), request.payload);
116
+ return { id: request.id, status: { type: "success", code: 200 }, payload, metadata: {} };
117
+ }
118
+ catch (error) {
119
+ return {
120
+ id: request.id,
121
+ status: { type: "error", code: "HANDLER_ERROR", message: error instanceof Error ? error.message : String(error), retryable: false },
122
+ metadata: {},
123
+ };
124
+ }
125
+ },
126
+ };
127
+ }
128
+ /**
129
+ * The delay before a reconnect try (from 1): an exponential backoff up to `maxDelay`, with a
130
+ * random part. With `jitter: 1` (full jitter, the default), the delay is a random time from 0 to
131
+ * the backoff, so the clients of a server that restarted do not come back at the same moments
132
+ * (roadmap R3). The backoff is `backoffDelay` of `@mark1russell7/service/backoff` (roadmap R37).
133
+ */
134
+ export function reconnectDelay(attempt, options, random = Math.random) {
135
+ return Math.round(backoffDelay({
136
+ base: options.initialDelay,
137
+ factor: options.backoffMultiplier,
138
+ cap: options.maxDelay,
139
+ jitter: { kind: "subtract", fraction: options.jitter },
140
+ }, attempt, random));
141
+ }
77
142
  export class WebSocketTransport {
78
143
  name = "websocket";
79
144
  ws = null;
@@ -93,62 +158,106 @@ export class WebSocketTransport {
93
158
  closing = false;
94
159
  reconnectAttempts = 0;
95
160
  reconnectTimer = undefined;
96
- heartbeatTimer = undefined;
97
- heartbeatTimeout = undefined;
161
+ /** The heartbeat of the connection: `heartbeat` of `@mark1russell7/service/heartbeat` (roadmap R37) */
162
+ heartbeat = undefined;
163
+ /** The clock of each timer of the transport (roadmap R35) */
164
+ clock;
165
+ /** The class of the sockets, or undefined for the global `WebSocket` (finding of roadmap R36) */
166
+ webSocketClass;
167
+ /** The observer of the changes of the state (roadmap R35) */
168
+ transitionHandler;
169
+ /** The protocol version of the server, from its hello, or undefined (an older server) (R25) */
170
+ serverProtocol;
171
+ /** The calls that the server sends on the current socket, served by `serve` (roadmap R24) */
172
+ incoming;
173
+ /** The time limit of the handshake of the socket that opens now */
174
+ handshakeTimer = undefined;
98
175
  constructor(options) {
99
176
  this.options = {
100
177
  url: options.url,
101
178
  reconnect: {
102
179
  enabled: options.reconnect?.enabled ?? true,
103
180
  maxAttempts: options.reconnect?.maxAttempts ?? Infinity,
104
- initialDelay: options.reconnect?.initialDelay ?? 1000,
105
- maxDelay: options.reconnect?.maxDelay ?? 30000,
181
+ initialDelay: optionalMs(options.reconnect?.initialDelay, "reconnect.initialDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectInitialDelayMs,
182
+ maxDelay: optionalMs(options.reconnect?.maxDelay, "reconnect.maxDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectMaxDelayMs,
106
183
  backoffMultiplier: options.reconnect?.backoffMultiplier ?? 1.5,
184
+ jitter: Math.min(1, Math.max(0, options.reconnect?.jitter ?? 1)),
107
185
  },
108
- connectionTimeout: options.connectionTimeout ?? 10000,
109
- requestTimeout: options.requestTimeout ?? 30000,
186
+ connectionTimeout: optionalMs(options.connectionTimeout, "connectionTimeout") ?? TIMER_DEFAULTS.webSocketClient.connectionTimeoutMs,
187
+ requestTimeout: optionalMs(options.requestTimeout, "requestTimeout") ?? TIMER_DEFAULTS.webSocketClient.requestTimeoutMs,
110
188
  streamWindow: Math.max(2, options.streamWindow ?? 64),
111
189
  heartbeat: {
112
190
  enabled: options.heartbeat?.enabled ?? true,
113
- interval: options.heartbeat?.interval ?? 30000,
114
- timeout: options.heartbeat?.timeout ?? 5000,
191
+ interval: optionalMs(options.heartbeat?.interval, "heartbeat.interval") ?? TIMER_DEFAULTS.webSocketClient.heartbeatIntervalMs,
192
+ timeout: optionalMs(options.heartbeat?.timeout, "heartbeat.timeout") ?? TIMER_DEFAULTS.webSocketClient.heartbeatTimeoutMs,
193
+ misses: Math.max(1, options.heartbeat?.misses ?? TIMER_DEFAULTS.webSocketClient.heartbeatMisses),
115
194
  },
116
195
  onConnect: options.onConnect,
117
196
  onDisconnect: options.onDisconnect,
118
197
  onReconnecting: options.onReconnecting,
119
198
  onError: options.onError,
120
199
  onServerRequest: options.onServerRequest,
200
+ serve: options.serve,
121
201
  onEvent: options.onEvent,
122
202
  };
203
+ this.clock = options.clock ?? realClock;
204
+ this.webSocketClass = options.webSocket;
205
+ this.transitionHandler = options.onTransition;
123
206
  // Auto-connect
124
207
  this.connect();
125
208
  }
126
- /** Change the state, and tell the callers that wait for a connection. */
127
- setState(state) {
209
+ /**
210
+ * Change the state, and tell the callers that wait for a connection. `on` is the event of
211
+ * `WEBSOCKET_CONNECTION_MACHINE`: a change goes to `onTransition` (roadmap R35).
212
+ */
213
+ setState(state, on) {
214
+ const from = this.state;
128
215
  this.state = state;
216
+ if (from !== state) {
217
+ this.transitionHandler?.({ machine: WEBSOCKET_CONNECTION_MACHINE.id, instance: this.name, from, on, to: state, at: this.clock.now() });
218
+ }
129
219
  for (const listener of [...this.stateListeners])
130
220
  listener();
131
221
  }
132
222
  /**
133
223
  * Connect to WebSocket server.
134
224
  */
135
- connect() {
225
+ /** `on` is the event of the machine: the first connect, or the end of a backoff. */
226
+ connect(on = "connect") {
136
227
  if (this.closing) {
137
228
  return;
138
229
  }
139
230
  if (this.state === WebSocketState.CONNECTING || this.state === WebSocketState.CONNECTED) {
140
231
  return;
141
232
  }
142
- this.setState(WebSocketState.CONNECTING);
233
+ this.setState(WebSocketState.CONNECTING, on);
143
234
  try {
144
- const ws = new WebSocket(this.options.url);
235
+ const WebSocketClass = (this.webSocketClass ?? WebSocket);
236
+ const ws = new WebSocketClass(this.options.url);
145
237
  this.ws = ws;
238
+ // The calls of the server on this socket (roadmap R24)
239
+ this.incoming = this.servedCallsOf(ws);
240
+ // The handshake has a time limit: an address that never answers does not keep the socket
241
+ // in CONNECTING (roadmap R3)
242
+ this.clearHandshakeTimer();
243
+ this.handshakeTimer = this.clock.after(this.options.connectionTimeout, () => {
244
+ this.handshakeTimer = undefined;
245
+ if (this.ws === ws && this.state === WebSocketState.CONNECTING) {
246
+ console.error(`[${this.name}] Handshake timeout after ${this.options.connectionTimeout}ms`);
247
+ this.dropConnection("Handshake timeout");
248
+ }
249
+ });
146
250
  // Connection opened
147
251
  ws.onopen = () => {
148
252
  if (this.ws !== ws)
149
253
  return;
254
+ this.clearHandshakeTimer();
150
255
  this.reconnectAttempts = 0;
151
- this.setState(WebSocketState.CONNECTED);
256
+ // The protocol version of this client (roadmap R25). An older server answers with an
257
+ // error frame, which the client ignores.
258
+ this.serverProtocol = undefined;
259
+ ws.send(JSON.stringify({ id: "hello", type: "hello", protocol: PROTOCOL_VERSION }));
260
+ this.setState(WebSocketState.CONNECTED, "open");
152
261
  console.error(`[${this.name}] Connected to ${this.options.url}`);
153
262
  if (this.options.onConnect) {
154
263
  this.options.onConnect();
@@ -186,6 +295,11 @@ export class WebSocketTransport {
186
295
  if (this.options.onError) {
187
296
  this.options.onError(error);
188
297
  }
298
+ // A failed handshake: the WebSocket of Node 22 gives no close event after this error, and
299
+ // stays in CONNECTING. The transport closes it now, so the reconnect starts at once
300
+ // (roadmap R30: before, each try waited for the time limit of the handshake).
301
+ if (this.state === WebSocketState.CONNECTING)
302
+ this.dropConnection("Connection failed");
189
303
  };
190
304
  }
191
305
  catch (error) {
@@ -203,12 +317,15 @@ export class WebSocketTransport {
203
317
  const ws = this.ws;
204
318
  this.ws = null;
205
319
  if (ws) {
206
- ws.onopen = null;
207
- ws.onmessage = null;
208
- ws.onclose = null;
209
- ws.onerror = null;
320
+ detach(ws);
210
321
  try {
211
- ws.close();
322
+ // A socket of the ws package ends at once. close() waits for the answer of a peer that can
323
+ // be dead (finding of roadmap R36).
324
+ const terminate = ws.terminate;
325
+ if (typeof terminate === "function")
326
+ terminate.call(ws);
327
+ else
328
+ ws.close();
212
329
  }
213
330
  catch {
214
331
  // The socket is unusable: nothing more to do
@@ -220,9 +337,7 @@ export class WebSocketTransport {
220
337
  failPending(reason) {
221
338
  for (const pending of this.pendingRequests.values()) {
222
339
  pending.queue.fail(new Error(reason));
223
- if (pending.timeout) {
224
- clearTimeout(pending.timeout);
225
- }
340
+ pending.timeout?.();
226
341
  }
227
342
  this.pendingRequests.clear();
228
343
  }
@@ -231,10 +346,13 @@ export class WebSocketTransport {
231
346
  */
232
347
  handleClose(reason) {
233
348
  this.stopHeartbeat();
349
+ this.incoming?.abortAll();
350
+ this.incoming = undefined;
351
+ this.clearHandshakeTimer();
234
352
  // An intentional close() must not reconnect (BUGS-2026-07 H6)
235
353
  const reconnect = this.options.reconnect;
236
354
  const willReconnect = !this.closing && reconnect.enabled && this.reconnectAttempts < reconnect.maxAttempts;
237
- this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED);
355
+ this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED, willReconnect ? "drop" : "give-up");
238
356
  console.error(`[${this.name}] Disconnected${reason ? `: ${reason}` : ""}`);
239
357
  if (this.options.onDisconnect) {
240
358
  this.options.onDisconnect(reason);
@@ -249,68 +367,99 @@ export class WebSocketTransport {
249
367
  */
250
368
  scheduleReconnect() {
251
369
  if (this.reconnectTimer) {
252
- clearTimeout(this.reconnectTimer);
370
+ this.reconnectTimer();
253
371
  }
254
372
  this.reconnectAttempts++;
255
- this.setState(WebSocketState.RECONNECTING);
256
- const reconnect = this.options.reconnect;
257
- const delay = Math.min(reconnect.initialDelay * Math.pow(reconnect.backoffMultiplier, this.reconnectAttempts - 1), reconnect.maxDelay);
373
+ this.setState(WebSocketState.RECONNECTING, "drop");
374
+ const delay = reconnectDelay(this.reconnectAttempts, this.options.reconnect);
258
375
  console.error(`[${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
259
376
  if (this.options.onReconnecting) {
260
377
  this.options.onReconnecting(this.reconnectAttempts);
261
378
  }
262
- this.reconnectTimer = setTimeout(() => {
379
+ this.reconnectTimer = this.clock.after(delay, () => {
263
380
  this.reconnectTimer = undefined;
264
381
  // connect() returns at once in RECONNECTING only for CONNECTING/CONNECTED
265
- this.connect();
266
- }, delay);
382
+ this.connect("reconnect-due");
383
+ });
267
384
  }
268
385
  /**
269
- * Start heartbeat to keep connection alive.
386
+ * Start the heartbeat of the connection: a ping each interval on the clock, one ping at a time.
387
+ * A ping without a pong at its timeout is a miss. The connection drops after `misses` misses in
388
+ * sequence. A ping whose send throws is a miss too.
270
389
  */
271
390
  startHeartbeat() {
272
391
  this.stopHeartbeat();
273
- this.heartbeatTimer = setInterval(() => {
274
- // One ping at a time: a new ping must not replace the timeout of an unanswered one
275
- if (this.state === WebSocketState.CONNECTED && this.ws && !this.heartbeatTimeout) {
276
- // Send ping
392
+ this.heartbeat = heartbeat({
393
+ interval: this.options.heartbeat.interval,
394
+ timeout: this.options.heartbeat.timeout,
395
+ misses: this.options.heartbeat.misses,
396
+ clock: this.clock,
397
+ due: () => this.state === WebSocketState.CONNECTED && this.ws !== null,
398
+ ping: () => {
277
399
  const ping = {
278
400
  id: `ping-${Date.now()}`,
279
401
  type: "ping",
280
402
  };
281
- this.ws.send(JSON.stringify(ping));
282
- // Set timeout for pong
283
- this.heartbeatTimeout = setTimeout(() => {
284
- this.heartbeatTimeout = undefined;
285
- console.warn(`[${this.name}] Heartbeat timeout - dropping connection`);
286
- this.dropConnection("Heartbeat timeout");
287
- }, this.options.heartbeat.timeout);
288
- }
289
- }, this.options.heartbeat.interval);
403
+ this.ws?.send(JSON.stringify(ping));
404
+ },
405
+ onDead: () => {
406
+ console.warn(`[${this.name}] Heartbeat timeout - dropping connection`);
407
+ this.dropConnection("Heartbeat timeout");
408
+ },
409
+ });
290
410
  }
291
- /**
292
- * Stop heartbeat.
293
- */
294
- stopHeartbeat() {
295
- if (this.heartbeatTimer) {
296
- clearInterval(this.heartbeatTimer);
297
- this.heartbeatTimer = undefined;
298
- }
299
- if (this.heartbeatTimeout) {
300
- clearTimeout(this.heartbeatTimeout);
301
- this.heartbeatTimeout = undefined;
411
+ /** Stop the time limit of the handshake. */
412
+ clearHandshakeTimer() {
413
+ if (this.handshakeTimer) {
414
+ this.handshakeTimer();
415
+ this.handshakeTimer = undefined;
302
416
  }
303
417
  }
418
+ /** Stop the heartbeat: its ticks, its timeout and its count of misses. */
419
+ stopHeartbeat() {
420
+ this.heartbeat?.stop();
421
+ this.heartbeat = undefined;
422
+ }
304
423
  /**
305
424
  * Handle incoming message.
306
425
  */
426
+ /**
427
+ * The calls of the server on a socket, served by the `serve` option (or by `onServerRequest`, or
428
+ * with `NOT_FOUND`), with the same frames as the calls of the client (roadmap R24).
429
+ */
430
+ servedCallsOf(ws) {
431
+ const handler = this.options.serve ?? legacyHandler(this.options.onServerRequest);
432
+ return new IncomingCalls({
433
+ handler,
434
+ send: (frame) => ws.send(JSON.stringify(frame)),
435
+ isOpen: () => this.ws === ws && ws.readyState === OPEN,
436
+ // A call of the server that this client opened: the client trusts it, so it has no
437
+ // credentials (a ProcedureServer applies no token to it)
438
+ requestOf: (frame, signal) => ({
439
+ id: frame.id,
440
+ method: frame.method,
441
+ payload: frame.payload === undefined ? {} : frame.payload,
442
+ metadata: withoutInternalKeys(frame.metadata),
443
+ signal,
444
+ }),
445
+ });
446
+ }
307
447
  handleMessage(message) {
448
+ // The hello of the server, or the error of an older server that does not know it (R25)
449
+ if (message.id === "hello" && (message.type === "hello" || message.type === "error")) {
450
+ if (message.type === "hello")
451
+ this.serverProtocol = protocolVersionOf(message.protocol);
452
+ else if (message.error?.code === "PROTOCOL_VERSION")
453
+ this.options.onError?.(new Error(message.error.message));
454
+ return;
455
+ }
456
+ // A call of the server, its cancel or its credit (roadmap R24)
457
+ if ((message.type === "request" || message.type === "cancel" || message.type === "credit") && this.incoming?.receive(message)) {
458
+ return;
459
+ }
308
460
  // Handle pong (heartbeat response)
309
461
  if (message.type === "pong") {
310
- if (this.heartbeatTimeout) {
311
- clearTimeout(this.heartbeatTimeout);
312
- this.heartbeatTimeout = undefined;
313
- }
462
+ this.heartbeat?.answer();
314
463
  return;
315
464
  }
316
465
  // Handle ping (server heartbeat)
@@ -347,7 +496,7 @@ export class WebSocketTransport {
347
496
  }
348
497
  // The first frame arrived: the request timeout ends. A stream can stay open longer.
349
498
  if (pending.timeout) {
350
- clearTimeout(pending.timeout);
499
+ pending.timeout();
351
500
  pending.timeout = undefined;
352
501
  }
353
502
  // A stream frame: one item, or the end of the stream (BUGS-2026-07 H5: before, the
@@ -457,7 +606,7 @@ export class WebSocketTransport {
457
606
  };
458
607
  const queue = new ItemQueue();
459
608
  const pending = { messageId: message.id, queue };
460
- pending.timeout = setTimeout(() => {
609
+ pending.timeout = this.clock.after(this.options.requestTimeout, () => {
461
610
  if (this.pendingRequests.get(wireId) !== pending)
462
611
  return;
463
612
  this.pendingRequests.delete(wireId);
@@ -467,14 +616,13 @@ export class WebSocketTransport {
467
616
  // so a retry middleware must not repeat it (deep dive TRN-8)
468
617
  queue.push(errorItem(message.id, "TIMEOUT", `Request timeout after ${this.options.requestTimeout}ms`, false));
469
618
  queue.end();
470
- }, this.options.requestTimeout);
619
+ });
471
620
  this.pendingRequests.set(wireId, pending);
472
621
  const onAbort = () => {
473
622
  if (this.pendingRequests.get(wireId) !== pending)
474
623
  return;
475
624
  this.pendingRequests.delete(wireId);
476
- if (pending.timeout)
477
- clearTimeout(pending.timeout);
625
+ pending.timeout?.();
478
626
  this.sendCancel(wireId);
479
627
  queue.push(abortedItem(message.id));
480
628
  queue.end();
@@ -500,8 +648,7 @@ export class WebSocketTransport {
500
648
  }
501
649
  finally {
502
650
  message.signal?.removeEventListener("abort", onAbort);
503
- if (pending.timeout)
504
- clearTimeout(pending.timeout);
651
+ pending.timeout?.();
505
652
  if (this.pendingRequests.get(wireId) === pending) {
506
653
  // The reader stopped before the end: the server stops the stream
507
654
  this.pendingRequests.delete(wireId);
@@ -537,7 +684,7 @@ export class WebSocketTransport {
537
684
  }
538
685
  return new Promise((resolve, reject) => {
539
686
  const finish = (error) => {
540
- clearTimeout(timeout);
687
+ timeout();
541
688
  this.stateListeners.delete(check);
542
689
  signal?.removeEventListener("abort", onAbort);
543
690
  if (error)
@@ -553,7 +700,7 @@ export class WebSocketTransport {
553
700
  };
554
701
  // The caller aborted: send() then gives the ABORTED item
555
702
  const onAbort = () => finish();
556
- const timeout = setTimeout(() => finish(new Error("Connection timeout")), this.options.connectionTimeout);
703
+ const timeout = this.clock.after(this.options.connectionTimeout, () => finish(new Error("Connection timeout")));
557
704
  this.stateListeners.add(check);
558
705
  signal?.addEventListener("abort", onAbort, { once: true });
559
706
  });
@@ -609,28 +756,35 @@ export class WebSocketTransport {
609
756
  */
610
757
  async close() {
611
758
  this.closing = true;
612
- this.setState(WebSocketState.DISCONNECTING);
759
+ this.setState(WebSocketState.DISCONNECTING, "close");
613
760
  this.stopHeartbeat();
761
+ this.clearHandshakeTimer();
762
+ this.incoming?.abortAll();
763
+ this.incoming = undefined;
614
764
  if (this.reconnectTimer) {
615
- clearTimeout(this.reconnectTimer);
765
+ this.reconnectTimer();
616
766
  this.reconnectTimer = undefined;
617
767
  }
618
768
  const hadSocket = this.ws !== null;
619
769
  if (this.ws) {
620
770
  const ws = this.ws;
621
771
  this.ws = null;
622
- ws.onopen = null;
623
- ws.onmessage = null;
624
- ws.onclose = null;
625
- ws.onerror = null;
772
+ detach(ws);
626
773
  ws.close();
627
774
  }
628
775
  this.failPending("WebSocket connection closed");
629
- this.setState(WebSocketState.DISCONNECTED);
776
+ this.setState(WebSocketState.DISCONNECTED, "closed");
630
777
  if (hadSocket && this.options.onDisconnect) {
631
778
  this.options.onDisconnect("closed");
632
779
  }
633
780
  }
781
+ /**
782
+ * The protocol version of the server: from its hello, or undefined before the hello or for a
783
+ * server older than protocol v1 (roadmap R25).
784
+ */
785
+ getServerProtocol() {
786
+ return this.serverProtocol;
787
+ }
634
788
  /**
635
789
  * Get current connection state.
636
790
  */