effect 4.0.2 → 4.0.4

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 (389) hide show
  1. package/AGENTS.md +24 -0
  2. package/CLAUDE.md +24 -0
  3. package/ai-docs/package.json +2 -2
  4. package/ai-docs/src/52_graphql/10_basics.ts +111 -0
  5. package/ai-docs/src/52_graphql/20_testing.ts +45 -0
  6. package/ai-docs/src/52_graphql/30_batching.ts +116 -0
  7. package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
  8. package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
  9. package/ai-docs/src/52_graphql/index.md +6 -0
  10. package/dist/Context.d.ts.map +1 -1
  11. package/dist/Context.js +12 -3
  12. package/dist/Context.js.map +1 -1
  13. package/dist/Crypto.d.ts +33 -5
  14. package/dist/Crypto.d.ts.map +1 -1
  15. package/dist/Crypto.js +8 -2
  16. package/dist/Crypto.js.map +1 -1
  17. package/dist/DateTime.d.ts +9 -6
  18. package/dist/DateTime.d.ts.map +1 -1
  19. package/dist/DateTime.js +9 -6
  20. package/dist/DateTime.js.map +1 -1
  21. package/dist/Duration.d.ts +6 -6
  22. package/dist/Duration.d.ts.map +1 -1
  23. package/dist/Duration.js +27 -8
  24. package/dist/Duration.js.map +1 -1
  25. package/dist/FileSystem.d.ts +44 -2
  26. package/dist/FileSystem.d.ts.map +1 -1
  27. package/dist/FileSystem.js.map +1 -1
  28. package/dist/RequestResolver.d.ts.map +1 -1
  29. package/dist/RequestResolver.js +4 -10
  30. package/dist/RequestResolver.js.map +1 -1
  31. package/dist/Scheduler.d.ts +9 -0
  32. package/dist/Scheduler.d.ts.map +1 -1
  33. package/dist/Scheduler.js +9 -0
  34. package/dist/Scheduler.js.map +1 -1
  35. package/dist/Schema.d.ts +481 -1
  36. package/dist/Schema.d.ts.map +1 -1
  37. package/dist/Schema.js +362 -4
  38. package/dist/Schema.js.map +1 -1
  39. package/dist/SchemaGetter.d.ts +4 -0
  40. package/dist/SchemaGetter.d.ts.map +1 -1
  41. package/dist/SchemaGetter.js +52 -33
  42. package/dist/SchemaGetter.js.map +1 -1
  43. package/dist/Sink.d.ts.map +1 -1
  44. package/dist/Sink.js +38 -17
  45. package/dist/Sink.js.map +1 -1
  46. package/dist/Stdio.d.ts +7 -1
  47. package/dist/Stdio.d.ts.map +1 -1
  48. package/dist/Stdio.js +1 -0
  49. package/dist/Stdio.js.map +1 -1
  50. package/dist/Stream.d.ts +22 -11
  51. package/dist/Stream.d.ts.map +1 -1
  52. package/dist/Stream.js +21 -6
  53. package/dist/Stream.js.map +1 -1
  54. package/dist/Utils.js +8 -3
  55. package/dist/Utils.js.map +1 -1
  56. package/dist/ai/AnthropicStructuredOutput.js +0 -2
  57. package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
  58. package/dist/ai/Chat.d.ts +2 -2
  59. package/dist/ai/Chat.d.ts.map +1 -1
  60. package/dist/ai/LanguageModel.d.ts +38 -7
  61. package/dist/ai/LanguageModel.d.ts.map +1 -1
  62. package/dist/ai/LanguageModel.js +25 -7
  63. package/dist/ai/LanguageModel.js.map +1 -1
  64. package/dist/ai/McpServer.d.ts +61 -0
  65. package/dist/ai/McpServer.d.ts.map +1 -1
  66. package/dist/ai/McpServer.js +196 -44
  67. package/dist/ai/McpServer.js.map +1 -1
  68. package/dist/ai/Prompt.d.ts.map +1 -1
  69. package/dist/ai/Prompt.js +28 -5
  70. package/dist/ai/Prompt.js.map +1 -1
  71. package/dist/ai/Response.d.ts +4 -3
  72. package/dist/ai/Response.d.ts.map +1 -1
  73. package/dist/ai/Response.js.map +1 -1
  74. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
  75. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  76. package/dist/ai/internal/mcpProtocol.js.map +1 -1
  77. package/dist/ai/internal/mcpRuntime.js +1 -0
  78. package/dist/ai/internal/mcpRuntime.js.map +1 -1
  79. package/dist/cli/CliOutput.d.ts +27 -0
  80. package/dist/cli/CliOutput.d.ts.map +1 -1
  81. package/dist/cli/CliOutput.js +33 -10
  82. package/dist/cli/CliOutput.js.map +1 -1
  83. package/dist/cli/Command.d.ts.map +1 -1
  84. package/dist/cli/Command.js +6 -3
  85. package/dist/cli/Command.js.map +1 -1
  86. package/dist/cli/Completions.d.ts +3 -3
  87. package/dist/cli/Completions.d.ts.map +1 -1
  88. package/dist/cli/Completions.js +8 -5
  89. package/dist/cli/Completions.js.map +1 -1
  90. package/dist/cli/GlobalFlag.d.ts +5 -4
  91. package/dist/cli/GlobalFlag.d.ts.map +1 -1
  92. package/dist/cli/GlobalFlag.js +3 -3
  93. package/dist/cli/GlobalFlag.js.map +1 -1
  94. package/dist/cli/Prompt.d.ts +17 -1
  95. package/dist/cli/Prompt.d.ts.map +1 -1
  96. package/dist/cli/Prompt.js +119 -114
  97. package/dist/cli/Prompt.js.map +1 -1
  98. package/dist/cli/internal/ansi.d.ts +0 -1
  99. package/dist/cli/internal/ansi.d.ts.map +1 -1
  100. package/dist/cli/internal/ansi.js +20 -3
  101. package/dist/cli/internal/ansi.js.map +1 -1
  102. package/dist/cli/internal/completions/powershell.d.ts +2 -0
  103. package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
  104. package/dist/cli/internal/completions/powershell.js +273 -0
  105. package/dist/cli/internal/completions/powershell.js.map +1 -0
  106. package/dist/cli/internal/completions/zsh.js +3 -2
  107. package/dist/cli/internal/completions/zsh.js.map +1 -1
  108. package/dist/cli/internal/wizard.d.ts +3 -3
  109. package/dist/cli/internal/wizard.d.ts.map +1 -1
  110. package/dist/cli/internal/wizard.js +21 -18
  111. package/dist/cli/internal/wizard.js.map +1 -1
  112. package/dist/cluster/ClusterSchema.d.ts +28 -0
  113. package/dist/cluster/ClusterSchema.d.ts.map +1 -1
  114. package/dist/cluster/ClusterSchema.js +30 -0
  115. package/dist/cluster/ClusterSchema.js.map +1 -1
  116. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  117. package/dist/cluster/ClusterWorkflowEngine.js +56 -26
  118. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  119. package/dist/cluster/MessageStorage.d.ts +18 -3
  120. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  121. package/dist/cluster/MessageStorage.js +31 -11
  122. package/dist/cluster/MessageStorage.js.map +1 -1
  123. package/dist/cluster/Runners.d.ts.map +1 -1
  124. package/dist/cluster/Runners.js +8 -2
  125. package/dist/cluster/Runners.js.map +1 -1
  126. package/dist/cluster/Sharding.d.ts.map +1 -1
  127. package/dist/cluster/Sharding.js +187 -66
  128. package/dist/cluster/Sharding.js.map +1 -1
  129. package/dist/cluster/SqlMessageStorage.d.ts +59 -19
  130. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  131. package/dist/cluster/SqlMessageStorage.js +84 -55
  132. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  133. package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
  134. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  135. package/dist/cluster/SqlRunnerStorage.js +191 -113
  136. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  137. package/dist/cluster/internal/entityManager.js +104 -37
  138. package/dist/cluster/internal/entityManager.js.map +1 -1
  139. package/dist/encoding/Sse.d.ts +6 -0
  140. package/dist/encoding/Sse.d.ts.map +1 -1
  141. package/dist/encoding/Sse.js +62 -70
  142. package/dist/encoding/Sse.js.map +1 -1
  143. package/dist/graphql/GraphQL.d.ts +349 -0
  144. package/dist/graphql/GraphQL.d.ts.map +1 -0
  145. package/dist/graphql/GraphQL.js +207 -0
  146. package/dist/graphql/GraphQL.js.map +1 -0
  147. package/dist/graphql/GraphQLClient.d.ts +562 -0
  148. package/dist/graphql/GraphQLClient.d.ts.map +1 -0
  149. package/dist/graphql/GraphQLClient.js +498 -0
  150. package/dist/graphql/GraphQLClient.js.map +1 -0
  151. package/dist/graphql/GraphQLClientError.d.ts +445 -0
  152. package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
  153. package/dist/graphql/GraphQLClientError.js +479 -0
  154. package/dist/graphql/GraphQLClientError.js.map +1 -0
  155. package/dist/graphql/GraphQLGroup.d.ts +118 -0
  156. package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
  157. package/dist/graphql/GraphQLGroup.js +109 -0
  158. package/dist/graphql/GraphQLGroup.js.map +1 -0
  159. package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
  160. package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
  161. package/dist/graphql/GraphQLMiddleware.js +130 -0
  162. package/dist/graphql/GraphQLMiddleware.js.map +1 -0
  163. package/dist/graphql/GraphQLProtocol.d.ts +731 -0
  164. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
  165. package/dist/graphql/GraphQLProtocol.js +719 -0
  166. package/dist/graphql/GraphQLProtocol.js.map +1 -0
  167. package/dist/graphql/GraphQLResolver.d.ts +453 -0
  168. package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
  169. package/dist/graphql/GraphQLResolver.js +552 -0
  170. package/dist/graphql/GraphQLResolver.js.map +1 -0
  171. package/dist/graphql/index.d.ts +40 -0
  172. package/dist/graphql/index.d.ts.map +1 -0
  173. package/dist/graphql/index.js +41 -0
  174. package/dist/graphql/index.js.map +1 -0
  175. package/dist/http/FindMyWay/internal/router.js +3 -6
  176. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  177. package/dist/http/HttpClient.d.ts +21 -0
  178. package/dist/http/HttpClient.d.ts.map +1 -1
  179. package/dist/http/HttpClient.js +63 -12
  180. package/dist/http/HttpClient.js.map +1 -1
  181. package/dist/http/HttpEffect.d.ts.map +1 -1
  182. package/dist/http/HttpEffect.js +14 -7
  183. package/dist/http/HttpEffect.js.map +1 -1
  184. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  185. package/dist/http/HttpMiddleware.js +31 -32
  186. package/dist/http/HttpMiddleware.js.map +1 -1
  187. package/dist/http/HttpRouter.d.ts +35 -0
  188. package/dist/http/HttpRouter.d.ts.map +1 -1
  189. package/dist/http/HttpRouter.js +35 -0
  190. package/dist/http/HttpRouter.js.map +1 -1
  191. package/dist/http/HttpServerResponse.d.ts +8 -6
  192. package/dist/http/HttpServerResponse.d.ts.map +1 -1
  193. package/dist/http/HttpServerResponse.js +27 -16
  194. package/dist/http/HttpServerResponse.js.map +1 -1
  195. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  196. package/dist/http/HttpTraceContext.js +4 -2
  197. package/dist/http/HttpTraceContext.js.map +1 -1
  198. package/dist/http/Multipart.d.ts.map +1 -1
  199. package/dist/http/Multipart.js +15 -1
  200. package/dist/http/Multipart.js.map +1 -1
  201. package/dist/http-api/HttpApiScalar.d.ts +5 -0
  202. package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
  203. package/dist/http-api/HttpApiScalar.js.map +1 -1
  204. package/dist/internal/dateTime.js +40 -35
  205. package/dist/internal/dateTime.js.map +1 -1
  206. package/dist/internal/effect.js +19 -9
  207. package/dist/internal/effect.js.map +1 -1
  208. package/dist/internal/graphql.d.ts +2 -0
  209. package/dist/internal/graphql.d.ts.map +1 -0
  210. package/dist/internal/graphql.js +195 -0
  211. package/dist/internal/graphql.js.map +1 -0
  212. package/dist/internal/graphqlHeaders.d.ts +4 -0
  213. package/dist/internal/graphqlHeaders.d.ts.map +1 -0
  214. package/dist/internal/graphqlHeaders.js +11 -0
  215. package/dist/internal/graphqlHeaders.js.map +1 -0
  216. package/dist/internal/request.js +20 -1
  217. package/dist/internal/request.js.map +1 -1
  218. package/dist/internal/schema/toCodeDocument.js +4 -1
  219. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  220. package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
  221. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  222. package/dist/internal/version.js +1 -1
  223. package/dist/net/AddressResolver.d.ts +145 -0
  224. package/dist/net/AddressResolver.d.ts.map +1 -0
  225. package/dist/net/AddressResolver.js +114 -0
  226. package/dist/net/AddressResolver.js.map +1 -0
  227. package/dist/net/Dns.d.ts +596 -0
  228. package/dist/net/Dns.d.ts.map +1 -0
  229. package/dist/net/Dns.js +549 -0
  230. package/dist/net/Dns.js.map +1 -0
  231. package/dist/net/Host.d.ts +369 -0
  232. package/dist/net/Host.d.ts.map +1 -0
  233. package/dist/net/Host.js +397 -0
  234. package/dist/net/Host.js.map +1 -0
  235. package/dist/net/IpInterface.d.ts +76 -3
  236. package/dist/net/IpInterface.d.ts.map +1 -1
  237. package/dist/net/IpInterface.js +53 -4
  238. package/dist/net/IpInterface.js.map +1 -1
  239. package/dist/net/IpNetwork.d.ts +82 -8
  240. package/dist/net/IpNetwork.d.ts.map +1 -1
  241. package/dist/net/IpNetwork.js +60 -4
  242. package/dist/net/IpNetwork.js.map +1 -1
  243. package/dist/net/NetAddress.d.ts +396 -35
  244. package/dist/net/NetAddress.d.ts.map +1 -1
  245. package/dist/net/NetAddress.js +358 -34
  246. package/dist/net/NetAddress.js.map +1 -1
  247. package/dist/net/index.d.ts +15 -0
  248. package/dist/net/index.d.ts.map +1 -1
  249. package/dist/net/index.js +15 -0
  250. package/dist/net/index.js.map +1 -1
  251. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  252. package/dist/observability/OtlpMetrics.js +15 -21
  253. package/dist/observability/OtlpMetrics.js.map +1 -1
  254. package/dist/observability/PrometheusMetrics.js +1 -0
  255. package/dist/observability/PrometheusMetrics.js.map +1 -1
  256. package/dist/persistence/PersistedQueue.d.ts +32 -3
  257. package/dist/persistence/PersistedQueue.d.ts.map +1 -1
  258. package/dist/persistence/PersistedQueue.js +75 -16
  259. package/dist/persistence/PersistedQueue.js.map +1 -1
  260. package/dist/rpc/RpcClient.d.ts +22 -6
  261. package/dist/rpc/RpcClient.d.ts.map +1 -1
  262. package/dist/rpc/RpcClient.js +12 -5
  263. package/dist/rpc/RpcClient.js.map +1 -1
  264. package/dist/rpc/RpcServer.d.ts +6 -0
  265. package/dist/rpc/RpcServer.d.ts.map +1 -1
  266. package/dist/rpc/RpcServer.js +78 -29
  267. package/dist/rpc/RpcServer.js.map +1 -1
  268. package/dist/socket/DatagramSocket.d.ts +456 -0
  269. package/dist/socket/DatagramSocket.d.ts.map +1 -0
  270. package/dist/socket/DatagramSocket.js +535 -0
  271. package/dist/socket/DatagramSocket.js.map +1 -0
  272. package/dist/socket/Socket.d.ts +20 -1
  273. package/dist/socket/Socket.d.ts.map +1 -1
  274. package/dist/socket/Socket.js +9 -1
  275. package/dist/socket/Socket.js.map +1 -1
  276. package/dist/socket/index.d.ts +5 -0
  277. package/dist/socket/index.d.ts.map +1 -1
  278. package/dist/socket/index.js +5 -0
  279. package/dist/socket/index.js.map +1 -1
  280. package/dist/sql/Migrator.d.ts.map +1 -1
  281. package/dist/sql/Migrator.js +7 -2
  282. package/dist/sql/Migrator.js.map +1 -1
  283. package/dist/sql/SqlClient.d.ts +7 -1
  284. package/dist/sql/SqlClient.d.ts.map +1 -1
  285. package/dist/sql/SqlClient.js +31 -6
  286. package/dist/sql/SqlClient.js.map +1 -1
  287. package/dist/testing/TestCrypto.d.ts +73 -0
  288. package/dist/testing/TestCrypto.d.ts.map +1 -0
  289. package/dist/testing/TestCrypto.js +87 -0
  290. package/dist/testing/TestCrypto.js.map +1 -0
  291. package/dist/testing/index.d.ts +5 -0
  292. package/dist/testing/index.d.ts.map +1 -1
  293. package/dist/testing/index.js +5 -0
  294. package/dist/testing/index.js.map +1 -1
  295. package/dist/workflow/DurableDeferred.d.ts +54 -0
  296. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  297. package/dist/workflow/DurableDeferred.js +108 -50
  298. package/dist/workflow/DurableDeferred.js.map +1 -1
  299. package/dist/workflow/WorkflowEngine.d.ts +24 -2
  300. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  301. package/dist/workflow/WorkflowEngine.js +13 -3
  302. package/dist/workflow/WorkflowEngine.js.map +1 -1
  303. package/package.json +6 -3
  304. package/src/Context.ts +12 -3
  305. package/src/Crypto.ts +43 -9
  306. package/src/DateTime.ts +9 -6
  307. package/src/Duration.ts +36 -16
  308. package/src/FileSystem.ts +48 -2
  309. package/src/RequestResolver.ts +4 -10
  310. package/src/Scheduler.ts +9 -0
  311. package/src/Schema.ts +677 -5
  312. package/src/SchemaGetter.ts +47 -35
  313. package/src/Sink.ts +45 -18
  314. package/src/Stdio.ts +9 -2
  315. package/src/Stream.ts +44 -15
  316. package/src/Utils.ts +8 -5
  317. package/src/ai/AnthropicStructuredOutput.ts +2 -2
  318. package/src/ai/Chat.ts +2 -2
  319. package/src/ai/LanguageModel.ts +96 -24
  320. package/src/ai/McpServer.ts +288 -94
  321. package/src/ai/Prompt.ts +24 -10
  322. package/src/ai/Response.ts +6 -3
  323. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
  324. package/src/ai/internal/mcpProtocol.ts +3 -0
  325. package/src/ai/internal/mcpRuntime.ts +2 -0
  326. package/src/cli/CliOutput.ts +33 -16
  327. package/src/cli/Command.ts +4 -3
  328. package/src/cli/Completions.ts +9 -6
  329. package/src/cli/GlobalFlag.ts +19 -11
  330. package/src/cli/Prompt.ts +159 -149
  331. package/src/cli/internal/ansi.ts +26 -3
  332. package/src/cli/internal/completions/powershell.ts +322 -0
  333. package/src/cli/internal/completions/zsh.ts +4 -2
  334. package/src/cli/internal/wizard.ts +34 -26
  335. package/src/cluster/ClusterSchema.ts +32 -0
  336. package/src/cluster/ClusterWorkflowEngine.ts +64 -36
  337. package/src/cluster/MessageStorage.ts +45 -14
  338. package/src/cluster/Runners.ts +8 -2
  339. package/src/cluster/Sharding.ts +219 -58
  340. package/src/cluster/SqlMessageStorage.ts +118 -84
  341. package/src/cluster/SqlRunnerStorage.ts +224 -128
  342. package/src/cluster/internal/entityManager.ts +151 -55
  343. package/src/encoding/Sse.ts +69 -79
  344. package/src/graphql/GraphQL.ts +494 -0
  345. package/src/graphql/GraphQLClient.ts +912 -0
  346. package/src/graphql/GraphQLClientError.ts +542 -0
  347. package/src/graphql/GraphQLGroup.ts +167 -0
  348. package/src/graphql/GraphQLMiddleware.ts +253 -0
  349. package/src/graphql/GraphQLProtocol.ts +1286 -0
  350. package/src/graphql/GraphQLResolver.ts +1010 -0
  351. package/src/graphql/index.ts +48 -0
  352. package/src/http/FindMyWay/internal/router.ts +4 -7
  353. package/src/http/HttpClient.ts +110 -12
  354. package/src/http/HttpEffect.ts +11 -5
  355. package/src/http/HttpMiddleware.ts +33 -34
  356. package/src/http/HttpRouter.ts +35 -0
  357. package/src/http/HttpServerResponse.ts +28 -16
  358. package/src/http/HttpTraceContext.ts +8 -2
  359. package/src/http/Multipart.ts +17 -1
  360. package/src/http-api/HttpApiScalar.ts +5 -0
  361. package/src/internal/dateTime.ts +13 -10
  362. package/src/internal/effect.ts +22 -15
  363. package/src/internal/graphql.ts +263 -0
  364. package/src/internal/graphqlHeaders.ts +11 -0
  365. package/src/internal/request.ts +31 -3
  366. package/src/internal/schema/toCodeDocument.ts +7 -1
  367. package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
  368. package/src/internal/version.ts +1 -1
  369. package/src/net/AddressResolver.ts +222 -0
  370. package/src/net/Dns.ts +1035 -0
  371. package/src/net/Host.ts +544 -0
  372. package/src/net/IpInterface.ts +117 -7
  373. package/src/net/IpNetwork.ts +116 -17
  374. package/src/net/NetAddress.ts +620 -76
  375. package/src/net/index.ts +18 -0
  376. package/src/observability/OtlpMetrics.ts +12 -23
  377. package/src/observability/PrometheusMetrics.ts +1 -0
  378. package/src/persistence/PersistedQueue.ts +146 -53
  379. package/src/rpc/RpcClient.ts +41 -19
  380. package/src/rpc/RpcServer.ts +115 -40
  381. package/src/socket/DatagramSocket.ts +919 -0
  382. package/src/socket/Socket.ts +28 -2
  383. package/src/socket/index.ts +6 -0
  384. package/src/sql/Migrator.ts +21 -5
  385. package/src/sql/SqlClient.ts +39 -15
  386. package/src/testing/TestCrypto.ts +94 -0
  387. package/src/testing/index.ts +6 -0
  388. package/src/workflow/DurableDeferred.ts +160 -47
  389. package/src/workflow/WorkflowEngine.ts +50 -5
package/src/net/index.ts CHANGED
@@ -5,6 +5,24 @@
5
5
 
6
6
  // @barrel: Auto-generated exports. Do not edit manually.
7
7
 
8
+ /**
9
+ * @stability experimental
10
+ * @since 4.0.0
11
+ */
12
+ export * as AddressResolver from "./AddressResolver.ts"
13
+
14
+ /**
15
+ * @stability experimental
16
+ * @since 4.0.0
17
+ */
18
+ export * as Dns from "./Dns.ts"
19
+
20
+ /**
21
+ * @stability experimental
22
+ * @since 4.0.0
23
+ */
24
+ export * as Host from "./Host.ts"
25
+
8
26
  /**
9
27
  * @stability unstable
10
28
  * @since 4.0.0
@@ -12,7 +12,6 @@
12
12
  * @stability unstable
13
13
  * @since 4.0.0
14
14
  */
15
- import * as Arr from "../Array.ts"
16
15
  import { Clock } from "../Clock.ts"
17
16
  import * as Config from "../Config.ts"
18
17
  import * as Duration from "../Duration.ts"
@@ -228,27 +227,24 @@ export const make: (options: {
228
227
  break
229
228
  }
230
229
  case "Histogram": {
231
- const size = state.state.buckets.length
232
230
  const currentBuckets = {
233
- boundaries: Arr.allocate(size - 1) as Array<number>,
234
- counts: Arr.allocate(size) as Array<number>
231
+ boundaries: [] as Array<number>,
232
+ counts: [] as Array<number>
235
233
  }
236
- let idx = 0
237
234
  let prev = 0
238
235
  for (const [boundary, value] of state.state.buckets) {
239
- if (idx < size - 1) {
240
- currentBuckets.boundaries[idx] = boundary
241
- }
242
- currentBuckets.counts[idx] = value - prev
236
+ if (boundary === Number.POSITIVE_INFINITY) break
237
+ currentBuckets.boundaries.push(boundary)
238
+ currentBuckets.counts.push(value - prev)
243
239
  prev = value
244
- idx++
245
240
  }
241
+ // Derive overflow from the total count, whether or not boundaries include Infinity.
242
+ currentBuckets.counts.push(state.state.count - prev)
246
243
 
247
244
  let reportCount = state.state.count
248
245
  let reportSum = state.state.sum
249
246
  let reportBucketCounts = currentBuckets.counts
250
- const reportMin = state.state.min
251
- const reportMax = state.state.max
247
+ let reportExtrema = true
252
248
 
253
249
  if (isDelta) {
254
250
  const previousState = previousHistogramState.get(metricKey)
@@ -258,17 +254,13 @@ export const make: (options: {
258
254
  reportBucketCounts = currentBuckets.counts.map((c, i) =>
259
255
  Math.max(0, c - (previousState.bucketCounts[i] ?? 0))
260
256
  )
261
- // For delta, min/max represent the interval's min/max
262
- // We can't compute these from cumulative state, so we use current values
263
- // Note: This is a limitation - true delta min/max would require tracking
264
- // observations within each interval
257
+ // Cumulative extrema cannot describe a later delta interval.
258
+ reportExtrema = false
265
259
  }
266
260
  nextHistogramState.set(metricKey, {
267
261
  count: state.state.count,
268
262
  sum: state.state.sum,
269
- bucketCounts: currentBuckets.counts.slice(),
270
- min: state.state.min,
271
- max: state.state.max
263
+ bucketCounts: currentBuckets.counts.slice()
272
264
  })
273
265
  }
274
266
 
@@ -277,8 +269,7 @@ export const make: (options: {
277
269
  startTimeUnixNano: intervalStartTime,
278
270
  timeUnixNano: nowTime,
279
271
  count: reportCount,
280
- min: reportMin,
281
- max: reportMax,
272
+ ...(reportExtrema ? { min: state.state.min, max: state.state.max } : undefined),
282
273
  sum: reportSum,
283
274
  bucketCounts: reportBucketCounts,
284
275
  explicitBounds: currentBuckets.boundaries
@@ -519,8 +510,6 @@ interface PreviousHistogramState {
519
510
  readonly count: number
520
511
  readonly sum: number
521
512
  readonly bucketCounts: Array<number>
522
- readonly min: number
523
- readonly max: number
524
513
  }
525
514
 
526
515
  /** Properties of an InstrumentationScope. */
@@ -425,6 +425,7 @@ const formatHistogram = (
425
425
  // Effect buckets are [boundary, cumulativeCount] pairs
426
426
  for (let i = 0; i < state.buckets.length; i++) {
427
427
  const [boundary, cumulativeCount] = state.buckets[i]
428
+ if (boundary === Number.POSITIVE_INFINITY) continue
428
429
  const bucketLabels = formatLabels(metric.attributes, [["le", boundary.toString()]])
429
430
  lines.push(`${name}_bucket${bucketLabels} ${cumulativeCount}`)
430
431
  }
@@ -34,6 +34,7 @@ import * as Scope from "../Scope.ts"
34
34
  import * as Migrator from "../sql/Migrator.ts"
35
35
  import * as SqlClient from "../sql/SqlClient.ts"
36
36
  import type { SqlError } from "../sql/SqlError.ts"
37
+ import type { Fragment } from "../sql/Statement.ts"
37
38
  import * as Redis from "./Redis.ts"
38
39
 
39
40
  /**
@@ -80,11 +81,27 @@ export interface PersistedQueue<in out A, out R = never> {
80
81
  * **Details**
81
82
  *
82
83
  * If an element with the same id already exists in the queue, it will not be
83
- * added again. De-duplication survives completion until the id is removed by
84
+ * added again, and the `delay` of the duplicate offer is ignored.
85
+ * De-duplication survives completion until the id is removed by
84
86
  * `layerCleanup`.
87
+ *
88
+ * `delay` postpones the first delivery. Measured by the store's clock, the
89
+ * element is never delivered before the delay elapses, and is typically
90
+ * delivered within the store's `pollInterval` after. The SQL store uses the
91
+ * database clock and rounds delays up to whole seconds. The Redis store
92
+ * compares the offering process's clock with the polling worker's, so it
93
+ * relies on those clocks agreeing.
94
+ * Retries still use the queue's `retrySchedule`, and the delay does not
95
+ * count an attempt. A zero, negative or omitted delay enqueues the element
96
+ * immediately.
97
+ *
98
+ * An infinite delay does nothing: the id is returned, but nothing is stored
99
+ * and no de-duplication record is written, so the same id can be offered
100
+ * again later.
85
101
  */
86
102
  readonly offer: (value: A, options?: {
87
- readonly id: string | undefined
103
+ readonly id?: string | undefined
104
+ readonly delay?: Duration.Input | undefined
88
105
  }) => Effect.Effect<string, PersistedQueueError | Schema.SchemaError, R>
89
106
 
90
107
  /**
@@ -232,21 +249,27 @@ export const makeFactory = Effect.gen(function*() {
232
249
  return Effect.succeed<PersistedQueue<S["Type"], S["EncodingServices"] | S["DecodingServices"]>>({
233
250
  [TypeId]: TypeId,
234
251
  offer: (value, opts) =>
235
- Effect.flatMap(
236
- encodeUnknown(value),
237
- (element) => {
238
- const id = opts?.id ?? crypto.randomUUID()
239
- return Effect.as(
240
- store.offer({
241
- name: options.name,
242
- id,
243
- element,
244
- isCustomId: opts?.id !== undefined
245
- }),
246
- id
247
- )
252
+ Effect.suspend(() => {
253
+ const id = opts?.id ?? crypto.randomUUID()
254
+ const delay = positiveDelay(opts?.delay)
255
+ if (delay !== undefined && !Duration.isFinite(delay)) {
256
+ return Effect.succeed(id)
248
257
  }
249
- ),
258
+ return Effect.flatMap(
259
+ encodeUnknown(value),
260
+ (element) =>
261
+ Effect.as(
262
+ store.offer({
263
+ name: options.name,
264
+ id,
265
+ element,
266
+ isCustomId: opts?.id !== undefined,
267
+ delay
268
+ }),
269
+ id
270
+ )
271
+ )
272
+ }),
250
273
  take: <XA, XE, XR>(
251
274
  f: (value: S["Type"], metadata: {
252
275
  readonly id: string
@@ -430,6 +453,13 @@ class DeadLetter {
430
453
  const isDeadLetter = (u: unknown): u is DeadLetter =>
431
454
  Predicate.isTagged(u, "~effect/persistence/PersistedQueue/DeadLetter")
432
455
 
456
+ // stores only see a positive delay, so a zero or negative one is dropped here
457
+ const positiveDelay = (input: Duration.Input | undefined): Duration.Duration | undefined => {
458
+ if (input === undefined) return undefined
459
+ const delay = Duration.fromInputUnsafe(input)
460
+ return Duration.isPositive(delay) ? delay : undefined
461
+ }
462
+
433
463
  const deadLetterFromCause = (cause: Cause.Cause<unknown>): DeadLetter | undefined => {
434
464
  for (const reason of cause.reasons) {
435
465
  if (Cause.isFailReason(reason) && isDeadLetter(reason.error)) {
@@ -452,6 +482,9 @@ const deadLetterFromCause = (cause: Cause.Cause<unknown>): DeadLetter | undefine
452
482
  * The store persists offered elements and returns taken elements in a scope so
453
483
  * the finalizer can complete or retry them based on the processing exit.
454
484
  *
485
+ * An offered `delay` is always finite and positive, and the element must not
486
+ * be taken before it elapses. It is `undefined` for immediate elements.
487
+ *
455
488
  * Claiming an element counts an attempt, so the `attempts` returned by `take`
456
489
  * is 1-based. When the take scope closes with a success the element is marked
457
490
  * completed; a failure retries it according to `retryDelay` or marks it failed
@@ -471,6 +504,7 @@ export class PersistedQueueStore extends Context.Service<
471
504
  readonly id: string
472
505
  readonly element: unknown
473
506
  readonly isCustomId: boolean
507
+ readonly delay?: Duration.Duration | undefined
474
508
  }
475
509
  ) => Effect.Effect<void, PersistedQueueError>
476
510
 
@@ -509,6 +543,9 @@ export class PersistedQueueStore extends Context.Service<
509
543
  * queue's retry schedule until the configured maximum attempts is reached,
510
544
  * after which the element is marked as failed.
511
545
  *
546
+ * Delayed elements are scanned on every take loop, so the store is meant for
547
+ * development and tests rather than large delayed backlogs.
548
+ *
512
549
  * @stability unstable
513
550
  * @category layers
514
551
  * @since 4.0.0
@@ -558,7 +595,7 @@ export const layerStoreMemory: Layer.Layer<
558
595
  element: options.element,
559
596
  attempts: 0,
560
597
  state: "pending",
561
- visibleAt: now,
598
+ visibleAt: options.delay === undefined ? now : now + Duration.toMillis(options.delay),
562
599
  stateChangedAt: now
563
600
  }
564
601
  queue.entries.set(options.id, entry)
@@ -655,7 +692,9 @@ export const layerStoreMemory: Layer.Layer<
655
692
  * The store uses Redis lists, hashes, and sorted sets with worker locks,
656
693
  * periodically refreshes locks while items are being processed, delays retried
657
694
  * items with the queue's retry schedule, and moves exhausted items to a failed
658
- * queue.
695
+ * queue. Delayed offers share the retry sorted set: they are scored with the
696
+ * offering process's clock and released by the polling worker's clock, so
697
+ * clock skew between hosts shifts their delivery.
659
698
  *
660
699
  * @stability unstable
661
700
  * @category constructors
@@ -716,6 +755,7 @@ export const makeStoreRedis = Effect.fnUntraced(function*(
716
755
  const retry = redis.eval(retryRedis)
717
756
  const resetQueue = redis.eval(resetQueueRedis)
718
757
  const offer = redis.eval(offerRedis)
758
+ const offerDelayed = redis.eval(offerDelayedRedis)
719
759
  const take = redis.eval(takeRedis)
720
760
  const expireAll = redis.eval(expireAllRedis)
721
761
  const trimFailed = redis.eval(trimFailedRedis)
@@ -856,24 +896,33 @@ export const makeStoreRedis = Effect.fnUntraced(function*(
856
896
  })
857
897
 
858
898
  return PersistedQueueStore.of({
859
- offer: ({ element, id, isCustomId, name }) => {
899
+ offer: ({ delay, element, id, isCustomId, name }) => {
860
900
  const keys = keysFor(name)
861
901
  const payload = JSON.stringify({ id, element })
862
- return (isCustomId
863
- ? offer(keys.queue, keys.ids, id, payload)
864
- : redis.send("RPUSH", keys.queue, payload)).pipe(
865
- Effect.mapError(({ cause }) =>
866
- new PersistedQueueError({
867
- message: "Failed to offer element to persisted queue",
868
- cause
869
- })
870
- ),
871
- Effect.tap(() =>
872
- Effect.sync(() => {
873
- queueStates.peek(name)?.nudge.openUnsafe()
874
- })
875
- )
902
+ const send = Effect.suspend(() => {
903
+ if (delay === undefined) {
904
+ return isCustomId
905
+ ? offer(keys.queue, keys.ids, id, payload)
906
+ : redis.send("RPUSH", keys.queue, payload)
907
+ }
908
+ const visibleAt = clock.currentTimeMillisUnsafe() + Duration.toMillis(delay)
909
+ return isCustomId
910
+ ? offerDelayed(keys.delayed, keys.ids, id, payload, visibleAt)
911
+ : redis.send("ZADD", keys.delayed, visibleAt.toString(), payload)
912
+ })
913
+ return send.pipe(
914
+ Effect.mapError(({ cause }) =>
915
+ new PersistedQueueError({
916
+ message: "Failed to offer element to persisted queue",
917
+ cause
918
+ })
919
+ ),
920
+ Effect.tap(() =>
921
+ Effect.sync(() => {
922
+ queueStates.peek(name)?.nudge.openUnsafe()
923
+ })
876
924
  )
925
+ )
877
926
  },
878
927
  take: (options) =>
879
928
  Effect.uninterruptibleMask((restore) => {
@@ -1009,6 +1058,25 @@ end
1009
1058
  }
1010
1059
  )
1011
1060
 
1061
+ const offerDelayedRedis = Redis.script(
1062
+ (...args: [keyDelayed: string, keyIds: string, id: string, payload: string, visibleAt: number]) => args,
1063
+ {
1064
+ lua: `
1065
+ local key_delayed = KEYS[1]
1066
+ local key_ids = KEYS[2]
1067
+ local id = ARGV[1]
1068
+ local payload = ARGV[2]
1069
+ local visible_at = ARGV[3]
1070
+
1071
+ local result = redis.call("ZADD", key_ids, "NX", "+inf", id)
1072
+ if result == 1 then
1073
+ redis.call("ZADD", key_delayed, visible_at, payload)
1074
+ end
1075
+ `,
1076
+ numberOfKeys: 2
1077
+ }
1078
+ )
1079
+
1012
1080
  const resetQueueRedis = Redis.script(
1013
1081
  (
1014
1082
  ...args: [
@@ -1296,6 +1364,9 @@ export const layerStoreRedis: (
1296
1364
  * per-worker locks, refreshes active locks while scoped takes are running, and
1297
1365
  * retries or completes rows according to the processing exit.
1298
1366
  *
1367
+ * Offer and retry delays are measured with the database clock and rounded up
1368
+ * to whole seconds.
1369
+ *
1299
1370
  * @stability unstable
1300
1371
  * @category constructors
1301
1372
  * @since 4.0.0
@@ -1360,34 +1431,46 @@ export const makeStoreSql: (
1360
1431
  }
1361
1432
  const secondsAgo = (seconds: number) => secondsOffset(-Math.max(Math.ceil(seconds), 0))
1362
1433
  const secondsFromNow = (seconds: number) => secondsOffset(Math.max(Math.ceil(seconds), 0))
1434
+ // Deadline for a delayed offer, which must never land before the delay
1435
+ // elapses. pg reads clock_timestamp() since NOW() is fixed at transaction
1436
+ // start. mysql and sqlite clocks drop the fraction of the current second, so
1437
+ // they get a whole extra second to cover it.
1438
+ const delayedVisibleAt = (delay: Duration.Duration) => {
1439
+ const seconds = Math.ceil(Duration.toSeconds(delay))
1440
+ return sql.onDialectOrElse({
1441
+ pg: () => sql`clock_timestamp() + INTERVAL '${sql.literal(seconds.toString())} seconds'`,
1442
+ mssql: () => secondsOffset(seconds),
1443
+ orElse: () => secondsOffset(seconds + 1)
1444
+ })
1445
+ }
1363
1446
  const expiresAt = secondsAgo(Duration.toSeconds(lockExpiration))
1364
1447
 
1365
1448
  const offer = sql.onDialectOrElse({
1366
- pg: () => (id: string, name: string, element: string) =>
1449
+ pg: () => (id: string, name: string, element: string, visibleAt: Fragment) =>
1367
1450
  sql`
1368
1451
  INSERT INTO ${tableNameSql} (id, queue_name, element, state, attempts, visible_at, created_at, updated_at)
1369
- VALUES (${id}, ${name}, ${element}, 'pending', 0, ${sqlNow}, ${sqlNow}, ${sqlNow})
1452
+ VALUES (${id}, ${name}, ${element}, 'pending', 0, ${visibleAt}, ${sqlNow}, ${sqlNow})
1370
1453
  ON CONFLICT (id, queue_name) DO NOTHING
1371
1454
  `,
1372
- mysql: () => (id: string, name: string, element: string) =>
1455
+ mysql: () => (id: string, name: string, element: string, visibleAt: Fragment) =>
1373
1456
  sql`
1374
1457
  INSERT IGNORE INTO ${tableNameSql} (id, queue_name, element, state, attempts, visible_at, created_at, updated_at)
1375
- VALUES (${id}, ${name}, ${element}, 'pending', 0, ${sqlNow}, ${sqlNow}, ${sqlNow})
1458
+ VALUES (${id}, ${name}, ${element}, 'pending', 0, ${visibleAt}, ${sqlNow}, ${sqlNow})
1376
1459
  `,
1377
- mssql: () => (id: string, name: string, element: string) =>
1460
+ mssql: () => (id: string, name: string, element: string, visibleAt: Fragment) =>
1378
1461
  sql`
1379
1462
  MERGE ${tableNameSql} WITH (HOLDLOCK) AS target
1380
1463
  USING (SELECT ${id} AS id, ${name} AS queue_name) AS source
1381
1464
  ON target.id = source.id AND target.queue_name = source.queue_name
1382
1465
  WHEN NOT MATCHED THEN
1383
1466
  INSERT (id, queue_name, element, state, attempts, visible_at, created_at, updated_at)
1384
- VALUES (source.id, source.queue_name, ${element}, 'pending', 0, ${sqlNow}, ${sqlNow}, ${sqlNow});
1467
+ VALUES (source.id, source.queue_name, ${element}, 'pending', 0, ${visibleAt}, ${sqlNow}, ${sqlNow});
1385
1468
  `,
1386
1469
  // sqlite
1387
- orElse: () => (id: string, name: string, element: string) =>
1470
+ orElse: () => (id: string, name: string, element: string, visibleAt: Fragment) =>
1388
1471
  sql`
1389
1472
  INSERT OR IGNORE INTO ${tableNameSql} (id, queue_name, element, state, attempts, visible_at, created_at, updated_at)
1390
- VALUES (${id}, ${name}, ${element}, 'pending', 0, ${sqlNow}, ${sqlNow}, ${sqlNow})
1473
+ VALUES (${id}, ${name}, ${element}, 'pending', 0, ${visibleAt}, ${sqlNow}, ${sqlNow})
1391
1474
  `
1392
1475
  })
1393
1476
 
@@ -1696,20 +1779,30 @@ export const makeStoreSql: (
1696
1779
  )
1697
1780
 
1698
1781
  return PersistedQueueStore.of({
1699
- offer: ({ element, id, name }) =>
1700
- Effect.catchCause(Effect.suspend(() => offer(id, name, JSON.stringify(element))), (cause) =>
1701
- Effect.fail(
1702
- new PersistedQueueError({
1703
- message: "Failed to offer element to persisted queue",
1704
- cause
1705
- })
1706
- )).pipe(
1707
- Effect.tap(() =>
1708
- Effect.sync(() => {
1709
- queueStates.peek(name)?.nudge.openUnsafe()
1710
- })
1782
+ offer: ({ delay, element, id, name }) =>
1783
+ Effect.catchCause(
1784
+ Effect.suspend(() =>
1785
+ offer(
1786
+ id,
1787
+ name,
1788
+ JSON.stringify(element),
1789
+ delay === undefined ? sqlNow : delayedVisibleAt(delay)
1711
1790
  )
1712
1791
  ),
1792
+ (cause) =>
1793
+ Effect.fail(
1794
+ new PersistedQueueError({
1795
+ message: "Failed to offer element to persisted queue",
1796
+ cause
1797
+ })
1798
+ )
1799
+ ).pipe(
1800
+ Effect.tap(() =>
1801
+ Effect.sync(() => {
1802
+ queueStates.peek(name)?.nudge.openUnsafe()
1803
+ })
1804
+ )
1805
+ ),
1713
1806
  take: (options) => {
1714
1807
  queueStates.get(options.name).maxAttempts = options.maxAttempts
1715
1808
  const loop: Effect.Effect<
@@ -1066,11 +1066,19 @@ export const makeProtocolSocket = (options?: {
1066
1066
  readonly retryPolicy?: Schedule.Schedule<any, Socket.SocketError> | undefined
1067
1067
  /**
1068
1068
  * Runs for each retried `SocketOpenError` when `retryTransientErrors` is enabled.
1069
- * A ping timeout fails in-flight calls and is not reported through this hook.
1070
- * The returned `Effect<void>` cannot fail with a typed error or require
1071
- * services; defects are logged and ignored so retries can continue.
1069
+ * A ping timeout fails in-flight calls and is reported through `onPingTimeout`
1070
+ * instead. The returned `Effect<void>` cannot fail with a typed error or
1071
+ * require services; defects are logged and ignored so retries can continue.
1072
1072
  */
1073
1073
  readonly onTransientError?: ((error: RpcClientError) => Effect.Effect<void>) | undefined
1074
+ /**
1075
+ * Runs when an open connection is dropped because no server frame arrived
1076
+ * within `pingTimeout`, before `ConnectionHooks.onDisconnect` and before
1077
+ * in-flight calls fail with a `SocketReadError`. Defects are logged and ignored.
1078
+ *
1079
+ * @since 4.0.3
1080
+ */
1081
+ readonly onPingTimeout?: Effect.Effect<void> | undefined
1074
1082
  }): Effect.Effect<
1075
1083
  Protocol["Service"],
1076
1084
  never,
@@ -1086,10 +1094,12 @@ export const makeProtocolSocket = (options?: {
1086
1094
 
1087
1095
  let parser = serialization.makeUnsafe()
1088
1096
 
1089
- // `parser` is replaced on every connect, and a stateful serialization
1090
- // encodes against the connection it is writing to, so the ping is encoded
1091
- // when it is sent rather than once up front.
1097
+ // Encode each ping with the current connection's parser.
1092
1098
  const pinger = yield* makePinger(Effect.suspend(() => writer.write(parser.encode(constPing)!)), options)
1099
+ const onPingTimeout = Effect.ignoreCause(options?.onPingTimeout ?? Effect.void, {
1100
+ log: true,
1101
+ message: "RpcClient onPingTimeout hook failed"
1102
+ })
1093
1103
  let currentError: RpcClientError | undefined
1094
1104
 
1095
1105
  const broadcast = (response: FromServerEncoded) =>
@@ -1143,9 +1153,11 @@ export const makeProtocolSocket = (options?: {
1143
1153
  yield* Effect.suspend(() => {
1144
1154
  parser = serialization.makeUnsafe()
1145
1155
  pinger.reset()
1156
+ let connected = false
1146
1157
  return Effect.gen(function*() {
1147
1158
  const { pull } = yield* socket.reader
1148
1159
  currentError = undefined
1160
+ connected = true
1149
1161
  if (Option.isSome(hooks)) {
1150
1162
  yield* hooks.value.onConnect
1151
1163
  }
@@ -1157,17 +1169,19 @@ export const makeProtocolSocket = (options?: {
1157
1169
  }
1158
1170
  }).pipe(
1159
1171
  Effect.scoped,
1160
- Effect.raceFirst(Effect.flatMap(
1161
- pinger.timeout,
1162
- () =>
1163
- Effect.fail(
1164
- new Socket.SocketError({
1165
- reason: new Socket.SocketReadError({
1166
- cause: new Error("ping timeout")
1167
- })
1172
+ // The read loop never succeeds, so the race only succeeds on a ping
1173
+ // timeout, after the socket has been cleaned up.
1174
+ Effect.raceFirst(pinger.timeout),
1175
+ Effect.andThen(() => connected ? onPingTimeout : Effect.void),
1176
+ Effect.andThen(() =>
1177
+ Effect.fail(
1178
+ new Socket.SocketError({
1179
+ reason: new Socket.SocketReadError({
1180
+ cause: new Error("ping timeout")
1168
1181
  })
1169
- )
1170
- ))
1182
+ })
1183
+ )
1184
+ )
1171
1185
  )
1172
1186
  }).pipe(
1173
1187
  Option.isSome(hooks) ? Effect.ensuring(hooks.value.onDisconnect) : identity,
@@ -1278,11 +1292,19 @@ export const layerProtocolSocket = (options?: {
1278
1292
  readonly retryPolicy?: Schedule.Schedule<any, Socket.SocketError> | undefined
1279
1293
  /**
1280
1294
  * Runs for each retried `SocketOpenError` when `retryTransientErrors` is enabled.
1281
- * A ping timeout fails in-flight calls and is not reported through this hook.
1282
- * The returned `Effect<void>` cannot fail with a typed error or require
1283
- * services; defects are logged and ignored so retries can continue.
1295
+ * A ping timeout fails in-flight calls and is reported through `onPingTimeout`
1296
+ * instead. The returned `Effect<void>` cannot fail with a typed error or
1297
+ * require services; defects are logged and ignored so retries can continue.
1284
1298
  */
1285
1299
  readonly onTransientError?: ((error: RpcClientError) => Effect.Effect<void>) | undefined
1300
+ /**
1301
+ * Runs when an open connection is dropped because no server frame arrived
1302
+ * within `pingTimeout`, before `ConnectionHooks.onDisconnect` and before
1303
+ * in-flight calls fail with a `SocketReadError`. Defects are logged and ignored.
1304
+ *
1305
+ * @since 4.0.3
1306
+ */
1307
+ readonly onPingTimeout?: Effect.Effect<void> | undefined
1286
1308
  }): Layer.Layer<
1287
1309
  Protocol,
1288
1310
  never,