effect 4.0.3 → 4.0.5

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