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,924 @@
1
+ /**
2
+ * A typed client with one method per operation in a `GraphQLGroup`. Queries
3
+ * and mutations return an `Effect`; subscriptions return a `Stream`. The
4
+ * client encodes variables, runs middleware, calls the `GraphQLProtocol` in
5
+ * context and decodes results.
6
+ *
7
+ * Retryable subscription failures restart the middleware chain on the
8
+ * `subscriptionRetry` schedule. {@link pages} and {@link items} provide
9
+ * cursor pagination. {@link makeTest} builds a mock client for tests, and
10
+ * {@link layerTest} provides it as a service layer. See `GraphQLProtocol`
11
+ * for transport options.
12
+ *
13
+ * @stability experimental
14
+ * @since 4.0.0
15
+ */
16
+ import * as Cause from "../Cause.ts"
17
+ import * as Context from "../Context.ts"
18
+ import * as Duration from "../Duration.ts"
19
+ import * as Effect from "../Effect.ts"
20
+ import { identity } from "../Function.ts"
21
+ import * as Headers from "../http/Headers.ts"
22
+ import * as UrlParams from "../http/UrlParams.ts"
23
+ import * as internalGraphQL from "../internal/graphql.ts"
24
+ import { getStackTraceLimit, setStackTraceLimit } from "../internal/stackTraceLimit.ts"
25
+ import * as Layer from "../Layer.ts"
26
+ import * as Option from "../Option.ts"
27
+ import type * as Pull from "../Pull.ts"
28
+ import * as Schedule from "../Schedule.ts"
29
+ import * as Schema from "../Schema.ts"
30
+ import * as Stream from "../Stream.ts"
31
+ import * as Struct from "../Struct.ts"
32
+ import type * as Types from "../Types.ts"
33
+ import type * as GraphQL from "./GraphQL.ts"
34
+ import {
35
+ DecodeError,
36
+ GraphQLClientError,
37
+ type GraphQLError,
38
+ isGraphQLClientError,
39
+ PaginationError
40
+ } from "./GraphQLClientError.ts"
41
+ import type * as GraphQLGroup from "./GraphQLGroup.ts"
42
+ import type * as GraphQLMiddleware from "./GraphQLMiddleware.ts"
43
+ import { ExecutionResult, GraphQLProtocol, type GraphQLRequest } from "./GraphQLProtocol.ts"
44
+
45
+ /**
46
+ * Decoded `data` and response `errors` for a `{ partial: true }` call.
47
+ * The errors array may be empty.
48
+ *
49
+ * @stability experimental
50
+ * @category models
51
+ * @since 4.0.0
52
+ */
53
+ export interface PartialResult<A> {
54
+ readonly data: A
55
+ readonly errors: ReadonlyArray<GraphQLError>
56
+ }
57
+
58
+ type Args<Variables, Options> = {} extends Variables ? [variables?: Variables, options?: Options]
59
+ : [variables: Variables, options?: Options]
60
+
61
+ type ArgsWithOptions<Variables, Options> = {} extends Variables ? [variables: Variables | undefined, options: Options]
62
+ : [variables: Variables, options: Options]
63
+
64
+ /**
65
+ * The client method for a query or mutation.
66
+ *
67
+ * **Details**
68
+ *
69
+ * Called as `(variables?, options?)`. Without `partial`, or with
70
+ * `partial: false`, it returns the decoded result and fails with a
71
+ * `ResponseError` on any `errors`. With `partial: true` it returns
72
+ * `{ data, errors }`. `partial` must be a literal `true` or `false`, not
73
+ * an arbitrary `boolean`.
74
+ *
75
+ * An operation without variables is called as `client.Viewer()` or, with
76
+ * options, `client.Viewer(undefined, { headers })`.
77
+ *
78
+ * `headers` and `urlParams` are sent by the HTTP transport, ignored over
79
+ * graphql-ws, and visible to middleware either way as
80
+ * `GraphQLRequest.headers` and `GraphQLRequest.urlParams`. Header options
81
+ * accept `Headers.Input` and are normalized to `Headers.Headers` before
82
+ * middleware runs. `context` provides
83
+ * services for this call only, and removes them from the method's
84
+ * requirements.
85
+ *
86
+ * @stability experimental
87
+ * @category models
88
+ * @since 4.0.0
89
+ */
90
+ export interface OperationMethod<Op extends GraphQL.Any> {
91
+ /** The operation this method runs. */
92
+ readonly operation: Op
93
+ <R2 = never>(
94
+ ...args: ArgsWithOptions<GraphQL.Variables<Op>, {
95
+ readonly headers?: Headers.Input | undefined
96
+ readonly urlParams?: UrlParams.Input | undefined
97
+ readonly context?: Context.Context<R2> | undefined
98
+ readonly partial: true
99
+ }>
100
+ ): Effect.Effect<PartialResult<GraphQL.Result<Op>>, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>>
101
+ <R2 = never>(
102
+ ...args: Args<GraphQL.Variables<Op>, {
103
+ readonly headers?: Headers.Input | undefined
104
+ readonly urlParams?: UrlParams.Input | undefined
105
+ readonly context?: Context.Context<R2> | undefined
106
+ readonly partial?: false | undefined
107
+ }>
108
+ ): Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>>
109
+ }
110
+
111
+ /**
112
+ * The client method for a subscription. It returns a `Stream` of decoded
113
+ * events; an event with `errors` fails the stream with a `ResponseError`,
114
+ * unless that error is retryable and the client resubscribes (see
115
+ * {@link make}). `headers`, `urlParams` and `context` work as for
116
+ * {@link OperationMethod}.
117
+ *
118
+ * @stability experimental
119
+ * @category models
120
+ * @since 4.0.0
121
+ */
122
+ export interface SubscriptionMethod<Op extends GraphQL.Any> {
123
+ /** The operation this method runs. */
124
+ readonly operation: Op
125
+ <R2 = never>(
126
+ ...args: Args<GraphQL.Variables<Op>, {
127
+ readonly headers?: Headers.Input | undefined
128
+ readonly urlParams?: UrlParams.Input | undefined
129
+ readonly context?: Context.Context<R2> | undefined
130
+ }>
131
+ ): Stream.Stream<GraphQL.Result<Op>, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>>
132
+ }
133
+
134
+ /**
135
+ * The client method type for an operation, by its kind.
136
+ *
137
+ * @stability experimental
138
+ * @category models
139
+ * @since 4.0.0
140
+ */
141
+ export type Method<Op extends GraphQL.Any> = Op["kind"] extends "subscription" ? SubscriptionMethod<Op>
142
+ : OperationMethod<Op>
143
+
144
+ /**
145
+ * A client: one method per operation, keyed by operation name.
146
+ *
147
+ * @stability experimental
148
+ * @category models
149
+ * @since 4.0.0
150
+ */
151
+ export type GraphQLClient<Ops extends GraphQL.Any> = {
152
+ readonly [Op in Ops as Op["name"]]: Method<Op>
153
+ }
154
+
155
+ /**
156
+ * Derives the client type for all operations in a `GraphQLGroup`.
157
+ *
158
+ * @stability experimental
159
+ * @category utility types
160
+ * @since 4.0.0
161
+ */
162
+ export type FromGroup<Group> = GraphQLClient<GraphQLGroup.Operations<Group>>
163
+
164
+ /**
165
+ * The default subscription retry schedule: exponential from 500 milliseconds
166
+ * with factor 1.5, capped at 5 seconds, and never shorter than the server's
167
+ * `retryAfter`.
168
+ *
169
+ * @stability experimental
170
+ * @category constants
171
+ * @since 4.0.0
172
+ */
173
+ export const defaultSubscriptionRetry: Schedule.Schedule<Duration.Duration, GraphQLClientError> = Schedule.exponential(
174
+ "500 millis",
175
+ 1.5
176
+ ).pipe(
177
+ Schedule.setInputType<GraphQLClientError>(),
178
+ Schedule.modifyDelay(({ duration, input }) => {
179
+ const capped = Duration.min(duration, Duration.seconds(5))
180
+ return Effect.succeed(input.retryAfter === undefined ? capped : Duration.max(capped, input.retryAfter))
181
+ })
182
+ )
183
+
184
+ /**
185
+ * Builds a client from a group, requiring `GraphQLProtocol` and every
186
+ * middleware service attached to the group or its operations.
187
+ *
188
+ * **Details**
189
+ *
190
+ * Group middleware runs outside operation middleware. Within each level,
191
+ * middleware run in the order attached.
192
+ *
193
+ * `subscriptionRetry` decides how a subscription is retried after it fails
194
+ * with a retryable error: usually a `TransportError`, but also a
195
+ * `ResponseError` whose response carried a valid `Retry-After`, including an
196
+ * event with `errors` on a graphql-sse stream whose response had that header.
197
+ * It defaults to {@link defaultSubscriptionRetry}. Fatal errors are never
198
+ * retried, whatever the schedule. A retry starts a new subscription, so the
199
+ * server may send events again that the stream already emitted.
200
+ *
201
+ * **Example** (A client as a service)
202
+ *
203
+ * ```ts import.meta.vitest
204
+ * import { Context, Effect, Layer, Schema, Stream } from "effect"
205
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
206
+ *
207
+ * // What the generator emits for viewer.graphql
208
+ * const Viewer = GraphQL.query("Viewer", {
209
+ * document: "query Viewer{viewer{login}}",
210
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
211
+ * })
212
+ * const ViewerGraphQLGroup = GraphQLGroup.make(Viewer)
213
+ *
214
+ * class GitHub extends Context.Service<GitHub>()("app/GitHub", {
215
+ * make: GraphQLClient.make(ViewerGraphQLGroup)
216
+ * }) {}
217
+ *
218
+ * // A scripted transport; use GraphQLProtocol.layerHttp({ url }) for a real server
219
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
220
+ * execute: () => Effect.succeed({ body: { data: { viewer: { login: "tim" } } } }),
221
+ * subscribe: () => Stream.empty
222
+ * })
223
+ *
224
+ * const program = GitHub.use((github) => github.Viewer()).pipe(
225
+ * Effect.provide(Layer.effect(GitHub, GitHub.make).pipe(Layer.provide(Scripted)))
226
+ * )
227
+ *
228
+ * await Effect.runPromise(program) // => { viewer: { login: "tim" } }
229
+ * ```
230
+ *
231
+ * **Example** (Partial results)
232
+ *
233
+ * ```ts import.meta.vitest
234
+ * import { Effect, Layer, Schema, Stream } from "effect"
235
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
236
+ *
237
+ * const Repos = GraphQL.query("Repos", {
238
+ * document: "query Repos{a:repository(owner:\"x\",name:\"a\"){name} b:repository(owner:\"x\",name:\"b\"){name}}",
239
+ * result: Schema.Struct({
240
+ * a: Schema.NullOr(Schema.Struct({ name: Schema.String })),
241
+ * b: Schema.NullOr(Schema.Struct({ name: Schema.String }))
242
+ * })
243
+ * })
244
+ *
245
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
246
+ * execute: () =>
247
+ * Effect.succeed({
248
+ * body: {
249
+ * data: { a: { name: "a" }, b: null },
250
+ * errors: [{ message: "Could not resolve to a Repository", path: ["b"] }]
251
+ * }
252
+ * }),
253
+ * subscribe: () => Stream.empty
254
+ * })
255
+ *
256
+ * const program = Effect.gen(function*() {
257
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Repos))
258
+ * // The default call fails with a ResponseError because `errors` is non-empty
259
+ * const failed = yield* Effect.flip(client.Repos())
260
+ * // With partial: true the decoded data comes back together with the errors
261
+ * const { data, errors } = yield* client.Repos(undefined, { partial: true })
262
+ * return [failed.reason._tag, data.a?.name, errors[0].path?.[0]]
263
+ * })
264
+ *
265
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => ["ResponseError", "a", "b"]
266
+ * ```
267
+ *
268
+ * @stability experimental
269
+ * @category constructors
270
+ * @since 4.0.0
271
+ */
272
+ export const make = <Ops extends GraphQL.Any>(
273
+ group: GraphQLGroup.GraphQLGroup<Ops>,
274
+ options?: {
275
+ readonly subscriptionRetry?: Schedule.Schedule<unknown, GraphQLClientError> | undefined
276
+ } | undefined
277
+ ): Effect.Effect<
278
+ GraphQLClient<Ops>,
279
+ never,
280
+ GraphQLProtocol | GraphQLMiddleware.Identifier<GraphQL.Middleware<Ops>>
281
+ > =>
282
+ Effect.gen(function*() {
283
+ const protocol = yield* GraphQLProtocol
284
+ const context = yield* Effect.context<never>()
285
+ const retryPolicy = retryableOnly(options?.subscriptionRetry ?? defaultSubscriptionRetry)
286
+ return fromGroup(group, (operation) =>
287
+ makeMethod(operation, middlewareChain(context, group, operation), protocol, retryPolicy))
288
+ })
289
+
290
+ // Type boundary: a mapping over the operations cannot prove the mapped type's
291
+ // name/kind relationship.
292
+ const fromGroup = <Ops extends GraphQL.Any>(
293
+ group: GraphQLGroup.GraphQLGroup<Ops>,
294
+ f: (operation: Ops) => unknown
295
+ ): GraphQLClient<Ops> =>
296
+ Object.fromEntries(group.operations.map((operation) => [operation.name, f(operation)])) as GraphQLClient<Ops>
297
+
298
+ /**
299
+ * The middleware implementations an operation runs through, outermost first,
300
+ * typed with the errors and requirements its middleware declared.
301
+ */
302
+ type MiddlewareChain<Op extends GraphQL.Any> = ReadonlyArray<
303
+ GraphQLMiddleware.Implementation<
304
+ GraphQLMiddleware.Error<GraphQL.Middleware<Op>>,
305
+ GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>
306
+ >
307
+ >
308
+
309
+ // Runtime tags erase middleware types; `make` requires all their identifiers.
310
+ const middlewareChain = <Op extends GraphQL.Any>(
311
+ context: Context.Context<never>,
312
+ group: GraphQLGroup.GraphQLGroup<Op>,
313
+ operation: Op
314
+ ): MiddlewareChain<Op> => [...group.middlewares, ...operation.middlewares].map((tag) => Context.getUnsafe(context, tag))
315
+
316
+ // Steps `schedule` only for retryable `GraphQLClientError`s. Middleware
317
+ // errors and fatal errors end the retries before reaching it, so a custom
318
+ // schedule can rely on its input being a `GraphQLClientError`.
319
+ const retryableOnly = <Output, Error, Env>(
320
+ schedule: Schedule.Schedule<Output, GraphQLClientError, Error, Env>
321
+ ): Schedule.Schedule<Output | undefined, unknown, Error, Env> =>
322
+ Schedule.fromStep(Effect.map(
323
+ Schedule.toStep(schedule),
324
+ (step) =>
325
+ (now: number, input: unknown): Pull.Pull<[Output | undefined, Duration.Duration], Error, Output | undefined, Env> =>
326
+ isGraphQLClientError(input) && input.isRetryable ? step(now, input) : Cause.done(undefined)
327
+ ))
328
+
329
+ // `status` and `headers` are transport metadata, never read from the body.
330
+ const decodeExecutionResult = Schema.decodeUnknownEffect(ExecutionResult.mapFields(Struct.omit(["status", "headers"])))
331
+
332
+ interface CallOptions<R2 = never> {
333
+ readonly headers?: Headers.Input | undefined
334
+ readonly urlParams?: UrlParams.Input | undefined
335
+ readonly context?: Context.Context<R2> | undefined
336
+ readonly partial?: boolean | undefined
337
+ }
338
+
339
+ // Type boundary: a call without `context` has `R2 = never` unless the caller
340
+ // names `R2` explicitly, so an empty context stands in for `Context<R2>`.
341
+ const callContext = <R2>(options: CallOptions<R2> | undefined): Context.Context<R2> =>
342
+ options?.context ?? (Context.empty() as Context.Context<R2>)
343
+
344
+ const makeMethod = <Op extends GraphQL.Any>(
345
+ operation: Op,
346
+ chain: MiddlewareChain<Op>,
347
+ protocol: GraphQLProtocol["Service"],
348
+ retryPolicy: Schedule.Schedule<unknown, unknown>
349
+ ): OperationMethod<Op> | SubscriptionMethod<Op> => {
350
+ const { decodePartial, decodeStrict, encodeVariables, fail, schemaFail } = internalGraphQL.makeCodec(operation)
351
+
352
+ const notGraphQL = schemaFail((description) =>
353
+ new DecodeError({ description: `Not a GraphQL response: ${description}` })
354
+ )
355
+
356
+ const buildRequest = (variables: GraphQL.Variables<Op> | undefined, options: CallOptions | undefined) =>
357
+ encodeVariables(variables).pipe(
358
+ Effect.map((encoded): GraphQLRequest => ({
359
+ kind: operation.kind,
360
+ query: operation.document,
361
+ operationName: operation.name,
362
+ variables: encoded,
363
+ headers: Headers.fromInput(options?.headers),
364
+ urlParams: options?.urlParams === undefined ? UrlParams.empty : UrlParams.fromInput(options.urlParams)
365
+ }))
366
+ )
367
+
368
+ const decodeResponse = ({ body, ...metadata }: {
369
+ readonly body: unknown
370
+ readonly status?: number | undefined
371
+ readonly headers?: Headers.Headers | undefined
372
+ }) =>
373
+ decodeExecutionResult(body).pipe(
374
+ Effect.map((result): ExecutionResult => ({ ...result, ...metadata })),
375
+ Effect.catch(notGraphQL)
376
+ )
377
+
378
+ // Type boundary: a middleware sees the errors of the middleware further in
379
+ // as `Types.unhandled` (as in `RpcMiddleware`), and none of their
380
+ // requirements. Those errors are part of `GraphQL.Error<Op>`, so the chain
381
+ // restates its type here. Force POST and the resolved URL apply only at the
382
+ // transport boundary.
383
+ const execute = (
384
+ index: number,
385
+ request: GraphQLRequest,
386
+ forcePost: boolean,
387
+ resolvedUrl: internalGraphQL.ResolvedUrl
388
+ ): Effect.Effect<ExecutionResult, GraphQL.Error<Op>, GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>> =>
389
+ index === chain.length
390
+ ? protocol.execute(request).pipe(
391
+ forcePost ? Effect.provideService(internalGraphQL.ForcePost, true) : identity,
392
+ resolvedUrl === undefined ? identity : Effect.provideService(internalGraphQL.ResolvedUrl, resolvedUrl),
393
+ Effect.mapError(fail),
394
+ Effect.flatMap(decodeResponse)
395
+ )
396
+ : chain[index].execute({
397
+ operation,
398
+ request,
399
+ next: (request) =>
400
+ execute(index + 1, request, forcePost, resolvedUrl) as Effect.Effect<
401
+ ExecutionResult,
402
+ GraphQLClientError | Types.unhandled
403
+ >
404
+ }) as Effect.Effect<ExecutionResult, GraphQL.Error<Op>, GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>>
405
+
406
+ // Hands a resolver batch's URL to the transport only, so calls made by
407
+ // middleware resolve their own.
408
+ const run = (request: GraphQLRequest, forcePost: boolean) =>
409
+ internalGraphQL.ResolvedUrl.use((resolved) =>
410
+ resolved === undefined
411
+ ? execute(0, request, forcePost, undefined)
412
+ : execute(0, request, forcePost, resolved).pipe(Effect.provideService(internalGraphQL.ResolvedUrl, undefined))
413
+ )
414
+
415
+ const subscribe = (
416
+ index: number,
417
+ request: GraphQLRequest
418
+ ): Stream.Stream<ExecutionResult, GraphQL.Error<Op>, GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>> =>
419
+ index === chain.length
420
+ ? protocol.subscribe(request).pipe(
421
+ Stream.mapError(fail),
422
+ Stream.mapEffect(decodeResponse)
423
+ )
424
+ : chain[index].subscribe({
425
+ operation,
426
+ request,
427
+ next: (request) =>
428
+ subscribe(index + 1, request) as Stream.Stream<ExecutionResult, GraphQLClientError | Types.unhandled>
429
+ }) as Stream.Stream<ExecutionResult, GraphQL.Error<Op>, GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>>
430
+
431
+ if (operation.kind === "subscription") {
432
+ const subscriptionMethod = <R2 = never>(...[variables, options]: Args<GraphQL.Variables<Op>, CallOptions<R2>>) =>
433
+ Stream.unwrap(Effect.map(buildRequest(variables, options), (request) => subscribe(0, request))).pipe(
434
+ Stream.mapEffect(decodeStrict),
435
+ Stream.retry(retryPolicy),
436
+ Stream.provideContext(callContext(options))
437
+ )
438
+ return Object.assign(subscriptionMethod, { operation })
439
+ }
440
+
441
+ function method<R2 = never>(
442
+ ...args: ArgsWithOptions<GraphQL.Variables<Op>, CallOptions<R2> & { readonly partial: true }>
443
+ ): Effect.Effect<PartialResult<GraphQL.Result<Op>>, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>>
444
+ function method<R2 = never>(
445
+ ...args: Args<GraphQL.Variables<Op>, CallOptions<R2> & { readonly partial?: false | undefined }>
446
+ ): Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>>
447
+ function method<R2>(
448
+ variables?: GraphQL.Variables<Op>,
449
+ options?: CallOptions<R2>
450
+ ): Effect.Effect<
451
+ GraphQL.Result<Op> | PartialResult<GraphQL.Result<Op>>,
452
+ GraphQL.Error<Op>,
453
+ Exclude<GraphQL.Services<Op>, R2>
454
+ > {
455
+ return buildRequest(variables, options).pipe(
456
+ Effect.flatMap((request) => run(request, false)),
457
+ Effect.flatMap(
458
+ (result): Effect.Effect<
459
+ GraphQL.Result<Op> | PartialResult<GraphQL.Result<Op>>,
460
+ GraphQLClientError,
461
+ Op["result"]["DecodingServices"]
462
+ > => options?.partial === true ? decodePartial(result) : decodeStrict(result)
463
+ ),
464
+ Effect.provideContext(callContext(options))
465
+ )
466
+ }
467
+ internalGraphQL.rawExecutors.set(method, { execute: (request) => run(request, true), url: protocol.url })
468
+ return Object.assign(method, { operation })
469
+ }
470
+
471
+ /**
472
+ * The `pageInfo` a paged connection must select.
473
+ *
474
+ * @stability experimental
475
+ * @category paging
476
+ * @since 4.0.0
477
+ */
478
+ export interface PageInfo {
479
+ readonly hasNextPage: boolean
480
+ readonly endCursor: string | null
481
+ }
482
+
483
+ /**
484
+ * What the `connection` getter of {@link pages} must return.
485
+ *
486
+ * @stability experimental
487
+ * @category paging
488
+ * @since 4.0.0
489
+ */
490
+ export interface Connection {
491
+ readonly pageInfo: PageInfo
492
+ }
493
+
494
+ /**
495
+ * What the `connection` getter of {@link items} must return: a connection
496
+ * that also selects `nodes`.
497
+ *
498
+ * @stability experimental
499
+ * @category paging
500
+ * @since 4.0.0
501
+ */
502
+ export interface NodesConnection<Node> extends Connection {
503
+ readonly nodes: ReadonlyArray<Node> | null
504
+ }
505
+
506
+ /**
507
+ * The type `variables` is given when the operation has no `$after` variable.
508
+ * Nothing is assignable to it, so the mistake is a compile error at the call.
509
+ *
510
+ * @stability experimental
511
+ * @category paging
512
+ * @since 4.0.0
513
+ */
514
+ export interface MissingAfterVariable {
515
+ readonly "effect/graphql":
516
+ "GraphQLClient.pages requires the operation to declare a nullable `$after: String` variable"
517
+ }
518
+
519
+ /**
520
+ * The type `variables` is given when the operation's `after` variable is
521
+ * required or does not accept a `string`. The helper leaves `after` out on the
522
+ * first page and sends the previous `endCursor` afterwards, so it can only
523
+ * drive an optional variable that takes a string.
524
+ *
525
+ * @stability experimental
526
+ * @category paging
527
+ * @since 4.0.0
528
+ */
529
+ export interface InvalidAfterVariable {
530
+ readonly "effect/graphql": "GraphQLClient.pages requires the `after` variable to be optional and to accept a string"
531
+ }
532
+
533
+ /**
534
+ * The `variables` accepted by {@link pages} and {@link items}: the method's
535
+ * variables without `after`, which the helper supplies.
536
+ *
537
+ * **Details**
538
+ *
539
+ * The operation's `after` variable must be optional and accept a `string`.
540
+ * `after?: string | null`, from a nullable `$after: String`, and
541
+ * `after?: string`, from a `$after: String!` with a default value, are both
542
+ * accepted. A required `after` gives {@link InvalidAfterVariable}, as does one
543
+ * that does not accept a `string`; no `after` at all gives
544
+ * {@link MissingAfterVariable}.
545
+ *
546
+ * @stability experimental
547
+ * @category paging
548
+ * @since 4.0.0
549
+ */
550
+ export type PagingVariables<Variables> = "after" extends keyof Variables
551
+ ? {} extends Pick<Variables, "after"> ? string extends Variables["after"] ? Omit<Variables, "after">
552
+ : InvalidAfterVariable
553
+ : InvalidAfterVariable
554
+ : MissingAfterVariable
555
+
556
+ /**
557
+ * Pages forward through a cursor connection, emitting one connection per
558
+ * page.
559
+ *
560
+ * **Details**
561
+ *
562
+ * Requires an optional string `$after` variable (see {@link PagingVariables}).
563
+ * The first request omits `after`; subsequent requests use the previous
564
+ * `endCursor`. Pages are fetched only when pulled, so `Stream.take` stops
565
+ * further requests. Middleware runs on each page.
566
+ *
567
+ * `connection` extracts the connection from each result. `options` is
568
+ * forwarded to every call but cannot include `partial`: errors fail the stream.
569
+ * Its `headers` accept `Headers.Input`, just as for operation calls.
570
+ *
571
+ * The stream ends after a page whose `hasNextPage` is `false`. A `null`
572
+ * connection on the first page gives an empty stream; on a later page it
573
+ * fails with a `PaginationError`, as does `hasNextPage: true` with a `null`
574
+ * or unchanged `endCursor`.
575
+ *
576
+ * Root fields that come with every page, such as GitHub's `rateLimit`, are for
577
+ * middleware to read from the raw `ExecutionResult`.
578
+ *
579
+ * **Example** (Paging issues, two per page)
580
+ *
581
+ * ```ts import.meta.vitest
582
+ * import { Effect, Layer, Schema, Stream } from "effect"
583
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
584
+ *
585
+ * const RepoIssues = GraphQL.query("RepoIssues", {
586
+ * document: "query RepoIssues($owner:String!,$name:String!,$after:String){repository(owner:$owner,name:$name){issues(first:2,after:$after){pageInfo{hasNextPage endCursor}nodes{number}}}}",
587
+ * variables: { owner: Schema.String, name: Schema.String, after: Schema.optional(Schema.NullOr(Schema.String)) },
588
+ * result: Schema.Struct({
589
+ * repository: Schema.NullOr(Schema.Struct({
590
+ * issues: Schema.Struct({
591
+ * pageInfo: Schema.Struct({ hasNextPage: Schema.Boolean, endCursor: Schema.NullOr(Schema.String) }),
592
+ * nodes: Schema.NullOr(Schema.Array(Schema.NullOr(Schema.Struct({ number: Schema.Int }))))
593
+ * })
594
+ * }))
595
+ * })
596
+ * })
597
+ *
598
+ * // A scripted server with two pages
599
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
600
+ * execute: (request) => {
601
+ * const after = (request.variables as { after?: string }).after
602
+ * const page = after === undefined
603
+ * ? { pageInfo: { hasNextPage: true, endCursor: "c1" }, nodes: [{ number: 1 }, { number: 2 }] }
604
+ * : { pageInfo: { hasNextPage: false, endCursor: null }, nodes: [{ number: 3 }] }
605
+ * return Effect.succeed({ body: { data: { repository: { issues: page } } } })
606
+ * },
607
+ * subscribe: () => Stream.empty
608
+ * })
609
+ *
610
+ * const program = Effect.gen(function*() {
611
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoIssues))
612
+ * const pages = GraphQLClient.pages(client.RepoIssues, {
613
+ * variables: { owner: "Effect-TS", name: "effect" },
614
+ * connection: (result) => result.repository?.issues
615
+ * })
616
+ * return yield* Stream.runCollect(Stream.map(pages, (page) => page.pageInfo.endCursor))
617
+ * })
618
+ *
619
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => ["c1", null]
620
+ * ```
621
+ *
622
+ * @stability experimental
623
+ * @category paging
624
+ * @since 4.0.0
625
+ */
626
+ export const pages = <Op extends GraphQL.Any, C extends Connection, R2 = never>(
627
+ method: OperationMethod<Op>,
628
+ options: {
629
+ readonly variables: PagingVariables<GraphQL.Variables<Op>>
630
+ readonly connection: (result: GraphQL.Result<Op>) => C | null | undefined
631
+ readonly options?: {
632
+ readonly headers?: Headers.Input | undefined
633
+ readonly urlParams?: UrlParams.Input | undefined
634
+ readonly context?: Context.Context<R2> | undefined
635
+ } | undefined
636
+ }
637
+ ): Stream.Stream<C, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>> => {
638
+ // Type boundary: `method`'s strict overload, with `variables` widened.
639
+ // `PagingVariables` has already checked that `options.variables` plus an
640
+ // optional string `after` are the operation's variables, but the overloads'
641
+ // conditional parameter types over a generic `Op` cannot carry that here.
642
+ const call = method as unknown as (
643
+ variables: unknown,
644
+ options: CallOptions<R2> | undefined
645
+ ) => Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>>
646
+ // The state is the cursor to send; the first page is fetched without one.
647
+ return Stream.paginate(undefined as string | undefined, (after) =>
648
+ Effect.flatMap(
649
+ call(after === undefined ? options.variables : { ...options.variables, after }, options.options),
650
+ (result): Effect.Effect<readonly [ReadonlyArray<C>, Option.Option<string>], GraphQLClientError> => {
651
+ const paginationError = (description: string) =>
652
+ Effect.fail(
653
+ new GraphQLClientError({
654
+ operation: method.operation.name,
655
+ reason: new PaginationError({ description, cursor: after ?? null })
656
+ })
657
+ )
658
+ const connection = options.connection(result)
659
+ if (connection === null || connection === undefined) {
660
+ return after === undefined
661
+ ? Effect.succeed([[], Option.none()])
662
+ : paginationError("The connection was null on a page after the first")
663
+ }
664
+ const { endCursor, hasNextPage } = connection.pageInfo
665
+ if (!hasNextPage) return Effect.succeed([[connection], Option.none()])
666
+ if (endCursor === null) return paginationError("hasNextPage is true but endCursor is null")
667
+ if (endCursor === after) return paginationError(`endCursor "${endCursor}" did not advance`)
668
+ return Effect.succeed([[connection], Option.some(endCursor)])
669
+ }
670
+ ))
671
+ }
672
+
673
+ /**
674
+ * Like {@link pages}, but emits decoded entries from `nodes`, including
675
+ * `null` entries. Use `Stream.filter(Predicate.isNotNull)` to drop them.
676
+ * For connections selecting only `edges`, use {@link pages} with
677
+ * `Stream.flatMap`.
678
+ *
679
+ * **Example** (Flattening issue numbers across pages)
680
+ *
681
+ * ```ts import.meta.vitest
682
+ * import { Effect, Layer, Schema, Stream } from "effect"
683
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
684
+ *
685
+ * const RepoIssues = GraphQL.query("RepoIssues", {
686
+ * document: "query RepoIssues($owner:String!,$name:String!,$after:String){repository(owner:$owner,name:$name){issues(first:2,after:$after){pageInfo{hasNextPage endCursor}nodes{number}}}}",
687
+ * variables: { owner: Schema.String, name: Schema.String, after: Schema.optional(Schema.NullOr(Schema.String)) },
688
+ * result: Schema.Struct({
689
+ * repository: Schema.NullOr(Schema.Struct({
690
+ * issues: Schema.Struct({
691
+ * pageInfo: Schema.Struct({ hasNextPage: Schema.Boolean, endCursor: Schema.NullOr(Schema.String) }),
692
+ * nodes: Schema.NullOr(Schema.Array(Schema.NullOr(Schema.Struct({ number: Schema.Int }))))
693
+ * })
694
+ * }))
695
+ * })
696
+ * })
697
+ *
698
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
699
+ * execute: (request) => {
700
+ * const after = (request.variables as { after?: string }).after
701
+ * const page = after === undefined
702
+ * ? { pageInfo: { hasNextPage: true, endCursor: "c1" }, nodes: [{ number: 1 }, null] }
703
+ * : { pageInfo: { hasNextPage: false, endCursor: null }, nodes: [{ number: 3 }] }
704
+ * return Effect.succeed({ body: { data: { repository: { issues: page } } } })
705
+ * },
706
+ * subscribe: () => Stream.empty
707
+ * })
708
+ *
709
+ * const program = Effect.gen(function*() {
710
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoIssues))
711
+ * const items = GraphQLClient.items(client.RepoIssues, {
712
+ * variables: { owner: "Effect-TS", name: "effect" },
713
+ * connection: (result) => result.repository?.issues
714
+ * })
715
+ * return yield* Stream.runCollect(Stream.map(items, (issue) => issue?.number ?? null))
716
+ * })
717
+ *
718
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [1, null, 3]
719
+ * ```
720
+ *
721
+ * @stability experimental
722
+ * @category paging
723
+ * @since 4.0.0
724
+ */
725
+ export const items = <Op extends GraphQL.Any, Node, R2 = never>(
726
+ method: OperationMethod<Op>,
727
+ options: {
728
+ readonly variables: PagingVariables<GraphQL.Variables<Op>>
729
+ readonly connection: (result: GraphQL.Result<Op>) => NodesConnection<Node> | null | undefined
730
+ readonly options?: {
731
+ readonly headers?: Headers.Input | undefined
732
+ readonly urlParams?: UrlParams.Input | undefined
733
+ readonly context?: Context.Context<R2> | undefined
734
+ } | undefined
735
+ }
736
+ ): Stream.Stream<Node, GraphQL.Error<Op>, Exclude<GraphQL.Services<Op>, R2>> =>
737
+ Stream.flatMap(pages(method, options), (connection) => Stream.fromArray(connection.nodes ?? []))
738
+
739
+ /**
740
+ * The options a {@link makeTest} handler receives: the `headers` and
741
+ * `urlParams` the caller passed, unchanged. Headers accept `Headers.Input`;
742
+ * unlike real transport requests, test call options are not normalized.
743
+ *
744
+ * @stability experimental
745
+ * @category testing
746
+ * @since 4.0.0
747
+ */
748
+ export interface TestCallOptions {
749
+ readonly headers?: Headers.Input | undefined
750
+ readonly urlParams?: UrlParams.Input | undefined
751
+ }
752
+
753
+ /**
754
+ * A {@link makeTest} handler for one operation. Queries and mutations return
755
+ * an `Effect` and subscriptions a `Stream` of the decoded result type.
756
+ *
757
+ * @stability experimental
758
+ * @category testing
759
+ * @since 4.0.0
760
+ */
761
+ export type TestHandler<Op extends GraphQL.Any> = Op["kind"] extends "subscription" ? (
762
+ variables: GraphQL.Variables<Op>,
763
+ options: TestCallOptions
764
+ ) => Stream.Stream<GraphQL.Result<Op>, GraphQL.Error<Op>>
765
+ : (
766
+ variables: GraphQL.Variables<Op>,
767
+ options: TestCallOptions
768
+ ) => Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>>
769
+
770
+ /**
771
+ * The handlers given to {@link makeTest}, keyed by operation name. Every
772
+ * handler is optional.
773
+ *
774
+ * @stability experimental
775
+ * @category testing
776
+ * @since 4.0.0
777
+ */
778
+ export type TestHandlers<Ops extends GraphQL.Any> = {
779
+ readonly [Op in Ops as Op["name"]]?: TestHandler<Op>
780
+ }
781
+
782
+ /**
783
+ * Builds a client for tests that answers each operation from a handler,
784
+ * without a protocol.
785
+ *
786
+ * **Details**
787
+ *
788
+ * Handlers receive the caller's `variables` and options unchanged and return
789
+ * values in their decoded form. Nothing is encoded, decoded or validated, and
790
+ * no middleware runs. Called with `{ partial: true }`, a method returns
791
+ * `{ data, errors: [] }`. Subscriptions are not retried. Every method carries
792
+ * `.operation`, so {@link pages} and {@link items} work on it.
793
+ *
794
+ * Calling an operation without a handler dies with an `UnimplementedError`
795
+ * defect, so `catchTag` in the code under test cannot swallow a missing stub.
796
+ *
797
+ * Methods keep the real client's types, including middleware service
798
+ * requirements. Tests must provide those services even though mocks do not
799
+ * use them.
800
+ *
801
+ * To test partial data together with `errors`, or the encode and decode path,
802
+ * provide a scripted `GraphQLProtocol` layer to {@link make} instead.
803
+ *
804
+ * **Example** (Replacing a client service in tests)
805
+ *
806
+ * ```ts import.meta.vitest
807
+ * import { Context, Effect, Layer, Schema } from "effect"
808
+ * import { GraphQL, GraphQLClient, GraphQLGroup } from "effect/graphql"
809
+ *
810
+ * const RepoIssues = GraphQL.query("RepoIssues", {
811
+ * document: "query RepoIssues($owner:String!,$name:String!){repository(owner:$owner,name:$name){issues{totalCount}}}",
812
+ * variables: { owner: Schema.String, name: Schema.String },
813
+ * result: Schema.Struct({
814
+ * repository: Schema.NullOr(Schema.Struct({ issues: Schema.Struct({ totalCount: Schema.Int }) }))
815
+ * })
816
+ * })
817
+ * const GitHubGraphQLGroup = GraphQLGroup.make(RepoIssues)
818
+ *
819
+ * class GitHub extends Context.Service<GitHub>()("app/GitHub", {
820
+ * make: GraphQLClient.make(GitHubGraphQLGroup)
821
+ * }) {}
822
+ *
823
+ * const GitHubTest = Layer.succeed(
824
+ * GitHub,
825
+ * GraphQLClient.makeTest(GitHubGraphQLGroup)({
826
+ * RepoIssues: ({ name }) => Effect.succeed({ repository: { issues: { totalCount: name.length } } })
827
+ * })
828
+ * )
829
+ *
830
+ * const program = GitHub.use((github) => github.RepoIssues({ owner: "Effect-TS", name: "effect" }))
831
+ *
832
+ * await Effect.runPromise(program.pipe(Effect.provide(GitHubTest))) // => { repository: { issues: { totalCount: 6 } } }
833
+ * ```
834
+ *
835
+ * @stability experimental
836
+ * @category testing
837
+ * @since 4.0.0
838
+ */
839
+ export const makeTest =
840
+ <Ops extends GraphQL.Any>(group: GraphQLGroup.GraphQLGroup<Ops>) =>
841
+ (handlers: TestHandlers<Ops>): GraphQLClient<Ops> => {
842
+ // Type boundary: the mapped type keys handlers by name, which a lookup by a
843
+ // runtime name cannot follow.
844
+ const byName = handlers as Record<string, TestHandler<Ops> | undefined>
845
+ return fromGroup(
846
+ group,
847
+ (operation) =>
848
+ makeTestMethod(operation, Object.hasOwn(byName, operation.name) ? byName[operation.name] : undefined)
849
+ )
850
+ }
851
+
852
+ /**
853
+ * Builds a mock layer for a client service using {@link makeTest}. Bind the
854
+ * service and group once to expose a `layerTest(handlers)` helper on the service.
855
+ * The group fixes the operation types before the handlers are checked.
856
+ *
857
+ * **Example** (Binding a mock layer to a client service)
858
+ *
859
+ * ```ts import.meta.vitest
860
+ * import { Context, Effect, Schema } from "effect"
861
+ * import { GraphQL, GraphQLClient, GraphQLGroup } from "effect/graphql"
862
+ *
863
+ * const RepoIssues = GraphQL.query("RepoIssues", {
864
+ * document: "query RepoIssues($owner:String!,$name:String!){repository(owner:$owner,name:$name){issues{totalCount}}}",
865
+ * variables: { owner: Schema.String, name: Schema.String },
866
+ * result: Schema.Struct({
867
+ * repository: Schema.NullOr(Schema.Struct({ issues: Schema.Struct({ totalCount: Schema.Int }) }))
868
+ * })
869
+ * })
870
+ * const GitHubGraphQLGroup = GraphQLGroup.make(RepoIssues)
871
+ *
872
+ * class GitHub extends Context.Service<GitHub>()("app/GitHub", {
873
+ * make: GraphQLClient.make(GitHubGraphQLGroup)
874
+ * }) {
875
+ * static readonly layerTest = GraphQLClient.layerTest(GitHub, GitHubGraphQLGroup)
876
+ * }
877
+ *
878
+ * const program = GitHub.use((github) => github.RepoIssues({ owner: "Effect-TS", name: "effect" }))
879
+ *
880
+ * await Effect.runPromise(program.pipe(Effect.provide(GitHub.layerTest({
881
+ * RepoIssues: ({ name }) => Effect.succeed({ repository: { issues: { totalCount: name.length } } })
882
+ * })))) // => { repository: { issues: { totalCount: 6 } } }
883
+ * ```
884
+ *
885
+ * @see {@link makeTest} for mock behavior and the middleware-services limitation.
886
+ * @stability experimental
887
+ * @category testing
888
+ * @since 4.0.0
889
+ */
890
+ export const layerTest = <I, Service, Ops extends GraphQL.Any>(
891
+ service: Context.Key<I, Service>,
892
+ group: GraphQLGroup.GraphQLGroup<Ops> & (GraphQLClient<Ops> extends NoInfer<Service> ? unknown : never)
893
+ ) =>
894
+ (handlers: TestHandlers<Ops>): Layer.Layer<I> =>
895
+ // The group constraint checks that the mock satisfies the service shape.
896
+ Layer.succeed(service, makeTest(group)(handlers) as unknown as Service)
897
+
898
+ // Handlers are called through these erased signatures; `makeTest` has
899
+ // already checked them against the operation.
900
+ type ErasedTestHandler = (variables: unknown, options: TestCallOptions) => unknown
901
+
902
+ const makeTestMethod = (operation: GraphQL.Any, handler: ErasedTestHandler | undefined) => {
903
+ const subscription = operation.kind === "subscription"
904
+ if (handler === undefined) {
905
+ const unimplemented = () => {
906
+ const prevLimit = getStackTraceLimit()
907
+ setStackTraceLimit(2)
908
+ const error = new Error(`GraphQLClient: Unimplemented operation "${operation.name}"`)
909
+ setStackTraceLimit(prevLimit)
910
+ error.name = "UnimplementedError"
911
+ return subscription ? Stream.die(error) : Effect.die(error)
912
+ }
913
+ return Object.assign(unimplemented, { operation })
914
+ }
915
+ const call = (variables: unknown, options: (TestCallOptions & { readonly partial?: boolean }) | undefined) => {
916
+ const run = () => handler(variables === undefined ? {} : variables, options ?? {})
917
+ if (subscription) return Stream.suspend(run as () => Stream.Stream<unknown, unknown>)
918
+ const result = Effect.suspend(run as () => Effect.Effect<unknown, unknown>)
919
+ return options?.partial === true
920
+ ? Effect.map(result, (data): PartialResult<unknown> => ({ data, errors: [] }))
921
+ : result
922
+ }
923
+ return Object.assign(call, { operation })
924
+ }