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
@@ -86,6 +86,10 @@ export interface Residency {
86
86
  readonly releaseUnsafe: () => void
87
87
  }
88
88
 
89
+ interface RequestTransaction {
90
+ settle?: ((outcome: Exit.Exit<unknown, unknown>) => Effect.Effect<void>) | undefined
91
+ }
92
+
89
93
  // Represents the entities managed by this entity manager
90
94
  /**
91
95
  * @internal
@@ -100,10 +104,13 @@ export type EntityState = {
100
104
  /** Excludes requests awaiting their first dispatch from replay. */
101
105
  delivered: boolean
102
106
  sentExit: boolean
107
+ /** Treat early termination interrupts like shutdown interrupts. */
108
+ terminating?: boolean | undefined
103
109
  lastSentChunk: Option.Option<Reply.Chunk<Rpc.Any>>
104
110
  sequence: number
105
111
  /** Set when the request should not outlive its caller. */
106
112
  callerScope?: Scope.Scope | undefined
113
+ transaction?: RequestTransaction | undefined
107
114
  }>
108
115
  lastActiveCheck: number
109
116
  write: RpcServer.RpcServer<any>["write"]
@@ -152,9 +159,13 @@ export const make = Effect.fnUntraced(function*<
152
159
  entityRpcs.set(KeepAliveRpc._tag, KeepAliveRpc as any)
153
160
 
154
161
  const activeServers = new Map<EntityId, EntityState>()
162
+ // Entities finishing their in-flight requests after removal, which still
163
+ // accept interrupts and acks
164
+ const drainingServers = new Map<EntityId, EntityState>()
155
165
  const serverCloseLatches = new Map<EntityAddress, {
156
166
  readonly closed: Latch.Latch
157
167
  readonly force: Latch.Latch
168
+ closing: boolean
158
169
  }>()
159
170
  const processedRequestIds = new Set<Snowflake.Snowflake>()
160
171
 
@@ -181,7 +192,8 @@ export const make = Effect.fnUntraced(function*<
181
192
  const keepAliveLatch = Latch.makeUnsafe()
182
193
  const closeLatches = {
183
194
  closed: Latch.makeUnsafe(),
184
- force: Latch.makeUnsafe()
195
+ force: Latch.makeUnsafe(),
196
+ closing: false
185
197
  }
186
198
 
187
199
  yield* Scope.addFinalizer(
@@ -264,22 +276,23 @@ export const make = Effect.fnUntraced(function*<
264
276
  // They will be retried when the entity is restarted.
265
277
  // Also, if the request is uninterruptible, we ignore the
266
278
  // interrupt.
279
+ const persisted = storageEnabled && Context.get(request.message.annotations, Persisted)
267
280
  if (
268
- storageEnabled &&
269
- Context.get(request.message.annotations, Persisted) &&
281
+ persisted &&
270
282
  Exit.hasInterrupts(response.exit) &&
271
- (isShuttingDown || isUninterruptibleForServer(request.message.annotations))
283
+ (isShuttingDown || request.terminating || isUninterruptibleForServer(request.message.annotations))
272
284
  ) {
273
- if (!isShuttingDown) {
285
+ if (!isShuttingDown && !request.terminating) {
274
286
  request.sentExit = false
275
287
  return server.write(0, requestEnvelope(request), requestWriteOptions(request)).pipe(
288
+ Effect.setContext(handlerContext),
276
289
  Effect.forkIn(handlerScope)
277
290
  )
278
291
  }
279
292
  activeRequests.delete(Snowflake.Snowflake(response.requestId))
280
293
  return options.storage.unregisterReplyHandler(request.message.envelope.requestId)
281
294
  }
282
- return retryRespond(
295
+ const save = retryRespond(
283
296
  4,
284
297
  Effect.suspend(() =>
285
298
  request.message.respond(
@@ -290,22 +303,53 @@ export const make = Effect.fnUntraced(function*<
290
303
  })
291
304
  )
292
305
  )
293
- ).pipe(
294
- Effect.flatMap(() => {
295
- if (storageEnabled) {
296
- processedRequestIds.add(request.message.envelope.requestId)
297
- }
298
- activeRequests.delete(Snowflake.Snowflake(response.requestId))
306
+ )
307
+ const complete = Effect.sync(() => {
308
+ if (storageEnabled) {
309
+ processedRequestIds.add(request.message.envelope.requestId)
310
+ }
311
+ activeRequests.delete(Snowflake.Snowflake(response.requestId))
299
312
 
300
- // Start the idle timer when the last request completes.
301
- if (activeRequests.size === 0) {
302
- state.lastActiveCheck = clock.currentTimeMillisUnsafe()
303
- }
313
+ // Start the idle timer when the last request completes.
314
+ if (activeRequests.size === 0) {
315
+ state.lastActiveCheck = clock.currentTimeMillisUnsafe()
316
+ }
317
+ })
318
+ const respond = Effect.orDie(Effect.andThen(save, complete))
319
+ const transaction = request.transaction
320
+ if (!transaction) return respond
321
+ const exit = response.exit
322
+
323
+ if (!persisted) {
324
+ if (Exit.isSuccess(exit)) return respond
325
+ transaction.settle = () => respond
326
+ return Effect.void
327
+ }
304
328
 
305
- return Effect.void
306
- }),
307
- Effect.orDie
308
- )
329
+ if (Exit.isSuccess(exit)) {
330
+ transaction.settle = (outcome) =>
331
+ Exit.isSuccess(outcome) ? complete : Effect.flatMap(
332
+ hasStoredExit(request.message.envelope.requestId),
333
+ (committed) => {
334
+ if (!committed) return restartFrom(outcome.cause)
335
+ // Commit succeeded despite the error; let callers read the saved reply.
336
+ return Effect.andThen(
337
+ complete,
338
+ options.storage.unregisterReplyHandler(request.message.envelope.requestId)
339
+ )
340
+ }
341
+ )
342
+ return Effect.orDie(save)
343
+ }
344
+ transaction.settle = (outcome) =>
345
+ // Transaction wrappers can add defects, but not typed errors; interrupts may be repeated.
346
+ Exit.isSuccess(outcome) || !outcome.cause.reasons.some((reason) =>
347
+ Cause.isDieReason(reason) &&
348
+ !exit.cause.reasons.some((r) => Cause.isDieReason(r) && r.defect === reason.defect)
349
+ )
350
+ ? respond
351
+ : restartFrom(outcome.cause)
352
+ return Effect.void
309
353
  }
310
354
  case "Chunk": {
311
355
  const request = activeRequests.get(Snowflake.Snowflake(response.requestId))
@@ -330,10 +374,7 @@ export const make = Effect.fnUntraced(function*<
330
374
  ))
331
375
  }
332
376
  case "Defect": {
333
- if (!isActive()) return endLatch.open
334
- const rebuild = writeRef.rebuildUnsafe({ from: server.write, prepare: replay })
335
- if (!rebuild) return Effect.void
336
- return Effect.forkIn(restart(Cause.die(response.defect), rebuild), managerScope)
377
+ return restartFrom(Cause.die(response.defect))
337
378
  }
338
379
  case "ClientEnd": {
339
380
  return endLatch.open
@@ -345,6 +386,21 @@ export const make = Effect.fnUntraced(function*<
345
386
  Effect.setContext(Context.merge(handlerContext, handlers))
346
387
  )
347
388
 
389
+ // Replay if the stored reply cannot be read.
390
+ const hasStoredExit = (requestId: Snowflake.Snowflake): Effect.Effect<boolean> =>
391
+ options.storage.repliesForUnfiltered([requestId]).pipe(
392
+ Effect.map((replies) => replies.some((reply) => reply._tag === "WithExit")),
393
+ Effect.catchCause(() => Effect.succeed(false))
394
+ )
395
+
396
+ // Do not inherit the failed handler's transaction context.
397
+ const restartFrom = (cause: Cause.Cause<unknown>): Effect.Effect<void> => {
398
+ if (!isActive()) return endLatch.open
399
+ const rebuild = writeRef.rebuildUnsafe({ from: server.write, prepare: replay })
400
+ if (!rebuild) return Effect.void
401
+ return Effect.forkIn(Effect.setContext(restart(cause, rebuild), handlerContext), managerScope)
402
+ }
403
+
348
404
  yield* Scope.addFinalizer(
349
405
  handlerScope,
350
406
  Effect.sync(() => {
@@ -415,15 +471,32 @@ export const make = Effect.fnUntraced(function*<
415
471
  Effect.suspend(() => {
416
472
  activeServers.delete(address.entityId)
417
473
  retired.openUnsafe()
474
+ closeLatches.closing = true
418
475
  acquireEntity(address)
476
+ drainingServers.set(address.entityId, state)
419
477
  return Effect.raceFirst(
420
- state.write(0, { _tag: "Eof" }).pipe(
478
+ Effect.gen(function*() {
479
+ for (const [requestId, request] of activeRequests) {
480
+ if (
481
+ !request.delivered || request.sentExit ||
482
+ !Context.get(request.message.annotations, ClusterSchema.InterruptOnTermination) ||
483
+ isUninterruptibleForServer(request.message.annotations)
484
+ ) continue
485
+ request.terminating = true
486
+ yield* state.write(0, { _tag: "Interrupt", requestId: requestId as any, interruptors: [] })
487
+ }
488
+ yield* state.write(0, { _tag: "Eof" })
489
+ }).pipe(
421
490
  Effect.andThen(endLatch.await),
422
491
  Effect.timeoutOption(config.entityTerminationTimeout),
423
492
  Effect.interruptible
424
493
  ),
425
494
  Effect.interruptible(closeLatches.force.await)
426
- )
495
+ ).pipe(Effect.ensuring(Effect.sync(() => {
496
+ if (drainingServers.get(address.entityId) === state) {
497
+ drainingServers.delete(address.entityId)
498
+ }
499
+ })))
427
500
  })
428
501
  )
429
502
  if (!options.sharding.hasShardId(address.shardId)) {
@@ -458,9 +531,38 @@ export const make = Effect.fnUntraced(function*<
458
531
  Effect.forkIn(managerScope)
459
532
  )
460
533
 
534
+ function sendEnvelope(server: EntityState, message: Message.IncomingEnvelope): Effect.Effect<void> {
535
+ const entry = server.activeRequests.get(message.envelope.requestId)
536
+ if (!entry) {
537
+ return Effect.void
538
+ } else if (
539
+ message.envelope._tag === "AckChunk" &&
540
+ Option.isSome(entry.lastSentChunk) &&
541
+ message.envelope.replyId !== entry.lastSentChunk.value.id
542
+ ) {
543
+ return Effect.void
544
+ }
545
+ return server.write(
546
+ 0,
547
+ message.envelope._tag === "AckChunk"
548
+ ? { _tag: "Ack", requestId: message.envelope.requestId as any }
549
+ : {
550
+ _tag: "Interrupt",
551
+ requestId: message.envelope.requestId as any,
552
+ interruptors: []
553
+ }
554
+ )
555
+ }
556
+
461
557
  function sendLocal<R extends Rpc.Any>(
462
558
  message: Message.IncomingLocal<R>
463
559
  ): Effect.Effect<void, EntityNotAssignedToRunner | MailboxFull | AlreadyProcessingMessage> {
560
+ if (message._tag === "IncomingEnvelope") {
561
+ const draining = drainingServers.get(message.envelope.address.entityId)
562
+ if (draining?.activeRequests.has(message.envelope.requestId)) {
563
+ return sendEnvelope(draining, message)
564
+ }
565
+ }
464
566
  return Effect.provideService(
465
567
  Effect.flatMap(
466
568
  entities.get(message.envelope.address),
@@ -557,26 +659,7 @@ export const make = Effect.fnUntraced(function*<
557
659
  return server.write(0, requestEnvelope(entry), requestWriteOptions(entry))
558
660
  }
559
661
  case "IncomingEnvelope": {
560
- const entry = server.activeRequests.get(message.envelope.requestId)
561
- if (!entry) {
562
- return Effect.void
563
- } else if (
564
- message.envelope._tag === "AckChunk" &&
565
- Option.isSome(entry.lastSentChunk) &&
566
- message.envelope.replyId !== entry.lastSentChunk.value.id
567
- ) {
568
- return Effect.void
569
- }
570
- return server.write(
571
- 0,
572
- message.envelope._tag === "AckChunk"
573
- ? { _tag: "Ack", requestId: message.envelope.requestId as any }
574
- : {
575
- _tag: "Interrupt",
576
- requestId: message.envelope.requestId as any,
577
- interruptors: []
578
- }
579
- )
662
+ return sendEnvelope(server, message)
580
663
  }
581
664
  }
582
665
  }
@@ -596,11 +679,24 @@ export const make = Effect.fnUntraced(function*<
596
679
  })
597
680
 
598
681
  const requestWriteOptions = (
599
- entry: { readonly message: Message.IncomingRequestLocal<any>; readonly callerScope?: Scope.Scope | undefined }
682
+ entry: {
683
+ readonly message: Message.IncomingRequestLocal<any>
684
+ readonly callerScope?: Scope.Scope | undefined
685
+ transaction?: RequestTransaction | undefined
686
+ }
600
687
  ): Parameters<EntityState["write"]>[2] => {
601
- const onTransaction = Context.get(entry.message.annotations, WithTransaction)
602
- ? options.storage.withTransaction
603
- : undefined
688
+ const onTransaction = !Context.get(entry.message.annotations, WithTransaction)
689
+ ? undefined
690
+ : <A, E, R>(effect: Effect.Effect<A, E, R>) =>
691
+ // Interrupts during commit must not hide the outcome from settle.
692
+ Effect.uninterruptibleMask((restore) => {
693
+ const transaction: RequestTransaction = {}
694
+ entry.transaction = transaction
695
+ return Effect.onExit(options.storage.withTransaction(restore(effect)), (outcome) => {
696
+ if (entry.transaction === transaction) entry.transaction = undefined
697
+ return transaction.settle ? transaction.settle(outcome) : Effect.void
698
+ })
699
+ })
604
700
  const onCaller = entry.callerScope && bindToCaller(entry.callerScope)
605
701
  if (!onCaller) return onTransaction && { onRequest: onTransaction }
606
702
  return { onRequest: onTransaction ? (effect) => onCaller(onTransaction(effect)) : onCaller }
@@ -621,13 +717,13 @@ export const make = Effect.fnUntraced(function*<
621
717
  }
622
718
  })
623
719
  }
624
- activeServers.forEach((state) => {
625
- if (shardId[Equal.symbol](state.address.shardId)) {
626
- fibers.push(runFork(entities.removeIgnore(state.address)))
627
- }
628
- })
720
+ // Look entities up by address: `activeServers` is keyed by entity id,
721
+ // so an entity with the same id on another shard can hide this one.
629
722
  serverCloseLatches.forEach((latches, address) => {
630
723
  if (shardId[Equal.symbol](address.shardId)) {
724
+ if (!latches.closing) {
725
+ fibers.push(runFork(entities.removeIgnore(address)))
726
+ }
631
727
  fibers.push(runFork(latches.closed.await))
632
728
  }
633
729
  })
@@ -92,6 +92,12 @@ export interface DecodeOptions {
92
92
  * Maximum number of string code units retained for a pending event. The default is 10 MiB.
93
93
  */
94
94
  readonly maxEventSize?: number | undefined
95
+ /**
96
+ * Also dispatch an event that sets an `event:` name but has no `data:`
97
+ * line, with empty `data`. The WHATWG algorithm drops such events, but some
98
+ * protocols use them, such as graphql-sse's `complete`. Defaults to `false`.
99
+ */
100
+ readonly dispatchEmptyNamedEvents?: boolean | undefined
95
101
  }
96
102
 
97
103
  const defaultMaxEventSize = 10 * 1024 * 1024
@@ -270,13 +276,16 @@ export const decodeDataSchema = <Type, DecodingServices, IE, Done>(
270
276
  */
271
277
  export function makeParser(onParse: (event: AnyEvent) => void, options?: DecodeOptions): Parser {
272
278
  const maxEventSize = options?.maxEventSize ?? defaultMaxEventSize
279
+ const dispatchEmptyNamedEvents = options?.dispatchEmptyNamedEvents === true
273
280
 
274
281
  // Processing state
275
282
  let isFirstChunk: boolean
276
- let buffer: string
277
- let startingPosition: number
278
- let startingFieldLength: number
283
+ // Pieces of the unterminated line, joined once when the line ends
284
+ let pending: Array<string>
285
+ let pendingLength: number
279
286
  let discardTrailingNewline: boolean
287
+ // Incremented by `reset`, so `feed` can stop if `onParse` resets the parser
288
+ let generation = 0
280
289
 
281
290
  // Event state
282
291
  let lastEventId: string | undefined
@@ -287,10 +296,10 @@ export function makeParser(onParse: (event: AnyEvent) => void, options?: DecodeO
287
296
  return { feed, reset }
288
297
 
289
298
  function reset(): void {
299
+ generation++
290
300
  isFirstChunk = true
291
- buffer = ""
292
- startingPosition = 0
293
- startingFieldLength = -1
301
+ pending = []
302
+ pendingLength = 0
294
303
  discardTrailingNewline = false
295
304
 
296
305
  lastEventId = undefined
@@ -299,120 +308,98 @@ export function makeParser(onParse: (event: AnyEvent) => void, options?: DecodeO
299
308
  }
300
309
 
301
310
  function feed(chunk: string): SseError | undefined {
302
- buffer = buffer ? buffer + chunk : chunk
303
-
304
311
  // Strip any UTF-8 byte order mark (BOM) at the start of the stream.
305
312
  // Note that we do not strip any non - UTF8 BOM, as eventsource streams are
306
313
  // always decoded as UTF8 as per the specification.
307
- if (isFirstChunk && buffer.startsWith(BOM)) {
308
- buffer = buffer.slice(BOM.length)
314
+ if (isFirstChunk && chunk.startsWith(BOM)) {
315
+ chunk = chunk.slice(BOM.length)
309
316
  }
310
317
 
311
318
  isFirstChunk = false
312
319
 
313
- // Set up chunk-specific processing state
314
- const length = buffer.length
320
+ const length = chunk.length
321
+ const currentGeneration = generation
315
322
  let position = 0
316
323
 
317
- // Read the current buffer byte by byte
318
324
  while (position < length) {
319
- // EventSource allows for carriage return + line feed, which means we
320
- // need to ignore a linefeed character if the previous character was a
321
- // carriage return
322
- // @todo refactor to reduce nesting, consider checking previous byte?
323
- // @todo but consider multiple chunks etc
325
+ // Skip the LF of a CRLF, whose CR may have ended the previous chunk
324
326
  if (discardTrailingNewline) {
325
- if (buffer[position] === "\n") {
327
+ discardTrailingNewline = false
328
+ if (chunk.charCodeAt(position) === LF) {
326
329
  ++position
330
+ continue
327
331
  }
328
- discardTrailingNewline = false
329
332
  }
330
333
 
331
- let lineLength = -1
332
- let fieldLength = startingFieldLength
333
- let character: string
334
-
335
- for (let index = position + startingPosition; lineLength < 0 && index < length; ++index) {
336
- character = buffer[index]
337
- if (character === ":" && fieldLength < 0) {
338
- fieldLength = index - position
339
- } else if (character === "\r") {
340
- discardTrailingNewline = true
341
- lineLength = index - position
342
- } else if (character === "\n") {
343
- lineLength = index - position
344
- }
334
+ let lineEnd = position
335
+ while (lineEnd < length) {
336
+ const code = chunk.charCodeAt(lineEnd)
337
+ if (code === CR || code === LF) break
338
+ ++lineEnd
345
339
  }
346
340
 
347
- if (lineLength < 0) {
348
- startingPosition = length - position
349
- startingFieldLength = fieldLength
341
+ if (lineEnd === length) {
342
+ pending.push(chunk.slice(position))
343
+ pendingLength += length - position
350
344
  break
351
- } else {
352
- startingPosition = 0
353
- startingFieldLength = -1
354
345
  }
355
346
 
356
- parseEventStreamLine(buffer, position, fieldLength, lineLength)
347
+ discardTrailingNewline = chunk.charCodeAt(lineEnd) === CR
357
348
 
358
- position += lineLength + 1
359
- }
349
+ let line = chunk.slice(position, lineEnd)
350
+ if (pending.length > 0) {
351
+ pending.push(line)
352
+ line = pending.join("")
353
+ pending = []
354
+ pendingLength = 0
355
+ }
356
+ parseLine(line)
357
+
358
+ if (generation !== currentGeneration) {
359
+ // `onParse` reset the parser, so drop the rest of this chunk
360
+ return
361
+ }
360
362
 
361
- if (position === length) {
362
- // If we consumed the entire buffer to read the event, reset the buffer
363
- buffer = ""
364
- } else if (position > 0) {
365
- // If there are bytes left to process, set the buffer to the unprocessed
366
- // portion of the buffer only
367
- buffer = buffer.slice(position)
363
+ position = lineEnd + 1
368
364
  }
369
365
 
370
- if (buffer.length + data.length > maxEventSize) {
366
+ if (pendingLength + data.length > maxEventSize) {
371
367
  const error = new SseError({ reason: new EventTooLarge({ maxEventSize }) })
372
368
  reset()
373
369
  return error
374
370
  }
375
371
  }
376
372
 
377
- function parseEventStreamLine(
378
- lineBuffer: string,
379
- index: number,
380
- fieldLength: number,
381
- lineLength: number
382
- ) {
383
- if (lineLength === 0) {
384
- // We reached the last line of this event
385
- if (data.length > 0) {
373
+ function parseLine(line: string) {
374
+ if (line.length === 0) {
375
+ // End of event. Clear the state first so a throwing `onParse` cannot
376
+ // leak it into the next event
377
+ const named = eventName !== undefined && eventName !== ""
378
+ const event = eventName || "message"
379
+ const eventData = data
380
+ eventName = undefined
381
+ data = ""
382
+ if (eventData.length > 0 || (dispatchEmptyNamedEvents && named)) {
386
383
  onParse({
387
384
  _tag: "Event",
388
385
  id: lastEventId,
389
- event: eventName || "message",
390
- data: data.slice(0, -1) // remove trailing newline
386
+ event,
387
+ data: eventData.slice(0, -1) // remove trailing newline
391
388
  })
392
- data = ""
393
389
  }
394
- eventName = undefined
395
390
  return
396
391
  }
397
392
 
398
- const noValue = fieldLength < 0
399
- const field = lineBuffer.slice(index, index + (noValue ? lineLength : fieldLength))
400
- let step = 0
401
-
402
- if (noValue) {
403
- step = lineLength
404
- } else if (lineBuffer[index + fieldLength + 1] === " ") {
405
- step = fieldLength + 2
406
- } else {
407
- step = fieldLength + 1
393
+ const colon = line.indexOf(":")
394
+ let field = line
395
+ let value = ""
396
+ if (colon >= 0) {
397
+ field = line.slice(0, colon)
398
+ value = line.slice(line.charCodeAt(colon + 1) === SPACE ? colon + 2 : colon + 1)
408
399
  }
409
400
 
410
- const position = index + step
411
- const valueLength = lineLength - step
412
- const value = lineBuffer.slice(position, position + valueLength).toString()
413
-
414
401
  if (field === "data") {
415
- data += value ? `${value}\n` : "\n"
402
+ data += value + "\n"
416
403
  } else if (field === "event") {
417
404
  eventName = value
418
405
  } else if (field === "id" && !value.includes("\u0000")) {
@@ -425,6 +412,9 @@ export function makeParser(onParse: (event: AnyEvent) => void, options?: DecodeO
425
412
  }
426
413
 
427
414
  const BOM = "\uFEFF"
415
+ const LF = 10
416
+ const CR = 13
417
+ const SPACE = 32
428
418
 
429
419
  /**
430
420
  * Stateful Server-Sent Events parser returned by `makeParser`.