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,719 @@
1
+ /**
2
+ * Transports for `GraphQLClient`. They accept encoded requests and return raw
3
+ * response bodies with available HTTP status and headers, without schema
4
+ * decoding or middleware.
5
+ *
6
+ * - {@link layerHttp} uses HTTP POST (or GET for queries) and graphql-sse
7
+ * subscriptions, with an optional graphql-ws subscription transport.
8
+ * - {@link layerWebSocket} multiplexes all operations over graphql-ws.
9
+ *
10
+ * {@link makeHttp} and {@link makeWebSocket} construct the same transports as effects.
11
+ *
12
+ * @stability experimental
13
+ * @since 4.0.0
14
+ */
15
+ import * as Cause from "../Cause.js";
16
+ import * as Clock from "../Clock.js";
17
+ import * as Context from "../Context.js";
18
+ import * as Deferred from "../Deferred.js";
19
+ import * as Duration from "../Duration.js";
20
+ import * as Effect from "../Effect.js";
21
+ import * as Sse from "../encoding/Sse.js";
22
+ import * as Fiber from "../Fiber.js";
23
+ import * as Headers from "../http/Headers.js";
24
+ import * as HttpClient from "../http/HttpClient.js";
25
+ import * as HttpClientError from "../http/HttpClientError.js";
26
+ import * as HttpClientRequest from "../http/HttpClientRequest.js";
27
+ import * as HttpClientResponse from "../http/HttpClientResponse.js";
28
+ import * as UrlParams from "../http/UrlParams.js";
29
+ import * as internalGraphQL from "../internal/graphql.js";
30
+ import { HeadersSchema } from "../internal/graphqlHeaders.js";
31
+ import * as Layer from "../Layer.js";
32
+ import * as Queue from "../Queue.js";
33
+ import * as Result from "../Result.js";
34
+ import * as Schema from "../Schema.js";
35
+ import * as Socket from "../socket/Socket.js";
36
+ import * as Stream from "../Stream.js";
37
+ import { GraphQLError, TransportError } from "./GraphQLClientError.js";
38
+ /**
39
+ * A raw GraphQL result before schema decoding. Middleware receive it from
40
+ * `next` and can read response `extensions`, such as rate-limit costs.
41
+ *
42
+ * **Details**
43
+ *
44
+ * `status` and `headers` are transport metadata, not part of the GraphQL
45
+ * response body: the client decodes the body without them and then copies
46
+ * what the transport reported. Middleware may read or rewrite them, and a
47
+ * failed call's `ResponseError` carries the final values. Headers use
48
+ * `Headers.Headers` at runtime and encode as a plain string record.
49
+ *
50
+ * @stability experimental
51
+ * @category schemas
52
+ * @since 4.0.0
53
+ */
54
+ export const ExecutionResult = /*#__PURE__*/Schema.Struct({
55
+ data: /*#__PURE__*/Schema.optional(Schema.Unknown),
56
+ errors: /*#__PURE__*/Schema.optional(/*#__PURE__*/Schema.Array(GraphQLError)),
57
+ extensions: /*#__PURE__*/Schema.optional(/*#__PURE__*/Schema.Record(Schema.String, Schema.Json)),
58
+ status: /*#__PURE__*/Schema.optional(Schema.Int),
59
+ headers: /*#__PURE__*/Schema.optional(HeadersSchema)
60
+ });
61
+ /**
62
+ * The transport service. `execute` answers a query or mutation with the raw
63
+ * JSON body, and `subscribe` turns a subscription into a stream of raw events.
64
+ * Both fail only with `TransportError`; GraphQL `errors` are left in the body
65
+ * for the client to interpret.
66
+ *
67
+ * **Details**
68
+ *
69
+ * Each answer is `{ body, status?, headers? }`. The HTTP transport sets
70
+ * `headers` on every answer, including graphql-sse events, and `status` on
71
+ * all but graphql-sse events. Headers are `Headers.Headers` values.
72
+ * graphql-ws leaves both unset.
73
+ *
74
+ * **Example** (A scripted transport for tests)
75
+ *
76
+ * ```ts import.meta.vitest
77
+ * import { Effect, Layer, Schema, Stream } from "effect"
78
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
79
+ *
80
+ * const Viewer = GraphQL.query("Viewer", {
81
+ * document: "query Viewer{viewer{login}}",
82
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
83
+ * })
84
+ *
85
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
86
+ * execute: () => Effect.succeed({ body: { data: { viewer: { login: "tim" } } } }),
87
+ * subscribe: () => Stream.empty
88
+ * })
89
+ *
90
+ * const program = Effect.gen(function*() {
91
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer))
92
+ * return yield* client.Viewer()
93
+ * })
94
+ *
95
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => { viewer: { login: "tim" } }
96
+ * ```
97
+ *
98
+ * @stability experimental
99
+ * @category services
100
+ * @since 4.0.0
101
+ */
102
+ export class GraphQLProtocol extends /*#__PURE__*/Context.Service()("effect/graphql/GraphQLProtocol") {}
103
+ const graphqlResponseContentType = "application/graphql-response+json";
104
+ const jsonContentType = "application/json";
105
+ const eventStreamContentType = "text/event-stream";
106
+ const isObject = u => typeof u === "object" && u !== null && !Array.isArray(u);
107
+ // A gateway's `{ "errors": "Throttled" }` is not a GraphQL response, so its
108
+ // status and Retry-After still apply.
109
+ const isGraphQLBody = body => isObject(body) && ("data" in body || "errors" in body) && (!("errors" in body) || Array.isArray(body.errors)) && (!("data" in body) || body.data === null || isObject(body.data));
110
+ // The JSON body of a POST and the payload of a graphql-ws `subscribe`.
111
+ // `extensions` is dropped from the JSON when unset.
112
+ const requestBody = request => ({
113
+ query: request.query,
114
+ operationName: request.operationName,
115
+ variables: request.variables,
116
+ extensions: request.extensions
117
+ });
118
+ const postRequest = (url, request, accept) => HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(request.headers), HttpClientRequest.setHeader("accept", accept), HttpClientRequest.appendUrlParams(request.urlParams), HttpClientRequest.bodyJsonUnsafe(requestBody(request)));
119
+ const graphqlGetParams = /*#__PURE__*/new Set(["query", "operationName", "variables", "extensions"]);
120
+ // Remove all protocol params from the configured URL and caller params before
121
+ // adding the request's values, including when optional fields are unset. Split
122
+ // the URL without resolving it so relative URLs remain supported.
123
+ const getRequest = (url, request, accept) => {
124
+ const hashIndex = url.indexOf("#");
125
+ const hash = hashIndex === -1 ? "" : url.slice(hashIndex);
126
+ const withoutHash = hashIndex === -1 ? url : url.slice(0, hashIndex);
127
+ const queryIndex = withoutHash.indexOf("?");
128
+ const baseUrl = queryIndex === -1 ? withoutHash : withoutHash.slice(0, queryIndex);
129
+ const urlParams = [...UrlParams.fromInput(new URLSearchParams(queryIndex === -1 ? "" : withoutHash.slice(queryIndex + 1))), ...request.urlParams].filter(([key]) => !graphqlGetParams.has(key));
130
+ const params = {
131
+ query: request.query,
132
+ operationName: request.operationName
133
+ };
134
+ if (request.variables !== undefined) params.variables = JSON.stringify(request.variables);
135
+ if (request.extensions !== undefined) params.extensions = JSON.stringify(request.extensions);
136
+ return HttpClientRequest.get(baseUrl + hash).pipe(HttpClientRequest.setHeaders(request.headers), HttpClientRequest.setHeader("accept", accept), HttpClientRequest.appendUrlParams(urlParams), HttpClientRequest.setUrlParams(params));
137
+ };
138
+ // Reads the clock when the error arrives, so an HTTP-date `Retry-After` is
139
+ // measured from when the response was received.
140
+ const fromHttpClientError = clock => effect => Effect.catch(effect, error => Effect.fail(HttpClientError.isHttpClientError(error) ? TransportError.fromHttpClientError(error, {
141
+ now: clock.currentTimeMillisUnsafe()
142
+ }) : error));
143
+ // Media types are case-insensitive.
144
+ const contentTypeOf = response => (response.headers["content-type"] ?? "").toLowerCase();
145
+ const readResponse = response => {
146
+ const contentType = contentTypeOf(response);
147
+ // The GraphQL-over-HTTP media type is authoritative, whatever the status.
148
+ if (contentType.includes(graphqlResponseContentType)) {
149
+ return response.json;
150
+ }
151
+ // Legacy servers answer with application/json; only a body with the
152
+ // GraphQL keys counts as a GraphQL response.
153
+ if (contentType.includes(jsonContentType)) {
154
+ return Effect.flatMap(response.json, body => isGraphQLBody(body) ? Effect.succeed(body) : Effect.as(HttpClientResponse.filterStatusOk(response), body));
155
+ }
156
+ return Effect.flatMap(HttpClientResponse.filterStatusOk(response), () => Effect.fail(new TransportError({
157
+ description: `Expected a GraphQL response, received ${contentType === "" ? "no Content-Type" : `Content-Type ${contentType}`}`,
158
+ status: response.status,
159
+ headers: response.headers
160
+ })));
161
+ };
162
+ const readAnswer = response => Effect.map(readResponse(response), body => ({
163
+ body,
164
+ status: response.status,
165
+ headers: response.headers
166
+ }));
167
+ const errorMessage = cause => cause instanceof Error ? cause.message : String(cause);
168
+ // The graphql-sse distinct-mode events of a `text/event-stream` body. The
169
+ // stream ends on a `complete` event, with or without a `data:` line. A body
170
+ // that ends without one means the connection was lost, so it fails with a
171
+ // retryable `TransportError`. A `retry:` line is kept and reported as
172
+ // `retryAfter` if the body fails or ends early.
173
+ const sseEvents = response => Stream.suspend(() => {
174
+ const headers = response.headers;
175
+ let retryAfter;
176
+ let complete = false;
177
+ let events = [];
178
+ const parser = Sse.makeParser(event => {
179
+ if (event._tag === "Retry") {
180
+ retryAfter = event.duration;
181
+ } else {
182
+ events.push(event);
183
+ }
184
+ }, {
185
+ dispatchEmptyNamedEvents: true
186
+ });
187
+ return response.stream.pipe(Stream.decodeText(), Stream.mapError(cause => new TransportError({
188
+ description: `The graphql-sse stream failed: ${cause.message}`,
189
+ retryAfter,
190
+ cause
191
+ })), Stream.mapEffect(text => {
192
+ const error = parser.feed(text);
193
+ if (error !== undefined) {
194
+ return Effect.fail(new TransportError({
195
+ description: error.message,
196
+ cause: error
197
+ }));
198
+ }
199
+ const batch = events;
200
+ events = [];
201
+ return Effect.succeed(batch);
202
+ }), Stream.flattenIterable, Stream.takeWhile(event => {
203
+ if (event.event === "complete") complete = true;
204
+ return !complete;
205
+ }), Stream.filter(event => event.event === "next"), Stream.mapEffect(event => Effect.try({
206
+ try: () => ({
207
+ body: JSON.parse(event.data),
208
+ headers
209
+ }),
210
+ catch: cause => new TransportError({
211
+ description: `Invalid graphql-sse next event: ${errorMessage(cause)}`,
212
+ cause
213
+ })
214
+ })), Stream.concat(Stream.suspend(() => complete ? Stream.empty : Stream.fail(new TransportError({
215
+ description: "The graphql-sse stream ended without a complete event",
216
+ retryAfter
217
+ })))));
218
+ });
219
+ const makeHttpWith = options => {
220
+ const http = Clock.clockWith(clock => HttpClient.HttpClient.useSync(client => {
221
+ const execute = request => Effect.flatMap(Effect.service(internalGraphQL.ForcePost), forcePost => {
222
+ const toRequest = options.queryMethod === "GET" && request.kind === "query" && !forcePost ? getRequest : postRequest;
223
+ return client.execute(toRequest(options.url, request, `${graphqlResponseContentType}, ${jsonContentType}`)).pipe(Effect.flatMap(readAnswer), fromHttpClientError(clock));
224
+ });
225
+ const subscribe = request => Stream.unwrap(Effect.suspend(() => client.execute(postRequest(options.url, request, eventStreamContentType)).pipe(Effect.flatMap(response => {
226
+ const contentType = contentTypeOf(response);
227
+ if (contentType.includes(eventStreamContentType) && response.status >= 200 && response.status < 300) {
228
+ return Effect.succeed(sseEvents(response));
229
+ }
230
+ // A server that rejects the subscription answers with a single
231
+ // GraphQL response, which the client reads like a query's.
232
+ return Effect.map(readAnswer(response), Stream.succeed);
233
+ }), fromHttpClientError(clock))));
234
+ return GraphQLProtocol.of({
235
+ execute,
236
+ subscribe
237
+ });
238
+ }));
239
+ const webSocket = options.subscriptions?.webSocket;
240
+ return webSocket === undefined ? http : Effect.zipWith(http, makeWebSocket(webSocket), (http, ws) => GraphQLProtocol.of({
241
+ execute: http.execute,
242
+ subscribe: ws.subscribe
243
+ }));
244
+ };
245
+ /**
246
+ * Builds the HTTP transport as an effect, for composing transports by hand.
247
+ * Most code uses {@link layerHttp}.
248
+ *
249
+ * **Details**
250
+ *
251
+ * Takes the same options as {@link layerHttp}, including `queryMethod`.
252
+ * Without `subscriptions`, subscriptions use graphql-sse distinct mode on
253
+ * `url`. With `subscriptions.webSocket`, they go over graphql-ws instead, and
254
+ * the effect also builds the graphql-ws transport, so it needs a
255
+ * `Socket.WebSocketConstructor` and a `Scope`.
256
+ *
257
+ * @stability experimental
258
+ * @category constructors
259
+ * @since 4.0.0
260
+ */
261
+ export const makeHttp = makeHttpWith;
262
+ /**
263
+ * The HTTP transport: queries and mutations are sent as `POST` with a JSON
264
+ * body to `url`, using the `HttpClient` in context. Subscriptions use
265
+ * graphql-sse distinct mode on the same URL, or graphql-ws when
266
+ * `subscriptions.webSocket` is set.
267
+ *
268
+ * **Details**
269
+ *
270
+ * With `queryMethod: "GET"` (default `"POST"`), queries are sent as `GET`
271
+ * with no body: `query` and `operationName` become URL params, plus
272
+ * `variables` and `extensions` as JSON when set. Mutations are always
273
+ * `POST`, and so are `GraphQLResolver.aliased` batches. A long query is not
274
+ * retried as `POST`.
275
+ *
276
+ * A request's `headers` and `urlParams`, from the call options or from
277
+ * middleware, are applied to every HTTP request. A query string already on
278
+ * `url` is kept. On `GET`, the request's `urlParams` come before the GraphQL
279
+ * params, which replace any caller param with the same name.
280
+ *
281
+ * Every response's HTTP `status` is passed to the client as
282
+ * `ExecutionResult.status`, and a call that fails with GraphQL `errors`
283
+ * reports it as `ResponseError.status`. Response headers are passed the
284
+ * same way, as `ExecutionResult.headers` and `ResponseError.headers`.
285
+ *
286
+ * A response is a GraphQL response when its `Content-Type` is
287
+ * `application/graphql-response+json`, whatever the status, or when it is
288
+ * `application/json` and the body has a list of `errors` or a `data` object
289
+ * (or `null`). Media types match case-insensitively. Anything else
290
+ * is a `TransportError` with the response `status` and `headers`, and a
291
+ * `Retry-After` header (whole seconds or an HTTP date, measured from when the
292
+ * response arrived) becomes `retryAfter`.
293
+ *
294
+ * A subscription is a `POST` with `Accept: text/event-stream`. Each `next`
295
+ * event is emitted with the response headers but no status, and the stream
296
+ * ends on a `complete` event, with or without a `data:` line. A body that
297
+ * fails, or ends without a `complete` event, is a retryable `TransportError`
298
+ * with a server `retry:` line as its `retryAfter`, so the client's
299
+ * `subscriptionRetry` resubscribes. A non-stream answer, such as a `400` with
300
+ * `errors`, is read like a query's response. Interrupting the stream aborts
301
+ * the request.
302
+ *
303
+ * With `subscriptions: { webSocket }`, subscriptions go over graphql-ws as in
304
+ * {@link layerWebSocket}, and the layer needs a `Socket.WebSocketConstructor`.
305
+ *
306
+ * **Example** (Building a client over HTTP)
307
+ *
308
+ * ```ts import.meta.vitest
309
+ * import { Effect, Layer, Schema } from "effect"
310
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
311
+ * import { HttpClient, HttpClientResponse } from "effect/http"
312
+ *
313
+ * const Viewer = GraphQL.query("Viewer", {
314
+ * document: "query Viewer{viewer{login}}",
315
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
316
+ * })
317
+ *
318
+ * // A stand-in for a real HttpClient layer such as FetchHttpClient.layer
319
+ * const FakeServer = Layer.succeed(
320
+ * HttpClient.HttpClient,
321
+ * HttpClient.make((request) =>
322
+ * Effect.succeed(HttpClientResponse.fromWeb(
323
+ * request,
324
+ * new Response(JSON.stringify({ data: { viewer: { login: "tim" } } }), {
325
+ * headers: { "content-type": "application/graphql-response+json" }
326
+ * })
327
+ * ))
328
+ * )
329
+ * )
330
+ *
331
+ * const program = Effect.gen(function*() {
332
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer))
333
+ * return yield* client.Viewer()
334
+ * }).pipe(
335
+ * Effect.provide(GraphQLProtocol.layerHttp({ url: "https://api.github.com/graphql" })),
336
+ * Effect.provide(FakeServer)
337
+ * )
338
+ *
339
+ * await Effect.runPromise(program) // => { viewer: { login: "tim" } }
340
+ * ```
341
+ *
342
+ * **Example** (Queries over GET with a per-call URL param)
343
+ *
344
+ * ```ts import.meta.vitest
345
+ * import { Effect, Layer, Schema } from "effect"
346
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
347
+ * import { HttpClient, HttpClientResponse } from "effect/http"
348
+ *
349
+ * const Viewer = GraphQL.query("Viewer", {
350
+ * document: "query Viewer{viewer{login}}",
351
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
352
+ * })
353
+ *
354
+ * // A stand-in server that answers with the method and the `preview` param
355
+ * const FakeServer = Layer.succeed(
356
+ * HttpClient.HttpClient,
357
+ * HttpClient.make((request, url) =>
358
+ * Effect.succeed(HttpClientResponse.fromWeb(
359
+ * request,
360
+ * new Response(
361
+ * JSON.stringify({ data: { viewer: { login: `${request.method} ${url.searchParams.get("preview")}` } } }),
362
+ * { headers: { "content-type": "application/graphql-response+json" } }
363
+ * )
364
+ * ))
365
+ * )
366
+ * )
367
+ *
368
+ * const program = Effect.gen(function*() {
369
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer))
370
+ * return yield* client.Viewer(undefined, { urlParams: { preview: "draft-token" } })
371
+ * }).pipe(
372
+ * Effect.provide(GraphQLProtocol.layerHttp({ url: "https://cms.example.com/graphql", queryMethod: "GET" })),
373
+ * Effect.provide(FakeServer)
374
+ * )
375
+ *
376
+ * await Effect.runPromise(program) // => { viewer: { login: "GET draft-token" } }
377
+ * ```
378
+ *
379
+ * **Example** (Subscriptions over graphql-ws, everything else over POST)
380
+ *
381
+ * ```ts import.meta.vitest
382
+ * import { Effect } from "effect"
383
+ * import { GraphQLProtocol } from "effect/graphql"
384
+ *
385
+ * const token = Effect.succeed("secret")
386
+ *
387
+ * // Provide an HttpClient and a Socket.WebSocketConstructor, such as
388
+ * // FetchHttpClient.layer and NodeSocket.layerWebSocketConstructor
389
+ * export const Protocol = GraphQLProtocol.layerHttp({
390
+ * url: "https://example.com/graphql",
391
+ * subscriptions: {
392
+ * webSocket: {
393
+ * url: "wss://example.com/graphql",
394
+ * connectionParams: Effect.map(token, (token) => ({ authorization: `Bearer ${token}` }))
395
+ * }
396
+ * }
397
+ * })
398
+ * ```
399
+ *
400
+ * @stability experimental
401
+ * @category layers
402
+ * @since 4.0.0
403
+ */
404
+ export const layerHttp = options => Layer.effect(GraphQLProtocol, makeHttpWith(options));
405
+ const textDecoder = /*#__PURE__*/new TextDecoder();
406
+ // As in graphql-ws's `validateMessage`, `error` carries at least one entry with
407
+ // a `message`.
408
+ const isFormattedErrors = u => Array.isArray(u) && u.length > 0 && u.every(error => isObject(error) && "message" in error);
409
+ const connectionError = cause => {
410
+ const found = Cause.findError(cause);
411
+ if (Result.isFailure(found)) {
412
+ return new TransportError({
413
+ description: "The graphql-ws connection was closed"
414
+ });
415
+ }
416
+ const error = found.success;
417
+ if (error instanceof TransportError) {
418
+ return error;
419
+ }
420
+ if (Socket.isSocketError(error) && error.reason._tag === "SocketCloseError") {
421
+ return new TransportError({
422
+ description: `The graphql-ws connection closed with ${error.reason.message}`,
423
+ closeCode: error.reason.code,
424
+ cause: error
425
+ });
426
+ }
427
+ return new TransportError({
428
+ description: `The graphql-ws connection failed: ${errorMessage(error)}`,
429
+ cause: error
430
+ });
431
+ };
432
+ /**
433
+ * Builds the graphql-ws transport as an effect, for composing transports by
434
+ * hand. Most code uses {@link layerWebSocket}, which describes the options.
435
+ *
436
+ * @stability experimental
437
+ * @category constructors
438
+ * @since 4.0.0
439
+ */
440
+ export const makeWebSocket = options => Effect.gen(function* () {
441
+ const socket = yield* Socket.makeWebSocket(options.url, {
442
+ protocols: "graphql-transport-ws",
443
+ headers: options.headers === undefined ? undefined : Headers.fromInput(options.headers)
444
+ });
445
+ const writer = yield* socket.writer;
446
+ const scope = yield* Effect.scope;
447
+ const context = yield* Effect.context();
448
+ const connectionParams = options.connectionParams === undefined ? Effect.succeed(undefined) : options.connectionParams.pipe(Effect.provideContext(context), Effect.mapError(cause => new TransportError({
449
+ description: `graphql-ws connectionParams failed: ${errorMessage(cause)}`,
450
+ cause
451
+ })));
452
+ const ackTimeout = options.connectionAckTimeout ?? Duration.seconds(10);
453
+ const keepAlive = options.keepAlive ?? Duration.seconds(10);
454
+ const idleTimeout = Duration.fromInputUnsafe(options.idleTimeout ?? 0);
455
+ const operations = new Map();
456
+ let connection;
457
+ let nextId = 0;
458
+ let active = 0;
459
+ let idleGeneration = 0;
460
+ const send = message => writer.write(JSON.stringify(message)).pipe(Effect.mapError(cause => connectionError(Cause.fail(cause))));
461
+ const lose = (conn, error) => {
462
+ if (conn.error !== undefined) return;
463
+ conn.error = error;
464
+ if (connection === conn) connection = undefined;
465
+ Deferred.doneUnsafe(conn.ack, Effect.fail(error));
466
+ for (const [id, operation] of operations) {
467
+ if (operation.connection !== conn) continue;
468
+ operations.delete(id);
469
+ Queue.failCauseUnsafe(operation.queue, Cause.fail(error));
470
+ }
471
+ };
472
+ // An invalid server message closes the connection with 4400, which is
473
+ // fatal for every operation on it.
474
+ const invalid = (reason, cause) => Effect.andThen(Effect.ignore(writer.write(new Socket.CloseEvent(4400, "Invalid message"))), Effect.fail(new TransportError({
475
+ description: `Invalid graphql-ws message: ${reason}`,
476
+ closeCode: 4400,
477
+ cause
478
+ })));
479
+ const handle = (conn, frame) => {
480
+ // Any frame answers a keep-alive ping; an invalid one drops the connection anyway.
481
+ conn.receivedFrame = true;
482
+ let message;
483
+ try {
484
+ message = JSON.parse(typeof frame === "string" ? frame : textDecoder.decode(frame));
485
+ } catch (cause) {
486
+ return invalid(errorMessage(cause), cause);
487
+ }
488
+ if (!isObject(message)) {
489
+ return invalid("expected an object");
490
+ }
491
+ switch (message.type) {
492
+ case "connection_ack":
493
+ case "ping":
494
+ case "pong":
495
+ {
496
+ if (message.payload != null && !isObject(message.payload)) {
497
+ return invalid(`${message.type} with a payload that is not an object`);
498
+ }
499
+ if (message.type === "connection_ack") {
500
+ Deferred.doneUnsafe(conn.ack, Effect.void);
501
+ }
502
+ return message.type === "ping" ? send({
503
+ type: "pong"
504
+ }) : Effect.void;
505
+ }
506
+ case "next":
507
+ case "error":
508
+ case "complete":
509
+ {
510
+ const id = message.id;
511
+ if (typeof id !== "string" || id === "") {
512
+ return invalid(`${message.type} without an id`);
513
+ }
514
+ if (message.type === "next" && !isObject(message.payload)) {
515
+ return invalid("next with a payload that is not an object");
516
+ }
517
+ if (message.type === "error" && !isFormattedErrors(message.payload)) {
518
+ return invalid("error with a payload that is not a list of GraphQL errors");
519
+ }
520
+ const operation = operations.get(id);
521
+ // Unknown ids, including operations this client already completed, are dropped.
522
+ if (operation === undefined || operation.connection !== conn) return Effect.void;
523
+ if (message.type === "next") {
524
+ Queue.offerUnsafe(operation.queue, {
525
+ body: message.payload
526
+ });
527
+ return Effect.void;
528
+ }
529
+ operations.delete(id);
530
+ if (message.type === "error") {
531
+ // The server rejected the operation; the client fails it with a ResponseError.
532
+ Queue.offerUnsafe(operation.queue, {
533
+ body: {
534
+ errors: message.payload
535
+ }
536
+ });
537
+ }
538
+ Queue.endUnsafe(operation.queue);
539
+ return Effect.void;
540
+ }
541
+ default:
542
+ {
543
+ return invalid(`unknown type ${JSON.stringify(message.type)}`);
544
+ }
545
+ }
546
+ };
547
+ const pinger = Effect.fnUntraced(function* (conn) {
548
+ if (keepAlive === false) return yield* Effect.never;
549
+ while (true) {
550
+ yield* Effect.sleep(keepAlive);
551
+ if (!conn.receivedFrame) {
552
+ return yield* Effect.fail(new TransportError({
553
+ description: "The graphql-ws server did not answer the keep-alive ping"
554
+ }));
555
+ }
556
+ conn.receivedFrame = false;
557
+ yield* send({
558
+ type: "ping"
559
+ });
560
+ }
561
+ });
562
+ const run = Effect.fnUntraced(function* (conn) {
563
+ const {
564
+ pull
565
+ } = yield* socket.reader;
566
+ const init = Effect.flatMap(connectionParams, payload => send({
567
+ type: "connection_init",
568
+ payload
569
+ }));
570
+ const read = Effect.forever(Effect.flatMap(pull, frames => Effect.forEach(frames, frame => handle(conn, frame), {
571
+ discard: true
572
+ })));
573
+ const acknowledged = Deferred.await(conn.ack).pipe(Effect.timeoutOrElse({
574
+ duration: ackTimeout,
575
+ orElse: () => Effect.fail(new TransportError({
576
+ description: "The graphql-ws server did not send connection_ack"
577
+ }))
578
+ }));
579
+ // Reading starts with `connectionParams`, so a close while it runs still
580
+ // fails the waiting operations. The ack timeout starts once
581
+ // `connection_init` is sent.
582
+ yield* Effect.all([read, init.pipe(Effect.andThen(acknowledged), Effect.andThen(pinger(conn)))], {
583
+ concurrency: 2,
584
+ discard: true
585
+ });
586
+ },
587
+ // Runs before the socket closes, so no write waits on a dead socket.
588
+ (effect, conn) => Effect.onError(effect, cause => Effect.sync(() => lose(conn, connectionError(cause)))), effect => Effect.ignore(Effect.scoped(effect)));
589
+ const close = conn => {
590
+ lose(conn, new TransportError({
591
+ description: "The graphql-ws connection was closed"
592
+ }));
593
+ return conn.fiber === undefined ? Effect.void : Fiber.interrupt(conn.fiber);
594
+ };
595
+ const acquireConnection = Effect.suspend(() => {
596
+ if (connection !== undefined) return Effect.succeed(connection);
597
+ const conn = {
598
+ ack: Deferred.makeUnsafe(),
599
+ error: undefined,
600
+ receivedFrame: true,
601
+ fiber: undefined
602
+ };
603
+ connection = conn;
604
+ return Effect.forkIn(run(conn), scope).pipe(Effect.map(fiber => {
605
+ conn.fiber = fiber;
606
+ return conn;
607
+ }));
608
+ });
609
+ const register = Effect.suspend(() => {
610
+ // A connection forked into a closed scope is interrupted before it
611
+ // starts, so new operations fail instead of waiting for an ack.
612
+ if (scope.state._tag === "Closed") {
613
+ return Effect.fail(new TransportError({
614
+ description: "The graphql-ws transport's scope is closed",
615
+ fatal: true
616
+ }));
617
+ }
618
+ active++;
619
+ idleGeneration++;
620
+ return Effect.map(acquireConnection, conn => [String(++nextId), conn]);
621
+ });
622
+ const closeIfIdle = Effect.suspend(() => {
623
+ if (--active > 0 || connection === undefined) return Effect.void;
624
+ const conn = connection;
625
+ if (Duration.isZero(idleTimeout)) return close(conn);
626
+ const generation = ++idleGeneration;
627
+ const closeIfStillIdle = Effect.suspend(() => generation === idleGeneration && connection === conn ? close(conn) : Effect.void);
628
+ return Effect.asVoid(Effect.forkIn(Effect.delay(closeIfStillIdle, idleTimeout), scope));
629
+ });
630
+ const release = (id, conn) => Effect.suspend(() => Effect.andThen(
631
+ // Only a subscribed operation the server has not completed is still in `operations`.
632
+ operations.delete(id) && conn.error === undefined ? Effect.ignore(send({
633
+ id,
634
+ type: "complete"
635
+ })) : Effect.void, closeIfIdle));
636
+ const operation = request => Stream.unwrap(Effect.gen(function* () {
637
+ const [id, conn] = yield* Effect.acquireRelease(register, ([id, conn]) => release(id, conn));
638
+ yield* Deferred.await(conn.ack);
639
+ if (conn.error !== undefined) {
640
+ return yield* Effect.fail(conn.error);
641
+ }
642
+ const queue = yield* Queue.unbounded();
643
+ operations.set(id, {
644
+ queue,
645
+ connection: conn
646
+ });
647
+ // Per-call `request.headers` and `request.urlParams` are ignored:
648
+ // graphql-ws has no per-operation headers or URL.
649
+ yield* send({
650
+ id,
651
+ type: "subscribe",
652
+ payload: requestBody(request)
653
+ });
654
+ return Stream.fromQueue(queue);
655
+ }));
656
+ const execute = request => Effect.flatMap(Stream.runHead(operation(request)), result => result._tag === "Some" ? Effect.succeed(result.value) : Effect.fail(new TransportError({
657
+ description: "The graphql-ws operation completed without a result"
658
+ })));
659
+ return GraphQLProtocol.of({
660
+ execute,
661
+ subscribe: operation
662
+ });
663
+ });
664
+ /**
665
+ * The graphql-ws transport: every operation goes over one WebSocket using the
666
+ * `graphql-transport-ws` subprotocol, with the `Socket.WebSocketConstructor`
667
+ * in context.
668
+ *
669
+ * **Details**
670
+ *
671
+ * The socket connects lazily and multiplexes operations by id. It closes after
672
+ * `idleTimeout` (default 0) with no active operations, or with code `1000`
673
+ * when the layer's scope closes.
674
+ *
675
+ * - Operations wait for `connection_ack`. `connectionAckTimeout` (default
676
+ * 10 seconds) starts after sending `connection_init`.
677
+ * - `connectionParams` runs on every connection, allowing token refresh.
678
+ * Failure or a socket close during evaluation is a `TransportError`.
679
+ * - Handshake `headers` accept `Headers.Input` and are normalized before
680
+ * reaching the socket. They require a Node or Bun WebSocket constructor;
681
+ * browsers cannot set them. Per-operation `headers` and `urlParams` are ignored.
682
+ * - Results carry no HTTP `status`.
683
+ * - `keepAlive` (default 10 seconds, or `false` to disable) sends `ping`.
684
+ * No server frame in the following interval causes a retryable failure.
685
+ * Server `ping` receives `pong`.
686
+ * - Lost connections fail every active operation with a `TransportError`
687
+ * carrying the close code. See `GraphQLClientError.isFatalCloseCode` for
688
+ * non-retryable codes. The transport never replays operations; the client
689
+ * retries subscriptions and the next operation reconnects.
690
+ * - Once the layer's scope has closed, every new operation fails at once
691
+ * with a non-retryable `TransportError` marked `fatal`.
692
+ * - Messages follow graphql-ws validation. Invalid messages close the socket
693
+ * with fatal code `4400`; valid messages for unknown ids are ignored.
694
+ * - Server `error` messages become `ResponseError` values. Interrupting a
695
+ * subscription sends `complete`.
696
+ *
697
+ * **Example** (A client over graphql-ws)
698
+ *
699
+ * ```ts import.meta.vitest
700
+ * import { Effect, Layer } from "effect"
701
+ * import { GraphQLProtocol } from "effect/graphql"
702
+ * import type { Socket } from "effect/socket"
703
+ *
704
+ * // Provide a Socket.WebSocketConstructor, such as
705
+ * // Socket.layerWebSocketConstructorGlobal in a browser
706
+ * export const Protocol: Layer.Layer<GraphQLProtocol.GraphQLProtocol, never, Socket.WebSocketConstructor> =
707
+ * GraphQLProtocol.layerWebSocket({
708
+ * url: "wss://example.com/graphql",
709
+ * connectionParams: Effect.succeed({ authorization: "Bearer secret" }),
710
+ * idleTimeout: "30 seconds"
711
+ * })
712
+ * ```
713
+ *
714
+ * @stability experimental
715
+ * @category layers
716
+ * @since 4.0.0
717
+ */
718
+ export const layerWebSocket = options => Layer.effect(GraphQLProtocol, makeWebSocket(options));
719
+ //# sourceMappingURL=GraphQLProtocol.js.map