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
@@ -0,0 +1,919 @@
1
+ /**
2
+ * Pull-based UDP sockets with scoped native handles.
3
+ *
4
+ * Acquiring a reader opens and binds a socket owned by its scope. Only one
5
+ * reader is open at a time; another acquisition waits interruptibly for it to
6
+ * close. Use `Effect.retry` around a scoped consume loop to open a new socket
7
+ * on each retry.
8
+ *
9
+ * Writes wait while no reader is open, so a send-only client must still
10
+ * acquire a reader.
11
+ *
12
+ * **Example** (Portable request/reply client)
13
+ *
14
+ * ```ts import.meta.vitest
15
+ * import { Effect } from "effect"
16
+ * import { NetAddress } from "effect/net"
17
+ * import { DatagramSocket } from "effect/socket"
18
+ *
19
+ * const server = NetAddress.inetAddressFromIpStringUnsafe("127.0.0.1", 5353)
20
+ *
21
+ * export const request = (payload: string) =>
22
+ * Effect.gen(function*() {
23
+ * const socket = yield* DatagramSocket.DatagramSocket
24
+ * const reader = yield* socket.reader
25
+ * const writer = yield* socket.writer
26
+ * yield* writer.write({ payload, address: server })
27
+ * const [response] = yield* reader.pull
28
+ * return response.payload
29
+ * }).pipe(Effect.scoped)
30
+ * ```
31
+ *
32
+ * @stability experimental
33
+ * @since 4.0.0
34
+ */
35
+ import type { NonEmptyReadonlyArray } from "../Array.ts"
36
+ import * as Context from "../Context.ts"
37
+ import * as Effect from "../Effect.ts"
38
+ import * as Fiber from "../Fiber.ts"
39
+ import { constVoid } from "../Function.ts"
40
+ import { args, contA, contAll, exitSucceed, makePrimitive, type Primitive, withFiber } from "../internal/core.ts"
41
+ import type { FiberImpl } from "../internal/effect.ts"
42
+ import * as Latch from "../Latch.ts"
43
+ import * as NetAddress from "../net/NetAddress.ts"
44
+ import * as Predicate from "../Predicate.ts"
45
+ import * as Schema from "../Schema.ts"
46
+ import * as Scope from "../Scope.ts"
47
+
48
+ /**
49
+ * Runtime type identifier attached to `DatagramSocket` services.
50
+ *
51
+ * @stability experimental
52
+ * @category type IDs
53
+ * @since 4.0.0
54
+ */
55
+ export const TypeId = "~effect/socket/DatagramSocket"
56
+
57
+ /**
58
+ * Service tag for UDP socket transports.
59
+ *
60
+ * @stability experimental
61
+ * @category services
62
+ * @since 4.0.0
63
+ */
64
+ export const DatagramSocket: Context.Service<DatagramSocket, DatagramSocket> = Context.Service<DatagramSocket>(
65
+ "effect/socket/DatagramSocket"
66
+ )
67
+
68
+ /**
69
+ * A UDP socket with a scoped, exclusive reader. Acquiring `reader` opens and
70
+ * binds a native socket; subsequent acquisitions wait for its scope to close.
71
+ * Acquiring `writer` cannot fail, but writes wait for an open reader.
72
+ *
73
+ * @stability experimental
74
+ * @category models
75
+ * @since 4.0.0
76
+ */
77
+ export interface DatagramSocket {
78
+ readonly [TypeId]: typeof TypeId
79
+ readonly reader: Effect.Effect<Reader, DatagramSocketError, Scope.Scope>
80
+ readonly writer: Effect.Effect<Writer, never, Scope.Scope>
81
+ }
82
+
83
+ /**
84
+ * A received packet.
85
+ *
86
+ * **Details**
87
+ *
88
+ * The payload is safe to retain. The sender's `address` is parsed on first
89
+ * read and cached.
90
+ *
91
+ * Reply with `writer.write({ payload, address: received })` to reuse the raw
92
+ * sender address without parsing. This requires a datagram from a platform
93
+ * adapter or `makeFromBackingSocket`. For hand-built datagrams or custom
94
+ * readers, use `{ payload, address: received.address }`.
95
+ *
96
+ * @stability experimental
97
+ * @category models
98
+ * @since 4.0.0
99
+ */
100
+ export interface Datagram {
101
+ readonly payload: Uint8Array
102
+ readonly address: NetAddress.InetAddress
103
+ }
104
+
105
+ /**
106
+ * A packet to send.
107
+ *
108
+ * **Details**
109
+ *
110
+ * String payloads are encoded as UTF-8. `address` can be omitted when the
111
+ * socket has a `peer` or is connected. On a connected socket an explicit
112
+ * `InetAddress` fails the write, while a received `Datagram` is allowed and
113
+ * goes out on the connected path.
114
+ *
115
+ * Pass a received `Datagram` as the destination to reply, or as the whole
116
+ * argument to echo it. Both reuse the raw sender address and require a
117
+ * datagram from a platform adapter or `makeFromBackingSocket`. Hand-built
118
+ * datagrams and custom readers must pass an explicit `InetAddress` instead.
119
+ *
120
+ * @stability experimental
121
+ * @category models
122
+ * @since 4.0.0
123
+ */
124
+ export interface OutgoingDatagram {
125
+ readonly payload: Uint8Array | string
126
+ readonly address?: NetAddress.InetAddress | Datagram | undefined
127
+ }
128
+
129
+ /**
130
+ * The receive side of an open native socket.
131
+ *
132
+ * **Details**
133
+ *
134
+ * `pull` returns everything queued since the last pull, or waits for the next
135
+ * packet. After a terminal error, `pull` first returns the queued packets and
136
+ * then fails with the same error until the reader's scope closes. Closing the
137
+ * scope discards the queue and fails every waiting `pull` with
138
+ * `DatagramSocketClosedError`.
139
+ *
140
+ * Concurrent pulls wait in FIFO order. Each incoming packet goes to the
141
+ * oldest waiter; a terminal error fails all waiters.
142
+ *
143
+ * `address` is the bound local address, parsed on first read and cached.
144
+ *
145
+ * `dropped` counts receive-buffer overflow since this reader opened, not
146
+ * kernel or network loss or packets discarded on scope close.
147
+ *
148
+ * `joinMulticast` joins a group until its scope closes; leave failures are
149
+ * ignored. Its `interface` selects where packets arrive (ingress), not the
150
+ * platform's `multicast.interface` for sending (egress).
151
+ *
152
+ * @stability experimental
153
+ * @category models
154
+ * @since 4.0.0
155
+ */
156
+ export interface Reader {
157
+ readonly pull: Effect.Effect<NonEmptyReadonlyArray<Datagram>, DatagramSocketError>
158
+ readonly address: NetAddress.InetAddress
159
+ readonly dropped: () => number
160
+ readonly joinMulticast: <A extends NetAddress.IpAddress>(options: {
161
+ readonly group: NetAddress.MulticastAddress<A>
162
+ readonly interface?: NetAddress.MulticastInterface<A> | undefined
163
+ readonly source?: A | undefined
164
+ }) => Effect.Effect<void, DatagramSocketError, Scope.Scope>
165
+ }
166
+
167
+ /**
168
+ * The send side of a `DatagramSocket`. Writes wait until a reader is open, so
169
+ * a send-only client must still acquire a reader.
170
+ *
171
+ * **Details**
172
+ *
173
+ * A write completes when the runtime reports the send's result, and send
174
+ * errors fail it with a `DatagramSocketWriteError` carrying the destination
175
+ * `address`. After a terminal reader error, writes fail with that error until
176
+ * the reader's scope closes, and then wait for the next reader.
177
+ *
178
+ * `writeAll` fails with the first error, without resending. Partial delivery is
179
+ * unspecified. The error has no packet index, and its `address` is absent on Bun.
180
+ *
181
+ * There is no built-in write timeout; use `Effect.timeout`.
182
+ *
183
+ * @stability experimental
184
+ * @category models
185
+ * @since 4.0.0
186
+ */
187
+ export interface Writer {
188
+ readonly write: (datagram: OutgoingDatagram) => Effect.Effect<void, DatagramSocketError>
189
+ readonly writeAll: (datagrams: NonEmptyReadonlyArray<OutgoingDatagram>) => Effect.Effect<void, DatagramSocketError>
190
+ }
191
+
192
+ /**
193
+ * Receive queue options.
194
+ *
195
+ * **Details**
196
+ *
197
+ * The queue is bounded in packets, with no byte cap. `capacity` defaults to
198
+ * 1024 and must be a positive safe integer. The worst-case
199
+ * memory is capacity × 64 KiB, about 64 MiB at the default.
200
+ *
201
+ * When the queue is full, `"dropping"` (the default) discards the incoming
202
+ * packet and `"sliding"` discards the oldest queued one. Both count the
203
+ * packet in `Reader.dropped`.
204
+ *
205
+ * @stability experimental
206
+ * @category models
207
+ * @since 4.0.0
208
+ */
209
+ export interface ReceiveBufferOptions {
210
+ readonly capacity?: number | undefined
211
+ readonly strategy?: "dropping" | "sliding" | undefined
212
+ }
213
+
214
+ /**
215
+ * Creates a `DatagramSocket` from its `reader` and `writer` effects.
216
+ *
217
+ * @stability experimental
218
+ * @category constructors
219
+ * @since 4.0.0
220
+ */
221
+ export const make = (options: {
222
+ readonly reader: DatagramSocket["reader"]
223
+ readonly writer: DatagramSocket["writer"]
224
+ }): DatagramSocket =>
225
+ DatagramSocket.of({
226
+ [TypeId]: TypeId,
227
+ reader: options.reader,
228
+ writer: options.writer
229
+ })
230
+
231
+ /**
232
+ * A native host and port, parsed on demand.
233
+ *
234
+ * @stability experimental
235
+ * @category models
236
+ * @since 4.0.0
237
+ */
238
+ export interface BackingAddress {
239
+ readonly host: string
240
+ readonly port: number
241
+ }
242
+
243
+ /**
244
+ * Native transport contract used by platform adapters.
245
+ *
246
+ * **Details**
247
+ *
248
+ * `address` is the raw bound address, read once at open. `scopeIds` maps IPv6
249
+ * zone names to scope IDs, for parsing named zones and formatting
250
+ * destinations. `peer` is the default destination, and `connected` marks a
251
+ * natively connected socket, whose sends receive no destination.
252
+ *
253
+ * `send` and `sendMany` hand datagrams to the runtime and call `done` exactly
254
+ * once, synchronously or later, when the runtime reports the result. They must
255
+ * not throw. `sendMany` passes the index of the failing datagram to `done`
256
+ * when the runtime knows it, so core can attach its address.
257
+ *
258
+ * Adapters normalize native errors before passing them to core.
259
+ * Read destination `host` and `port` during the call; do not retain the record.
260
+ *
261
+ * `close` must not throw.
262
+ *
263
+ * @stability experimental
264
+ * @category models
265
+ * @since 4.0.0
266
+ */
267
+ export interface BackingSocket {
268
+ readonly address: BackingAddress
269
+ readonly scopeIds?: ReadonlyMap<string, number> | undefined
270
+ readonly peer?: BackingAddress | undefined
271
+ readonly connected?: boolean | undefined
272
+ readonly send: (
273
+ payload: Uint8Array,
274
+ destination: BackingAddress | undefined,
275
+ done: (error?: DatagramSocketError) => void
276
+ ) => void
277
+ readonly sendMany: (
278
+ payloads: ReadonlyArray<Uint8Array>,
279
+ destinations: ReadonlyArray<BackingAddress | undefined>,
280
+ done: (error?: DatagramSocketError, index?: number) => void
281
+ ) => void
282
+ readonly joinMulticast: <A extends NetAddress.IpAddress>(options: {
283
+ readonly group: NetAddress.MulticastAddress<A>
284
+ readonly interface?: NetAddress.MulticastInterface<A> | undefined
285
+ readonly source?: A | undefined
286
+ }) => Effect.Effect<() => Effect.Effect<void, never>, DatagramSocketError>
287
+ readonly close: () => void
288
+ }
289
+
290
+ /**
291
+ * Callbacks installed before opening the backing socket; packets may arrive
292
+ * synchronously.
293
+ *
294
+ * **Details**
295
+ *
296
+ * `onPacket` queues a received packet. `onReadError` is a terminal receive
297
+ * error and `onClose` reports the native socket closing underneath the
298
+ * reader; both are sticky. `onError` forwards errors with no write left to
299
+ * fail, such as ICMP reports, to the user's `onError` option.
300
+ *
301
+ * Each callback can be passed on its own, for example as a runtime's event
302
+ * listener.
303
+ *
304
+ * @stability experimental
305
+ * @category models
306
+ * @since 4.0.0
307
+ */
308
+ export interface BackingEvents {
309
+ readonly onPacket: (payload: Uint8Array, host: string, port: number) => void
310
+ readonly onReadError: (error: DatagramSocketError) => void
311
+ readonly onError: (error: DatagramSocketError) => void
312
+ readonly onClose: () => void
313
+ }
314
+
315
+ /**
316
+ * The normalized kind of a send or receive failure.
317
+ *
318
+ * @stability experimental
319
+ * @category errors
320
+ * @since 4.0.0
321
+ */
322
+ export type IoErrorKind = "MessageTooLarge" | "Unreachable" | "ConnectionRefused" | "PermissionDenied" | "Unknown"
323
+
324
+ /**
325
+ * Opening or binding the native socket, applying its options, or resolving
326
+ * a name failed.
327
+ *
328
+ * @stability experimental
329
+ * @category errors
330
+ * @since 4.0.0
331
+ */
332
+ export class DatagramSocketOpenError
333
+ extends Schema.Error<DatagramSocketOpenError>("effect/socket/DatagramSocket/OpenError")({
334
+ _tag: Schema.tag("DatagramSocketOpenError"),
335
+ kind: Schema.Literals(["AddressInUse", "AddressNotAvailable", "PermissionDenied", "Unknown"]),
336
+ cause: Schema.Defect()
337
+ })
338
+ {}
339
+
340
+ /**
341
+ * A receive failed, or an ICMP report arrived with no write left to fail.
342
+ *
343
+ * @stability experimental
344
+ * @category errors
345
+ * @since 4.0.0
346
+ */
347
+ export class DatagramSocketReadError
348
+ extends Schema.Error<DatagramSocketReadError>("effect/socket/DatagramSocket/ReadError")({
349
+ _tag: Schema.tag("DatagramSocketReadError"),
350
+ kind: Schema.Literals(["MessageTooLarge", "Unreachable", "ConnectionRefused", "PermissionDenied", "Unknown"]),
351
+ cause: Schema.Defect()
352
+ })
353
+ {}
354
+
355
+ /**
356
+ * A send failed. `address` is the destination when it is known.
357
+ *
358
+ * @stability experimental
359
+ * @category errors
360
+ * @since 4.0.0
361
+ */
362
+ export class DatagramSocketWriteError
363
+ extends Schema.Error<DatagramSocketWriteError>("effect/socket/DatagramSocket/WriteError")({
364
+ _tag: Schema.tag("DatagramSocketWriteError"),
365
+ kind: Schema.Literals(["MessageTooLarge", "Unreachable", "ConnectionRefused", "PermissionDenied", "Unknown"]),
366
+ address: Schema.optional(Schema.InetAddress),
367
+ cause: Schema.Defect()
368
+ })
369
+ {}
370
+
371
+ /**
372
+ * The native socket closed underneath the reader, or the reader's scope
373
+ * closed.
374
+ *
375
+ * @stability experimental
376
+ * @category errors
377
+ * @since 4.0.0
378
+ */
379
+ export class DatagramSocketClosedError
380
+ extends Schema.Error<DatagramSocketClosedError>("effect/socket/DatagramSocket/ClosedError")({
381
+ _tag: Schema.tag("DatagramSocketClosedError")
382
+ })
383
+ {}
384
+
385
+ /**
386
+ * The runtime lacks a capability, such as source-specific multicast on Deno.
387
+ *
388
+ * @stability experimental
389
+ * @category errors
390
+ * @since 4.0.0
391
+ */
392
+ export class DatagramSocketUnsupportedError
393
+ extends Schema.Error<DatagramSocketUnsupportedError>("effect/socket/DatagramSocket/UnsupportedError")({
394
+ _tag: Schema.tag("DatagramSocketUnsupportedError"),
395
+ capability: Schema.String,
396
+ runtime: Schema.String
397
+ })
398
+ {
399
+ /**
400
+ * Names the runtime and the capability it lacks.
401
+ *
402
+ * @stability experimental
403
+ * @since 4.0.0
404
+ */
405
+ override get message() {
406
+ return `${this.runtime} does not support ${this.capability}`
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Schema for the union of `DatagramSocketError` reasons.
412
+ *
413
+ * @stability experimental
414
+ * @category errors
415
+ * @since 4.0.0
416
+ */
417
+ export const DatagramSocketErrorReason = Schema.Union([
418
+ DatagramSocketOpenError,
419
+ DatagramSocketReadError,
420
+ DatagramSocketWriteError,
421
+ DatagramSocketClosedError,
422
+ DatagramSocketUnsupportedError
423
+ ])
424
+ /**
425
+ * The union of `DatagramSocketError` reasons.
426
+ *
427
+ * @stability experimental
428
+ * @category errors
429
+ * @since 4.0.0
430
+ */
431
+ export type DatagramSocketErrorReason = typeof DatagramSocketErrorReason.Type
432
+
433
+ /**
434
+ * Runtime type identifier attached to `DatagramSocketError` values.
435
+ *
436
+ * @stability experimental
437
+ * @category errors
438
+ * @since 4.0.0
439
+ */
440
+ export const DatagramSocketErrorTypeId = "~effect/socket/DatagramSocket/DatagramSocketError"
441
+
442
+ /**
443
+ * Returns `true` when a value is a `DatagramSocketError`.
444
+ *
445
+ * @stability experimental
446
+ * @category guards
447
+ * @since 4.0.0
448
+ */
449
+ export const isDatagramSocketError = (u: unknown): u is DatagramSocketError =>
450
+ Predicate.hasProperty(u, DatagramSocketErrorTypeId)
451
+
452
+ /**
453
+ * The error raised by `DatagramSocket` operations, wrapping a specific
454
+ * reason. Its `cause` and `message` come from the reason.
455
+ *
456
+ * @stability experimental
457
+ * @category errors
458
+ * @since 4.0.0
459
+ */
460
+ export class DatagramSocketError
461
+ extends Schema.TaggedError<DatagramSocketError>(DatagramSocketErrorTypeId)("DatagramSocketError", {
462
+ _tag: Schema.tag("DatagramSocketError"),
463
+ reason: DatagramSocketErrorReason
464
+ })
465
+ {
466
+ // @effect-diagnostics-next-line overriddenSchemaConstructor:off
467
+ constructor(props: { readonly reason: DatagramSocketErrorReason }) {
468
+ if ("cause" in props.reason) {
469
+ super({ ...props, cause: props.reason.cause } as any)
470
+ } else {
471
+ super(props)
472
+ }
473
+ }
474
+ readonly [DatagramSocketErrorTypeId] = DatagramSocketErrorTypeId
475
+ override get message() {
476
+ return this.reason.message
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Builds a `DatagramSocket` over a backing socket, opening a new socket for
482
+ * each reader acquisition.
483
+ *
484
+ * **Details**
485
+ *
486
+ * `open` receives the event callbacks before the handle exists, so packets can
487
+ * be queued while it opens. Core owns the receive queue and its overflow
488
+ * strategy, reader exclusivity, sticky errors, lazy address parsing, the
489
+ * writer latch and destination formatting. The handle is closed when the
490
+ * reader's scope closes. If acquisition is interrupted while `open` is still
491
+ * running, the interruption returns at once, `open` is left to finish, and
492
+ * the handle it produces is closed. The next reader waits until that open has
493
+ * settled, so two native sockets never exist at once. An adopted `acquire`
494
+ * must therefore finish in bounded time.
495
+ *
496
+ * `open` runs in the reader's scope, with the services of the fiber that
497
+ * built the socket, so an adopted `acquire` can use them and register
498
+ * finalizers that run when the reader closes. Where the fiber acquiring the
499
+ * reader has a service with the same tag, its own takes precedence.
500
+ *
501
+ * Concurrent pulls are served in the order they started waiting.
502
+ *
503
+ * `onError` receives errors with no write left to fail. It runs
504
+ * synchronously, and anything it throws is ignored.
505
+ *
506
+ * Invalid `receiveBuffer.capacity` values cause a defect; use a positive
507
+ * safe integer.
508
+ *
509
+ * @stability experimental
510
+ * @category constructors
511
+ * @since 4.0.0
512
+ */
513
+ export const makeFromBackingSocket = <R = never>(
514
+ open: (events: BackingEvents) => Effect.Effect<BackingSocket, DatagramSocketError, R>,
515
+ options?: {
516
+ readonly receiveBuffer?: ReceiveBufferOptions | undefined
517
+ readonly onError?: ((error: DatagramSocketError) => void) | undefined
518
+ } | undefined
519
+ ): Effect.Effect<DatagramSocket, never, Exclude<R, Scope.Scope>> =>
520
+ withFiber((fiber) => {
521
+ const capacity = options?.receiveBuffer?.capacity ?? 1024
522
+ if (!Number.isSafeInteger(capacity) || capacity < 1) {
523
+ throw new RangeError(`DatagramSocket receive buffer capacity must be a positive integer, received ${capacity}`)
524
+ }
525
+ return Effect.succeed(
526
+ makeFromBackingSocketWithContext(
527
+ open,
528
+ fiber.context as Context.Context<R>,
529
+ capacity,
530
+ options?.receiveBuffer?.strategy === "sliding",
531
+ options?.onError
532
+ )
533
+ )
534
+ })
535
+
536
+ const makeFromBackingSocketWithContext = <R>(
537
+ open: (events: BackingEvents) => Effect.Effect<BackingSocket, DatagramSocketError, R>,
538
+ services: Context.Context<R>,
539
+ capacity: number,
540
+ sliding: boolean,
541
+ onError: ((error: DatagramSocketError) => void) | undefined
542
+ ): DatagramSocket => {
543
+ const free = Latch.makeUnsafe(true)
544
+ const latch = Latch.makeUnsafe(false)
545
+ let current: ReaderState | undefined
546
+
547
+ const abandon = (state: ReaderState, opening: Fiber.Fiber<BackingSocket, DatagramSocketError>) => {
548
+ state.close()
549
+ // Release reader ownership only after the interrupted open settles.
550
+ opening.addObserver((exit) => {
551
+ if (exit._tag === "Success") exit.value.close()
552
+ free.openUnsafe()
553
+ })
554
+ }
555
+
556
+ const release = (state: ReaderState) => {
557
+ state.close()
558
+ current = undefined
559
+ latch.closeUnsafe()
560
+ free.openUnsafe()
561
+ }
562
+
563
+ const reader: DatagramSocket["reader"] = Effect.uninterruptibleMask(
564
+ Effect.fnUntraced(function*(restore) {
565
+ while (!free.closeUnsafe()) yield* restore(free.await)
566
+ const scope = yield* Effect.scope
567
+ const state = new ReaderState(capacity, sliding, onError)
568
+ // Native opens may be uncancellable. Interrupt only the wait; `abandon`
569
+ // closes any socket returned later.
570
+ const opened = open(state.events).pipe(
571
+ Effect.updateContext((input: Context.Context<never>) =>
572
+ Context.add(Context.merge(services, input), Scope.Scope, scope)
573
+ )
574
+ ) as Effect.Effect<BackingSocket, DatagramSocketError>
575
+ const opening = yield* Effect.forkDetach(opened, { startImmediately: true })
576
+ state.handle = yield* restore(Fiber.join(opening)).pipe(
577
+ Effect.onError(() => Effect.sync(() => abandon(state, opening)))
578
+ )
579
+ current = state
580
+ latch.openUnsafe()
581
+ yield* Effect.addFinalizer(() => Effect.sync(() => release(state)))
582
+ return makeReader(state)
583
+ })
584
+ )
585
+
586
+ const write = (datagram: OutgoingDatagram): Effect.Effect<void, DatagramSocketError> =>
587
+ withFiber((fiber) => {
588
+ const state = current
589
+ return state === undefined ? latch.whenOpen(write(datagram)) : state.write(datagram, fiber)
590
+ })
591
+ const writeAll = (datagrams: NonEmptyReadonlyArray<OutgoingDatagram>): Effect.Effect<void, DatagramSocketError> =>
592
+ withFiber((fiber) => {
593
+ const state = current
594
+ return state === undefined ? latch.whenOpen(writeAll(datagrams)) : state.writeAll(datagrams, fiber)
595
+ })
596
+ const writer: DatagramSocket["writer"] = Effect.succeed({ write, writeAll })
597
+
598
+ return make({ reader, writer })
599
+ }
600
+
601
+ const encoder = new TextEncoder()
602
+ const encode = (payload: Uint8Array | string) => typeof payload === "string" ? encoder.encode(payload) : payload
603
+ const emptyScopeIds: ReadonlyMap<string, number> = new Map()
604
+
605
+ const closedError = () => new DatagramSocketError({ reason: new DatagramSocketClosedError() })
606
+
607
+ // Rejected writes return this sentinel, avoiding per-packet error type checks.
608
+ const rejected: BackingAddress = { host: "", port: 0 }
609
+
610
+ class DatagramImpl implements Datagram, BackingAddress {
611
+ payload: Uint8Array
612
+ host: string
613
+ port: number
614
+ readonly owner: ReaderState
615
+ #address: NetAddress.InetAddress | undefined
616
+ constructor(payload: Uint8Array, host: string, port: number, owner: ReaderState) {
617
+ this.payload = payload
618
+ this.host = host
619
+ this.port = port
620
+ this.owner = owner
621
+ }
622
+ get address(): NetAddress.InetAddress {
623
+ return this.#address ??= NetAddress.inetAddressFromNativeUnsafe(this.host, this.port, this.owner.scopeIds)
624
+ }
625
+ // only for a queued record, which no pull has returned yet
626
+ reuse(payload: Uint8Array, host: string, port: number) {
627
+ this.payload = payload
628
+ this.host = host
629
+ this.port = port
630
+ this.#address = undefined
631
+ }
632
+ }
633
+
634
+ const targetOf = (datagram: OutgoingDatagram): NetAddress.InetAddress | DatagramImpl | undefined =>
635
+ "host" in datagram ? datagram as DatagramImpl : datagram.address as NetAddress.InetAddress | DatagramImpl | undefined
636
+
637
+ class ReaderState {
638
+ readonly capacity: number
639
+ readonly sliding: boolean
640
+ readonly listener: ((error: DatagramSocketError) => void) | undefined
641
+ readonly events: BackingEvents = {
642
+ onPacket: (payload, host, port) => this.push(payload, host, port),
643
+ onReadError: (error) => this.fail(error),
644
+ onError: (error) => this.report(error),
645
+ onClose: () => this.fail(closedError())
646
+ }
647
+
648
+ // set when `open` completes; packets can arrive before that
649
+ handle: BackingSocket | undefined = undefined
650
+ failure: Effect.Effect<never, DatagramSocketError> | undefined = undefined
651
+ // separate from `failure`: after a sticky read error the socket is still
652
+ // open, so ICMP reports still reach `onError`
653
+ closed = false
654
+
655
+ // Sliding overflow turns the full buffer into a ring, oldest at `head`.
656
+ buffer: Array<DatagramImpl> = []
657
+ head = 0
658
+ dropped = 0
659
+
660
+ // A single consumer uses the first waiter slot without touching the array.
661
+ waiter: FiberImpl | undefined = undefined
662
+ waiters: Array<FiberImpl> = []
663
+ readonly unpark: Primitive = unpark(this)
664
+
665
+ bound: NetAddress.InetAddress | undefined = undefined
666
+ // one-entry cache for `write` to the same explicit address
667
+ lastTarget: NetAddress.InetAddress | undefined = undefined
668
+ lastDestination: BackingAddress | undefined = undefined
669
+ rejection: DatagramSocketError | undefined = undefined
670
+
671
+ constructor(capacity: number, sliding: boolean, listener: ((error: DatagramSocketError) => void) | undefined) {
672
+ this.capacity = capacity
673
+ this.sliding = sliding
674
+ this.listener = listener
675
+ }
676
+
677
+ push(payload: Uint8Array, host: string, port: number) {
678
+ if (this.failure !== undefined) return
679
+ if (this.buffer.length >= this.capacity) return this.overflow(payload, host, port)
680
+ const datagram = new DatagramImpl(payload, host, port, this)
681
+ if (this.waiter !== undefined) return this.wake(datagram)
682
+ const buffer = this.buffer
683
+ buffer[buffer.length] = datagram
684
+ }
685
+
686
+ wake(datagram: DatagramImpl) {
687
+ const fiber = this.waiter!
688
+ this.promoteWaiter()
689
+ fiber.evaluate(exitSucceed([datagram]) as any)
690
+ }
691
+
692
+ overflow(payload: Uint8Array, host: string, port: number) {
693
+ this.dropped++
694
+ if (!this.sliding) return
695
+ const buffer = this.buffer
696
+ const head = this.head
697
+ buffer[head].reuse(payload, host, port)
698
+ this.head = head + 1 === buffer.length ? 0 : head + 1
699
+ }
700
+
701
+ take(): NonEmptyReadonlyArray<Datagram> {
702
+ const buffer = this.buffer
703
+ const head = this.head
704
+ this.buffer = []
705
+ if (head === 0) return buffer as unknown as NonEmptyReadonlyArray<Datagram>
706
+ this.head = 0
707
+ const length = buffer.length
708
+ const batch = new Array<DatagramImpl>(length)
709
+ let j = 0
710
+ for (let i = head; i < length; i++) batch[j++] = buffer[i]
711
+ for (let i = 0; i < head; i++) batch[j++] = buffer[i]
712
+ return batch as unknown as NonEmptyReadonlyArray<Datagram>
713
+ }
714
+
715
+ park(fiber: FiberImpl) {
716
+ if (this.waiter === undefined) this.waiter = fiber
717
+ else this.waiters.push(fiber)
718
+ }
719
+
720
+ promoteWaiter() {
721
+ this.waiter = this.waiters.length === 0 ? undefined : this.waiters.shift()
722
+ }
723
+
724
+ removeWaiter(fiber: FiberImpl) {
725
+ if (this.waiter === fiber) return this.promoteWaiter()
726
+ if (this.waiters.length === 0) return
727
+ const index = this.waiters.indexOf(fiber)
728
+ if (index !== -1) this.waiters.splice(index, 1)
729
+ }
730
+
731
+ failWaiters() {
732
+ const waiter = this.waiter
733
+ if (waiter === undefined) return
734
+ const failure = this.failure!
735
+ const waiters = this.waiters
736
+ this.waiter = undefined
737
+ this.waiters = []
738
+ waiter.evaluate(failure as any)
739
+ for (let i = 0; i < waiters.length; i++) waiters[i].evaluate(failure as any)
740
+ }
741
+
742
+ fail(error: DatagramSocketError) {
743
+ if (this.failure !== undefined) return
744
+ this.failure = Effect.fail(error)
745
+ this.failWaiters()
746
+ }
747
+
748
+ close() {
749
+ this.closed = true
750
+ this.buffer = []
751
+ this.head = 0
752
+ this.failure = Effect.fail(closedError())
753
+ this.failWaiters()
754
+ this.handle?.close()
755
+ }
756
+
757
+ report(error: DatagramSocketError) {
758
+ const listener = this.listener
759
+ if (listener === undefined || this.closed) return
760
+ try {
761
+ listener(error)
762
+ } catch {
763
+ // Listener errors cannot fail native callbacks.
764
+ }
765
+ }
766
+
767
+ // packets can arrive before the handle does, so they read this lazily
768
+ get scopeIds(): ReadonlyMap<string, number> {
769
+ return this.handle?.scopeIds ?? emptyScopeIds
770
+ }
771
+
772
+ get address(): NetAddress.InetAddress {
773
+ return this.bound ??= NetAddress.inetAddressFromNativeUnsafe(
774
+ this.handle!.address.host,
775
+ this.handle!.address.port,
776
+ this.scopeIds
777
+ )
778
+ }
779
+
780
+ // Synchronous sends avoid parking; async completions resume inline.
781
+ // Ignore completions after interruption.
782
+ write(datagram: OutgoingDatagram, fiber: FiberImpl): Effect.Effect<void, DatagramSocketError> {
783
+ if (this.failure !== undefined) return this.failure
784
+ const target = targetOf(datagram)
785
+ const destination = this.destination(target)
786
+ if (destination === rejected) return Effect.fail(this.rejection!)
787
+ const handle = this.handle!
788
+ const payload = encode(datagram.payload)
789
+ let result: Effect.Effect<void, DatagramSocketError> | undefined
790
+ let parked = false
791
+ handle.send(payload, destination, (error) => {
792
+ if (result !== undefined) return
793
+ result = error === undefined ? Effect.void : Effect.fail(this.withAddress(error, target))
794
+ if (parked) fiber.evaluate(result as any)
795
+ })
796
+ if (result !== undefined) return result
797
+ parked = true
798
+ return fiber.yieldWith(() => {
799
+ parked = false
800
+ }) as any
801
+ }
802
+
803
+ writeAll(
804
+ datagrams: NonEmptyReadonlyArray<OutgoingDatagram>,
805
+ fiber: FiberImpl
806
+ ): Effect.Effect<void, DatagramSocketError> {
807
+ if (this.failure !== undefined) return this.failure
808
+ const payloads = new Array<Uint8Array>(datagrams.length)
809
+ const destinations = new Array<BackingAddress | undefined>(datagrams.length)
810
+ for (let i = 0; i < datagrams.length; i++) {
811
+ const datagram = datagrams[i]
812
+ const destination = this.destination(targetOf(datagram))
813
+ if (destination === rejected) return Effect.fail(this.rejection!)
814
+ payloads[i] = encode(datagram.payload)
815
+ destinations[i] = destination
816
+ }
817
+ let result: Effect.Effect<void, DatagramSocketError> | undefined
818
+ let parked = false
819
+ this.handle!.sendMany(payloads, destinations, (error, index) => {
820
+ if (result !== undefined) return
821
+ result = error === undefined
822
+ ? Effect.void
823
+ : Effect.fail(index === undefined ? error : this.withAddress(error, targetOf(datagrams[index]!)))
824
+ if (parked) fiber.evaluate(result as any)
825
+ })
826
+ if (result !== undefined) return result
827
+ parked = true
828
+ return fiber.yieldWith(() => {
829
+ parked = false
830
+ }) as any
831
+ }
832
+
833
+ // Returns `rejected` on failure, with the error stored in `rejection`.
834
+ destination(target: NetAddress.InetAddress | DatagramImpl | undefined): BackingAddress | undefined {
835
+ const handle = this.handle!
836
+ if (target === undefined) {
837
+ if (handle.connected) return undefined
838
+ return handle.peer ?? this.reject("DatagramSocket write has no destination and the socket has no peer")
839
+ }
840
+ if (!("_tag" in target)) return handle.connected ? undefined : target as DatagramImpl
841
+ if (handle.connected) return this.reject("an explicit address cannot be used on a connected DatagramSocket", target)
842
+ if (target === this.lastTarget) return this.lastDestination
843
+ const destination = { host: NetAddress.formatNativeHost(target, this.scopeIds), port: target.port }
844
+ this.lastTarget = target
845
+ this.lastDestination = destination
846
+ return destination
847
+ }
848
+
849
+ reject(message: string, address?: NetAddress.InetAddress): BackingAddress {
850
+ this.rejection = new DatagramSocketError({
851
+ reason: new DatagramSocketWriteError({ kind: "Unknown", address, cause: new Error(message) })
852
+ })
853
+ return rejected
854
+ }
855
+
856
+ withAddress(
857
+ error: DatagramSocketError,
858
+ target: NetAddress.InetAddress | DatagramImpl | undefined
859
+ ): DatagramSocketError {
860
+ const reason = error.reason
861
+ if (reason._tag !== "DatagramSocketWriteError" || reason.address !== undefined) return error
862
+ const address = this.addressOf(target)
863
+ if (address === undefined) return error
864
+ return new DatagramSocketError({
865
+ reason: new DatagramSocketWriteError({ kind: reason.kind, address, cause: reason.cause })
866
+ })
867
+ }
868
+
869
+ addressOf(target: NetAddress.InetAddress | DatagramImpl | undefined): NetAddress.InetAddress | undefined {
870
+ try {
871
+ if (target !== undefined) return "_tag" in target ? target : (target as DatagramImpl).address
872
+ const peer = this.handle!.peer
873
+ return peer === undefined
874
+ ? undefined
875
+ : NetAddress.inetAddressFromNativeUnsafe(peer.host, peer.port, this.scopeIds)
876
+ } catch {
877
+ // an unknown IPv6 zone leaves the address out
878
+ return undefined
879
+ }
880
+ }
881
+ }
882
+
883
+ // Park the fiber itself and resume it inline, avoiding `Effect.callback` allocations.
884
+ const makePull = (state: ReaderState): Reader["pull"] =>
885
+ withFiber((fiber): any => {
886
+ if (state.buffer.length !== 0) {
887
+ const batch = state.take()
888
+ const cont = fiber.getCont(contA)
889
+ return cont ? cont[contA](batch, fiber) : fiber.yieldWith(exitSucceed(batch))
890
+ }
891
+ if (state.failure !== undefined) return state.failure
892
+ state.park(fiber)
893
+ fiber._stack.push(state.unpark)
894
+ return fiber.yieldWith(constVoid)
895
+ })
896
+
897
+ // `contAll` removes interrupted waiters too; after a wake, removal is a no-op.
898
+ const unpark: (state: ReaderState) => Primitive = makePrimitive({
899
+ op: "DatagramSocketUnpark",
900
+ [contAll](fiber) {
901
+ this[args].removeWaiter(fiber)
902
+ }
903
+ })
904
+
905
+ const makeReader = (state: ReaderState): Reader => ({
906
+ pull: makePull(state),
907
+ get address() {
908
+ return state.address
909
+ },
910
+ dropped: () => state.dropped,
911
+ joinMulticast: (options) =>
912
+ Effect.suspend(() => {
913
+ if (state.failure !== undefined) return state.failure
914
+ return Effect.acquireRelease(
915
+ state.handle!.joinMulticast(options),
916
+ (leave) => state.closed ? Effect.void : Effect.ignoreCause(leave())
917
+ )
918
+ })
919
+ })