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
@@ -161,6 +161,12 @@ export interface Writer {
161
161
  * @since 4.0.0
162
162
  */
163
163
  export interface TlsUpgradeOptions {
164
+ /**
165
+ * DNS hostname used for Server Name Indication (SNI) and certificate identity
166
+ * verification during client upgrades. Server upgrades ignore this option.
167
+ * When omitted, the adapter retains its existing hostname behavior.
168
+ */
169
+ readonly servername?: string | undefined
164
170
  readonly key?:
165
171
  | Redacted.Redacted<string | Uint8Array>
166
172
  | ReadonlyArray<Redacted.Redacted<string | Uint8Array>>
@@ -799,13 +805,18 @@ export interface WebSocketLike {
799
805
  }
800
806
 
801
807
  /**
802
- * Common options understood by a WebSocket client implementation.
808
+ * Client options for Node and Bun WebSocket implementations, allowing
809
+ * handshake headers and subprotocols to be supplied together.
803
810
  *
804
811
  * @stability unstable
805
812
  * @category models
806
813
  * @since 4.0.0
807
814
  */
808
815
  export interface WebSocketClientOptions {
816
+ /**
817
+ * Subprotocols to offer in the opening handshake.
818
+ */
819
+ readonly protocols?: string | Array<string> | undefined
809
820
  /**
810
821
  * Headers to include in the opening handshake.
811
822
  *
@@ -857,6 +868,7 @@ export class WebSocketConstructor extends Context.Service<
857
868
 
858
869
  /**
859
870
  * Layer that provides `WebSocketConstructor` using `globalThis.WebSocket`.
871
+ * Rejects client options objects, including those with handshake headers.
860
872
  *
861
873
  * @stability unstable
862
874
  * @category layers
@@ -874,6 +886,8 @@ export const layerWebSocketConstructorGlobal: Layer.Layer<WebSocketConstructor>
874
886
  /**
875
887
  * Creates a `Socket` backed by a `WebSocketConstructor`, dialing the
876
888
  * WebSocket for each reader acquisition.
889
+ * Optional handshake headers require a Node or Bun constructor; without
890
+ * headers, protocols are passed directly for browser compatibility.
877
891
  *
878
892
  * @stability unstable
879
893
  * @category constructors
@@ -882,13 +896,21 @@ export const layerWebSocketConstructorGlobal: Layer.Layer<WebSocketConstructor>
882
896
  export const makeWebSocket = (url: string | Effect.Effect<string>, options?: {
883
897
  readonly openTimeout?: Duration.Input | undefined
884
898
  readonly protocols?: string | Array<string> | undefined
899
+ readonly headers?: Readonly<Record<string, string>> | undefined
885
900
  readonly highWaterMark?: number | undefined
886
901
  }): Effect.Effect<Socket, never, WebSocketConstructor> =>
887
902
  WebSocketConstructor.use((makeWs) =>
888
903
  fromWebSocket(
889
904
  Effect.acquireRelease(
890
905
  (typeof url === "string" ? Effect.succeed(url) : url).pipe(
891
- Effect.map((url) => makeWs(url, options?.protocols))
906
+ Effect.map((url) =>
907
+ makeWs(
908
+ url,
909
+ options?.headers === undefined
910
+ ? options?.protocols
911
+ : { protocols: options.protocols, headers: options.headers }
912
+ )
913
+ )
892
914
  ),
893
915
  (ws) => Effect.sync(() => ws.close(1000))
894
916
  ),
@@ -1165,6 +1187,7 @@ const defaultHighWaterMark = 64 * 1024
1165
1187
  /**
1166
1188
  * Creates a binary `Channel` backed by a WebSocket URL, requiring a
1167
1189
  * `WebSocketConstructor` service.
1190
+ * Supports subprotocols and, with a Node or Bun constructor, handshake headers.
1168
1191
  *
1169
1192
  * @stability unstable
1170
1193
  * @category constructors
@@ -1175,6 +1198,7 @@ export const makeWebSocketChannel = <IE = never>(
1175
1198
  options?: {
1176
1199
  readonly openTimeout?: Duration.Input | undefined
1177
1200
  readonly protocols?: string | Array<string> | undefined
1201
+ readonly headers?: Readonly<Record<string, string>> | undefined
1178
1202
  readonly highWaterMark?: number | undefined
1179
1203
  }
1180
1204
  ): Channel.Channel<
@@ -1193,6 +1217,7 @@ export const makeWebSocketChannel = <IE = never>(
1193
1217
  /**
1194
1218
  * Layer that provides a `Socket` service backed by a WebSocket URL or URL
1195
1219
  * effect.
1220
+ * Supports subprotocols and, with a Node or Bun constructor, handshake headers.
1196
1221
  *
1197
1222
  * @stability unstable
1198
1223
  * @category layers
@@ -1203,6 +1228,7 @@ export const layerWebSocket: (
1203
1228
  options?: {
1204
1229
  readonly openTimeout?: Duration.Input | undefined
1205
1230
  readonly protocols?: string | Array<string> | undefined
1231
+ readonly headers?: Readonly<Record<string, string>> | undefined
1206
1232
  readonly highWaterMark?: number | undefined
1207
1233
  } | undefined
1208
1234
  ) => Layer.Layer<Socket, never, WebSocketConstructor> = flow(makeWebSocket, Layer.effect(Socket))
@@ -5,6 +5,12 @@
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 DatagramSocket from "./DatagramSocket.ts"
13
+
8
14
  /**
9
15
  * @stability unstable
10
16
  * @since 4.0.0
@@ -140,14 +140,21 @@ export const make = <RD = never>({
140
140
  PRIMARY KEY (migration_id)
141
141
  )`,
142
142
  pg: () =>
143
- Effect.catch(
144
- sql`select ${table}::regclass`,
145
- () =>
146
- sql`CREATE TABLE ${sql(table)} (
143
+ Effect.flatMap(
144
+ migrationsTableExists(sql, table),
145
+ (exists) =>
146
+ exists ? Effect.void : Effect.asVoid(sql`CREATE TABLE ${sql(table)} (
147
147
  migration_id integer primary key,
148
148
  created_at timestamp with time zone not null default now(),
149
149
  name text not null
150
- )`
150
+ )`).pipe(
151
+ Effect.catch((error) =>
152
+ Effect.flatMap(
153
+ migrationsTableExists(sql, table),
154
+ (exists) => exists ? Effect.void : Effect.fail(error)
155
+ )
156
+ )
157
+ )
151
158
  ),
152
159
  orElse: () =>
153
160
  sql`CREATE TABLE IF NOT EXISTS ${sql(table)} (
@@ -328,6 +335,15 @@ export const make = <RD = never>({
328
335
  return completed
329
336
  })
330
337
 
338
+ const migrationsTableExists = (sql: Client.SqlClient, table: string): Effect.Effect<boolean, SqlError> => {
339
+ // Match query quoting: to_regclass folds unquoted names to lowercase.
340
+ const quotedTable = sql`${sql(table)}`.compile(true)[0]
341
+ return Effect.map(
342
+ sql`SELECT 1 AS found WHERE to_regclass(${quotedTable}) IS NOT NULL`.withoutTransform,
343
+ (rows) => rows.length > 0
344
+ )
345
+ }
346
+
331
347
  const migrationOrder = Order.make<ResolvedMigration>(([a], [b]) => Order.Number(a, b))
332
348
 
333
349
  const isConstraintConflict = (error: SqlError): boolean =>
@@ -9,7 +9,7 @@
9
9
  * @stability unstable
10
10
  * @since 4.0.0
11
11
  */
12
- import type * as Cause from "../Cause.ts"
12
+ import * as Cause from "../Cause.ts"
13
13
  import { Clock } from "../Clock.ts"
14
14
  import * as Context from "../Context.ts"
15
15
  import * as Effect from "../Effect.ts"
@@ -280,6 +280,12 @@ export const make = Effect.fnUntraced(function*(options: SqlClient.MakeOptions)
280
280
  *
281
281
  * **Details**
282
282
  *
283
+ * Both acquirers lease the connection into the current scope and hold the
284
+ * semaphore until that scope closes, so a statement or stream keeps the
285
+ * connection until it finishes and another fiber cannot begin a transaction
286
+ * underneath it. Effects that use the connection directly, such as an export,
287
+ * have to run inside `Effect.scoped`.
288
+ *
283
289
  * SQLite can keep a transaction open after COMMIT fails, for example on a
284
290
  * deferred foreign key violation. `onCommitFailure` rolls it back before the
285
291
  * connection is reused. If that ROLLBACK fails, the connection is rejected, and
@@ -300,7 +306,7 @@ export const makeSqliteAcquirers = <C extends Connection.Connection>(options: {
300
306
  readonly semaphore: Semaphore.Semaphore
301
307
  readonly isTransaction?: ((conn: C) => boolean) | undefined
302
308
  }): {
303
- readonly acquirer: Effect.Effect<C, SqlError>
309
+ readonly acquirer: Effect.Effect<C, SqlError, Scope.Scope>
304
310
  readonly transactionAcquirer: Effect.Effect<C, SqlError, Scope.Scope>
305
311
  readonly onCommitFailure: (conn: Connection.Connection) => Effect.Effect<void, SqlError>
306
312
  } => {
@@ -336,15 +342,17 @@ export const makeSqliteAcquirers = <C extends Connection.Connection>(options: {
336
342
  })
337
343
  })
338
344
 
345
+ const lease = Effect.uninterruptibleMask((restore) =>
346
+ Effect.flatMap(Effect.scope, (scope) =>
347
+ restore(semaphore.take(1)).pipe(
348
+ Effect.andThen(Scope.addFinalizer(scope, semaphore.release(1))),
349
+ Effect.andThen(available)
350
+ ))
351
+ )
352
+
339
353
  return {
340
- acquirer: semaphore.withPermits(1)(available),
341
- transactionAcquirer: Effect.uninterruptibleMask((restore) =>
342
- Effect.flatMap(Effect.scope, (scope) =>
343
- restore(semaphore.take(1)).pipe(
344
- Effect.andThen(Scope.addFinalizer(scope, semaphore.release(1))),
345
- Effect.andThen(available)
346
- ))
347
- ),
354
+ acquirer: lease,
355
+ transactionAcquirer: lease,
348
356
  onCommitFailure: (conn) => Effect.tapCause(rollback(conn as C), (cause) => Effect.sync(() => poison(cause)))
349
357
  }
350
358
  }
@@ -359,6 +367,18 @@ const isNoTransactionError = (error: SqlError): boolean => {
359
367
  return /no transaction is active/i.test(message);
360
368
  }
361
369
 
370
+ /**
371
+ * A failed COMMIT cleanup leaves the connection in an unsafe state, so both the
372
+ * COMMIT error and the cleanup error are promoted to defects: typed recovery
373
+ * must not be able to hide them.
374
+ */
375
+ const failuresToDefects = <E>(cause: Cause.Cause<E>): Cause.Cause<never> =>
376
+ Cause.fromReasons(cause.reasons.map((reason) =>
377
+ Cause.isFailReason(reason)
378
+ ? Cause.makeDieReason(reason.error).annotate(Cause.reasonAnnotations(reason))
379
+ : reason
380
+ ))
381
+
362
382
  /**
363
383
  * Builds a transaction wrapper that begins top-level transactions, uses
364
384
  * savepoints for nested transactions, commits on success, and rolls back on
@@ -424,15 +444,19 @@ export const makeWithTransaction = <I, S>(options: {
424
444
  )
425
445
  ),
426
446
  (exit) => {
427
- let effect: Effect.Effect<void>
447
+ let effect: Effect.Effect<void, SqlError>
428
448
  if (Exit.isSuccess(exit)) {
429
449
  if (id === 0) {
430
450
  span.event("effect.sql.transaction.commit", clock.currentTimeNanosUnsafe())
431
451
  const onCommitFailure = options.onCommitFailure
432
- effect = Effect.orDie(options.commit(conn))
433
- if (onCommitFailure) {
434
- effect = Effect.onError(effect, () => Effect.orDie(onCommitFailure(conn)))
435
- }
452
+ effect = onCommitFailure
453
+ ? Effect.catchCause(options.commit(conn), (commitCause) =>
454
+ Effect.matchCauseEffect(onCommitFailure(conn), {
455
+ onFailure: (cleanupCause) =>
456
+ Effect.failCause(failuresToDefects(Cause.combine(commitCause, cleanupCause))),
457
+ onSuccess: () => Effect.failCause(commitCause)
458
+ }))
459
+ : options.commit(conn)
436
460
  } else {
437
461
  span.event("effect.sql.transaction.savepoint", clock.currentTimeNanosUnsafe())
438
462
  effect = Effect.void
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Provides a deterministic implementation of the Effect `Crypto` service for
3
+ * tests.
4
+ *
5
+ * Random bytes, numbers, shuffles, UUIDs, and ULIDs are derived from a seeded
6
+ * pseudo-random generator. Digest operations are delegated to an underlying
7
+ * platform `Crypto` service.
8
+ *
9
+ * @stability unstable
10
+ * @since 4.0.0
11
+ */
12
+ import * as Crypto from "../Crypto.ts"
13
+ import * as Effect from "../Effect.ts"
14
+ import * as Layer from "../Layer.ts"
15
+ import * as Random from "../Random.ts"
16
+
17
+ /**
18
+ * Creates a deterministic `Crypto` service using a seeded pseudo-random
19
+ * generator.
20
+ *
21
+ * **When to use**
22
+ *
23
+ * Use to construct a `Crypto` service whose random operations are reproducible
24
+ * in tests while preserving the digest implementation of a platform `Crypto`
25
+ * service.
26
+ *
27
+ * **Details**
28
+ *
29
+ * The effect requires an existing `Crypto` service and delegates digest
30
+ * operations to it. Each evaluation starts a new random sequence from `seed`;
31
+ * every other random operation is derived from that sequence.
32
+ *
33
+ * **Gotchas**
34
+ *
35
+ * The generated values are deterministic and must not be used for
36
+ * security-sensitive purposes. The sequence depends on the order in which
37
+ * random operations are evaluated. UUIDv7 and ULID values also include the
38
+ * current `Clock` time, which must be controlled separately when their complete
39
+ * output needs to be reproducible.
40
+ *
41
+ * @see {@link layer} for providing the service as a layer
42
+ *
43
+ * @stability unstable
44
+ * @category constructors
45
+ * @since 4.0.0
46
+ */
47
+ export const make: (
48
+ seed: string | number
49
+ ) => Effect.Effect<Crypto.Crypto, never, Crypto.Crypto> = Effect.fnUntraced(
50
+ function*(_seed: string | number) {
51
+ const crypto = yield* Crypto.Crypto
52
+ const seededRandom = yield* Random.Random
53
+ return Crypto.make({
54
+ randomBytes: (size) => {
55
+ const bytes = new Uint8Array(size)
56
+ for (let i = 0; i < size; i++) {
57
+ bytes[i] = Math.floor(seededRandom.nextDoubleUnsafe() * 256)
58
+ }
59
+ return bytes
60
+ },
61
+ digest: crypto.digest
62
+ })
63
+ },
64
+ (effect, seed) => Random.withSeed(effect, seed)
65
+ )
66
+
67
+ /**
68
+ * Creates a layer that provides deterministic `Crypto` random operations from
69
+ * a seed.
70
+ *
71
+ * **When to use**
72
+ *
73
+ * Use to provide reproducible random bytes, numbers, shuffles, UUIDs, and ULIDs
74
+ * to an Effect program under test.
75
+ *
76
+ * **Details**
77
+ *
78
+ * The layer requires an existing platform `Crypto` service whose digest
79
+ * implementation is retained. Each layer build starts the random sequence from
80
+ * `seed`.
81
+ *
82
+ * **Gotchas**
83
+ *
84
+ * UUIDv7 and ULID values also depend on the current `Clock`. Provide a test
85
+ * clock when their complete output needs to be reproducible.
86
+ *
87
+ * @see {@link make} for constructing the service directly
88
+ *
89
+ * @stability unstable
90
+ * @category layers
91
+ * @since 4.0.0
92
+ */
93
+ export const layer = (seed: string | number): Layer.Layer<Crypto.Crypto, never, Crypto.Crypto> =>
94
+ Layer.effect(Crypto.Crypto, make(seed))
@@ -17,6 +17,12 @@ export * as TestClock from "./TestClock.ts"
17
17
  */
18
18
  export * as TestConsole from "./TestConsole.ts"
19
19
 
20
+ /**
21
+ * @stability unstable
22
+ * @since 4.0.0
23
+ */
24
+ export * as TestCrypto from "./TestCrypto.ts"
25
+
20
26
  /**
21
27
  * @stability unstable
22
28
  * @since 4.0.0
@@ -10,7 +10,6 @@
10
10
  * @stability unstable
11
11
  * @since 4.0.0
12
12
  */
13
- import * as Arr from "../Array.ts"
14
13
  import type { NonEmptyReadonlyArray } from "../Array.ts"
15
14
  import type * as Brand from "../Brand.ts"
16
15
  import * as Cause from "../Cause.ts"
@@ -18,8 +17,9 @@ import * as Context from "../Context.ts"
18
17
  import * as Effect from "../Effect.ts"
19
18
  import * as Base64Url from "../encoding/Base64Url.ts"
20
19
  import * as Exit from "../Exit.ts"
21
- import * as Filter from "../Filter.ts"
20
+ import * as Fiber from "../Fiber.ts"
22
21
  import { dual } from "../Function.ts"
22
+ import * as Latch from "../Latch.ts"
23
23
  import * as Option from "../Option.ts"
24
24
  import * as Schema from "../Schema.ts"
25
25
  import * as SchemaGetter from "../SchemaGetter.ts"
@@ -134,6 +134,25 @@ const CurrentAttempt = Context.Reference<number>(
134
134
  { defaultValue: () => 1 }
135
135
  )
136
136
 
137
+ /** Awaits registered under the instance copy of a `recordHeld` body. */
138
+ const awaitOwners = new WeakMap<WorkflowInstance["Service"], Array<string>>()
139
+
140
+ const registerAwait = (instance: WorkflowInstance["Service"], name: string) => {
141
+ instance.awaitedDeferreds.set(name, (instance.awaitedDeferreds.get(name) ?? 0) + 1)
142
+ awaitOwners.get(instance)?.push(name)
143
+ }
144
+
145
+ const releaseAwaits = (instance: WorkflowInstance["Service"], names: Array<string>) => {
146
+ for (const name of names) {
147
+ const count = instance.awaitedDeferreds.get(name)! - 1
148
+ if (count === 0) {
149
+ instance.awaitedDeferreds.delete(name)
150
+ } else {
151
+ instance.awaitedDeferreds.set(name, count)
152
+ }
153
+ }
154
+ }
155
+
137
156
  const await_: <Success extends Schema.Constraint, Error extends Schema.Constraint>(
138
157
  self: DurableDeferred<Success, Error>
139
158
  ) => Effect.Effect<
@@ -150,7 +169,7 @@ const await_: <Success extends Schema.Constraint, Error extends Schema.Constrain
150
169
  const engine = yield* EngineTag
151
170
  const instance = yield* InstanceTag
152
171
  // Register before the read so any later completion can preempt the run.
153
- instance.awaitedDeferreds.add(self.name)
172
+ registerAwait(instance, self.name)
154
173
  const exit = yield* Workflow.wrapActivityResult(
155
174
  engine.deferredResult(self),
156
175
  Option.isNone
@@ -180,6 +199,20 @@ export {
180
199
  * Runs an effect and records its exit into the durable deferred, resuming
181
200
  * workflows that are waiting on that deferred.
182
201
  *
202
+ * **Details**
203
+ *
204
+ * The effect and the write of its exit count as one in-flight activity of the
205
+ * workflow, so concurrent branches parked on durable waits do not suspend the
206
+ * run until the exit is recorded. This applies even when the effect itself is
207
+ * not an activity. If the effect parks on a durable wait, the run suspends
208
+ * once the other in-flight activities finish, and nothing is recorded.
209
+ *
210
+ * **Gotchas**
211
+ *
212
+ * A long-running effect keeps the run active instead of letting a parked
213
+ * sibling suspend it. Completing a deferred the run is waiting on still
214
+ * interrupts and replays the run.
215
+ *
183
216
  * @stability unstable
184
217
  * @category combinators
185
218
  * @since 4.0.0
@@ -189,6 +222,20 @@ export const into: {
189
222
  * Runs an effect and records its exit into the durable deferred, resuming
190
223
  * workflows that are waiting on that deferred.
191
224
  *
225
+ * **Details**
226
+ *
227
+ * The effect and the write of its exit count as one in-flight activity of the
228
+ * workflow, so concurrent branches parked on durable waits do not suspend the
229
+ * run until the exit is recorded. This applies even when the effect itself is
230
+ * not an activity. If the effect parks on a durable wait, the run suspends
231
+ * once the other in-flight activities finish, and nothing is recorded.
232
+ *
233
+ * **Gotchas**
234
+ *
235
+ * A long-running effect keeps the run active instead of letting a parked
236
+ * sibling suspend it. Completing a deferred the run is waiting on still
237
+ * interrupts and replays the run.
238
+ *
192
239
  * @stability unstable
193
240
  * @category combinators
194
241
  * @since 4.0.0
@@ -210,6 +257,20 @@ export const into: {
210
257
  * Runs an effect and records its exit into the durable deferred, resuming
211
258
  * workflows that are waiting on that deferred.
212
259
  *
260
+ * **Details**
261
+ *
262
+ * The effect and the write of its exit count as one in-flight activity of the
263
+ * workflow, so concurrent branches parked on durable waits do not suspend the
264
+ * run until the exit is recorded. This applies even when the effect itself is
265
+ * not an activity. If the effect parks on a durable wait, the run suspends
266
+ * once the other in-flight activities finish, and nothing is recorded.
267
+ *
268
+ * **Gotchas**
269
+ *
270
+ * A long-running effect keeps the run active instead of letting a parked
271
+ * sibling suspend it. Completing a deferred the run is waiting on still
272
+ * interrupts and replays the run.
273
+ *
213
274
  * @stability unstable
214
275
  * @category combinators
215
276
  * @since 4.0.0
@@ -243,42 +304,104 @@ export const into: {
243
304
  | Error["DecodingServices"]
244
305
  | Success["EncodingServices"]
245
306
  | Error["EncodingServices"]
246
- > =>
247
- Effect.contextWith(
248
- (context: Context.Context<WorkflowEngine | WorkflowInstance>) => {
249
- const engine = Context.get(context, EngineTag)
250
- const parentInstance = Context.get(context, InstanceTag)
251
- const instance = { ...parentInstance }
252
- return Effect.onExit(
253
- Effect.provideService(effect, InstanceTag, instance),
254
- Effect.fnUntraced(function*(exit) {
255
- if (Exit.isFailure(exit)) {
256
- const [interrupts, reasons] = Arr.partition(
257
- exit.cause.reasons,
258
- Filter.fromPredicate(Cause.isInterruptReason)
259
- )
260
- if (interrupts.length === exit.cause.reasons.length) {
261
- // An interrupt-only exit is never a result: the effect was
262
- // suspended, preempted or interrupted, so record nothing.
263
- if (instance.suspended) {
264
- parentInstance.suspended = true
265
- }
266
- return
267
- } else if (interrupts.length > 0) {
268
- exit = Exit.failCause(Cause.fromReasons(reasons))
269
- }
307
+ > => recordHeld(effect, self, false)
308
+ )
309
+
310
+ /**
311
+ * Records `exit` into `self`. An interrupt-only exit is never a result: the
312
+ * effect was suspended, preempted or interrupted, so nothing is recorded.
313
+ * Interrupts mixed into a failure are dropped from the recorded cause.
314
+ */
315
+ const recordExit = (
316
+ engine: WorkflowEngine["Service"],
317
+ instance: WorkflowInstance["Service"],
318
+ self: DurableDeferred<any, any>,
319
+ exit: Exit.Exit<any, any>
320
+ ) => {
321
+ if (Exit.isFailure(exit)) {
322
+ if (Cause.hasInterruptsOnly(exit.cause)) return Effect.void
323
+ if (Cause.hasInterrupts(exit.cause)) {
324
+ exit = Exit.failCause(Cause.fromReasons(
325
+ exit.cause.reasons.filter((reason) => !Cause.isInterruptReason(reason))
326
+ ))
327
+ }
328
+ }
329
+ return engine.deferredDone(self, {
330
+ workflowName: instance.workflow._tag,
331
+ executionId: instance.executionId,
332
+ deferredName: self.name,
333
+ exit
334
+ })
335
+ }
336
+
337
+ /**
338
+ * Runs `effect` as one activity of the enclosing workflow and records its exit
339
+ * into `self`, returning a previously recorded exit instead when `checkCache`
340
+ * is set. The hold spans the lookup, `effect` and the write, so a parked
341
+ * sibling cannot suspend the run before the continuation starts.
342
+ */
343
+ const recordHeld = Effect.fnUntraced(function*<A, E, R>(
344
+ effect: Effect.Effect<A, E, R>,
345
+ self: DurableDeferred<any, any>,
346
+ checkCache: boolean
347
+ ): Effect.fn.Return<A, E, R | WorkflowEngine | WorkflowInstance> {
348
+ const engine = yield* EngineTag
349
+ const instance = yield* InstanceTag
350
+ const exit = yield* Workflow.wrapActivityResult(
351
+ Effect.gen(function*() {
352
+ if (checkCache) {
353
+ const cached = yield* engine.deferredResult(self)
354
+ if (Option.isSome(cached)) return cached
355
+ }
356
+ // `effect` counts on its own activity state, so a durable await inside
357
+ // it waits for its own siblings and not for the hold above. The copy
358
+ // starts unsuspended: a parked arm of an enclosing race leaves that
359
+ // copy marked suspended while only interrupting its own fiber.
360
+ const local: WorkflowInstance["Service"] = {
361
+ ...instance,
362
+ suspended: false,
363
+ activityState: { count: 0, latch: Latch.makeUnsafe() }
364
+ }
365
+ // A losing branch parked on a durable wait has already exited when the
366
+ // race settles, so this body owns the waits registered beneath it.
367
+ const owned: Array<string> = []
368
+ awaitOwners.set(local, owned)
369
+ // A parked `effect` interrupts the fiber it runs on. The child fiber
370
+ // keeps that from skipping the suspension handling below.
371
+ const fiber = yield* effect.pipe(
372
+ Effect.provideService(InstanceTag, local),
373
+ Effect.onExit((exit) => recordExit(engine, instance, self, exit)),
374
+ Effect.forkChild({ startImmediately: true })
375
+ )
376
+ return yield* Effect.onInterrupt(Fiber.await(fiber), () => Fiber.interrupt(fiber)).pipe(
377
+ Effect.map((exit) =>
378
+ local.suspended && Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)
379
+ ? Option.none()
380
+ : Option.some(exit)
381
+ ),
382
+ // The waits are over once the body has a result. Otherwise every branch
383
+ // parked, or an enclosing race interrupted the body, and the waits move
384
+ // to the enclosing owner, if any, to be released when that race settles.
385
+ Effect.onExit((result) =>
386
+ Effect.sync(() => {
387
+ if (Exit.isSuccess(result) && Option.isSome(result.value)) {
388
+ releaseAwaits(instance, owned)
389
+ } else {
390
+ awaitOwners.get(instance)?.push(...owned)
270
391
  }
271
- yield* engine.deferredDone(self, {
272
- workflowName: instance.workflow._tag,
273
- executionId: instance.executionId,
274
- deferredName: self.name,
275
- exit
276
- })
277
392
  })
278
393
  )
279
- }
280
- )
281
- )
394
+ )
395
+ }),
396
+ // Every branch of `effect` parked: release the hold and let the enclosing
397
+ // activities finish before suspending. External preemption does not wait.
398
+ Option.isNone
399
+ )
400
+ if (Option.isNone(exit)) {
401
+ return yield* Workflow.suspend(instance)
402
+ }
403
+ return yield* exit.value
404
+ })
282
405
 
283
406
  /**
284
407
  * Runs effects as a durable race, returning a previously persisted result when
@@ -312,17 +435,7 @@ export const raceAll = <
312
435
  success: options.success,
313
436
  error: options.error
314
437
  })
315
- return Effect.gen(function*() {
316
- const engine = yield* EngineTag
317
- const exit = yield* engine.deferredResult(deferred)
318
- if (Option.isSome(exit)) {
319
- return yield* exit.value
320
- }
321
- return yield* into(
322
- Effect.raceAll(options.effects),
323
- deferred
324
- )
325
- })
438
+ return recordHeld(Effect.raceAll(options.effects), deferred, true)
326
439
  }
327
440
 
328
441
  /**