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,1038 @@
1
+ /**
2
+ * Batch lookups through a GraphQL client method with `RequestResolver`.
3
+ * {@link ordered} matches results by position; {@link findById} matches by
4
+ * key; {@link aliased} repeats an operation under aliases in one request.
5
+ * {@link request} turns any of them into `(key) => Effect`.
6
+ *
7
+ * Client middleware still run. When a method needs services, such as a
8
+ * middleware's `requires`, `batchKey` keeps lookups with different services
9
+ * in separate batches. Compose `RequestResolver.setDelay` and
10
+ * `RequestResolver.batchN` with `.pipe` to control batching.
11
+ *
12
+ * Lookups with different protocol URLs are batched separately. See
13
+ * `GraphQLProtocol.layerHttp` for middleware and caching constraints.
14
+ *
15
+ * @stability experimental
16
+ * @since 4.0.0
17
+ */
18
+ import * as Arr from "../Array.ts"
19
+ import * as Cause from "../Cause.ts"
20
+ import * as Context from "../Context.ts"
21
+ import * as Effect from "../Effect.ts"
22
+ import * as Exit from "../Exit.ts"
23
+ import { dual } from "../Function.ts"
24
+ import * as Headers from "../http/Headers.ts"
25
+ import * as UrlParams from "../http/UrlParams.ts"
26
+ import * as internalGraphQL from "../internal/graphql.ts"
27
+ import * as MutableHashMap from "../MutableHashMap.ts"
28
+ import * as Request from "../Request.ts"
29
+ import * as RequestResolver from "../RequestResolver.ts"
30
+ import type * as Types from "../Types.ts"
31
+ import type * as GraphQL from "./GraphQL.ts"
32
+ import type { OperationMethod, PartialResult } from "./GraphQLClient.ts"
33
+ import { DecodeError, type GraphQLClientError, type GraphQLError } from "./GraphQLClientError.ts"
34
+ import type { ExecutionResult, GraphQLRequest } from "./GraphQLProtocol.ts"
35
+
36
+ // -----------------------------------------------------------------------------
37
+ // Types
38
+ // -----------------------------------------------------------------------------
39
+
40
+ type NonNull<A> = Exclude<A, null | undefined>
41
+
42
+ type Step<A, K> = K extends keyof NonNull<A> ? NonNull<A>[K] : never
43
+
44
+ // Check one segment at a time for autocomplete without enumerating all paths.
45
+ // The path must end at a list and cannot cross one.
46
+ type CheckPath<A, P> = NonNull<A> extends ReadonlyArray<any> ? readonly []
47
+ : P extends readonly [infer Head, ...infer Tail]
48
+ ? readonly [Head extends keyof NonNull<A> ? Head : keyof NonNull<A>, ...CheckPath<Step<A, Head>, Tail>]
49
+ : readonly [keyof NonNull<A>]
50
+
51
+ type Path<A, P> = P extends CheckPath<A, P> ? P : CheckPath<A, P>
52
+
53
+ type AtPath<A, P> = P extends readonly [infer Head, ...infer Tail] ? AtPath<Step<A, Head>, Tail> : A
54
+
55
+ type Item<A, P> = NonNull<AtPath<A, P>> extends ReadonlyArray<infer I> ? NonNull<I> : never
56
+
57
+ type Typenames<I> = Types.IsUnion<I> extends true ?
58
+ I extends { readonly __typename: infer T extends string } ? T : never
59
+ : never
60
+
61
+ type Narrow<I, T> = [T] extends [never] ? I : Extract<I, { readonly __typename: T }>
62
+
63
+ type BatchKey<Op extends GraphQL.Any> = [GraphQL.Services<Op>] extends [never] ? { readonly batchKey?: never }
64
+ : { readonly batchKey: (context: Context.Context<GraphQL.Services<Op>>) => unknown }
65
+
66
+ interface KeyRequest<K, A, E, R> extends Request.Request<A, E, R> {
67
+ readonly key: K
68
+ }
69
+
70
+ // Requests are structural, so two requests with equal keys are one request.
71
+ const makeRequest = <K, A, E, R>(key: K): KeyRequest<K, A, E, R> => Request.of<KeyRequest<K, A, E, R>>()({ key })
72
+
73
+ // -----------------------------------------------------------------------------
74
+ // Running
75
+ // -----------------------------------------------------------------------------
76
+
77
+ /**
78
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
79
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
80
+ *
81
+ * **Details**
82
+ *
83
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
84
+ * are sent once per batch and share the result.
85
+ *
86
+ * **Example** (Looking up repositories by id)
87
+ *
88
+ * ```ts import.meta.vitest
89
+ * import { Effect, Layer, Schema, Stream } from "effect"
90
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
91
+ *
92
+ * const RepoNodes = GraphQL.query("RepoNodes", {
93
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
94
+ * variables: { ids: Schema.Array(Schema.String) },
95
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
96
+ * })
97
+ *
98
+ * let calls = 0
99
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
100
+ * execute: (request) => {
101
+ * calls++
102
+ * const { ids } = request.variables as { ids: Array<string> }
103
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
104
+ * },
105
+ * subscribe: () => Stream.empty
106
+ * })
107
+ *
108
+ * const program = Effect.gen(function*() {
109
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
110
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
111
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
112
+ * path: ["nodes"]
113
+ * }))
114
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
115
+ * return [repos.map((repo) => repo.name), calls]
116
+ * })
117
+ *
118
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
119
+ * ```
120
+ *
121
+ * @stability experimental
122
+ * @category running
123
+ * @since 4.0.0
124
+ */
125
+ export const request: {
126
+ // -----------------------------------------------------------------------------
127
+ // Running
128
+ // -----------------------------------------------------------------------------
129
+
130
+ /**
131
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
132
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
133
+ *
134
+ * **Details**
135
+ *
136
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
137
+ * are sent once per batch and share the result.
138
+ *
139
+ * **Example** (Looking up repositories by id)
140
+ *
141
+ * ```ts import.meta.vitest
142
+ * import { Effect, Layer, Schema, Stream } from "effect"
143
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
144
+ *
145
+ * const RepoNodes = GraphQL.query("RepoNodes", {
146
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
147
+ * variables: { ids: Schema.Array(Schema.String) },
148
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
149
+ * })
150
+ *
151
+ * let calls = 0
152
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
153
+ * execute: (request) => {
154
+ * calls++
155
+ * const { ids } = request.variables as { ids: Array<string> }
156
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
157
+ * },
158
+ * subscribe: () => Stream.empty
159
+ * })
160
+ *
161
+ * const program = Effect.gen(function*() {
162
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
163
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
164
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
165
+ * path: ["nodes"]
166
+ * }))
167
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
168
+ * return [repos.map((repo) => repo.name), calls]
169
+ * })
170
+ *
171
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
172
+ * ```
173
+ *
174
+ * @stability experimental
175
+ * @category running
176
+ * @since 4.0.0
177
+ */
178
+ <K, A, E, R>(resolver: RequestResolver.RequestResolver<KeyRequest<K, A, E, R>>): (key: K) => Effect.Effect<A, E, R>
179
+ // -----------------------------------------------------------------------------
180
+ // Running
181
+ // -----------------------------------------------------------------------------
182
+
183
+ /**
184
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
185
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
186
+ *
187
+ * **Details**
188
+ *
189
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
190
+ * are sent once per batch and share the result.
191
+ *
192
+ * **Example** (Looking up repositories by id)
193
+ *
194
+ * ```ts import.meta.vitest
195
+ * import { Effect, Layer, Schema, Stream } from "effect"
196
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
197
+ *
198
+ * const RepoNodes = GraphQL.query("RepoNodes", {
199
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
200
+ * variables: { ids: Schema.Array(Schema.String) },
201
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
202
+ * })
203
+ *
204
+ * let calls = 0
205
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
206
+ * execute: (request) => {
207
+ * calls++
208
+ * const { ids } = request.variables as { ids: Array<string> }
209
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
210
+ * },
211
+ * subscribe: () => Stream.empty
212
+ * })
213
+ *
214
+ * const program = Effect.gen(function*() {
215
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
216
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
217
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
218
+ * path: ["nodes"]
219
+ * }))
220
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
221
+ * return [repos.map((repo) => repo.name), calls]
222
+ * })
223
+ *
224
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
225
+ * ```
226
+ *
227
+ * @stability experimental
228
+ * @category running
229
+ * @since 4.0.0
230
+ */
231
+ <K, A, E, R>(key: K, resolver: RequestResolver.RequestResolver<KeyRequest<K, A, E, R>>): Effect.Effect<A, E, R>
232
+ } = dual(
233
+ 2,
234
+ <K, A, E, R>(key: K, resolver: RequestResolver.RequestResolver<KeyRequest<K, A, E, R>>): Effect.Effect<A, E, R> =>
235
+ Effect.request(makeRequest<// -----------------------------------------------------------------------------
236
+ // Running
237
+ // -----------------------------------------------------------------------------
238
+
239
+ /**
240
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
241
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
242
+ *
243
+ * **Details**
244
+ *
245
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
246
+ * are sent once per batch and share the result.
247
+ *
248
+ * **Example** (Looking up repositories by id)
249
+ *
250
+ * ```ts import.meta.vitest
251
+ * import { Effect, Layer, Schema, Stream } from "effect"
252
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
253
+ *
254
+ * const RepoNodes = GraphQL.query("RepoNodes", {
255
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
256
+ * variables: { ids: Schema.Array(Schema.String) },
257
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
258
+ * })
259
+ *
260
+ * let calls = 0
261
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
262
+ * execute: (request) => {
263
+ * calls++
264
+ * const { ids } = request.variables as { ids: Array<string> }
265
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
266
+ * },
267
+ * subscribe: () => Stream.empty
268
+ * })
269
+ *
270
+ * const program = Effect.gen(function*() {
271
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
272
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
273
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
274
+ * path: ["nodes"]
275
+ * }))
276
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
277
+ * return [repos.map((repo) => repo.name), calls]
278
+ * })
279
+ *
280
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
281
+ * ```
282
+ *
283
+ * @stability experimental
284
+ * @category running
285
+ * @since 4.0.0
286
+ */
287
+ K, // -----------------------------------------------------------------------------
288
+ // Running
289
+ // -----------------------------------------------------------------------------
290
+
291
+ /**
292
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
293
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
294
+ *
295
+ * **Details**
296
+ *
297
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
298
+ * are sent once per batch and share the result.
299
+ *
300
+ * **Example** (Looking up repositories by id)
301
+ *
302
+ * ```ts import.meta.vitest
303
+ * import { Effect, Layer, Schema, Stream } from "effect"
304
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
305
+ *
306
+ * const RepoNodes = GraphQL.query("RepoNodes", {
307
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
308
+ * variables: { ids: Schema.Array(Schema.String) },
309
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
310
+ * })
311
+ *
312
+ * let calls = 0
313
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
314
+ * execute: (request) => {
315
+ * calls++
316
+ * const { ids } = request.variables as { ids: Array<string> }
317
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
318
+ * },
319
+ * subscribe: () => Stream.empty
320
+ * })
321
+ *
322
+ * const program = Effect.gen(function*() {
323
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
324
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
325
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
326
+ * path: ["nodes"]
327
+ * }))
328
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
329
+ * return [repos.map((repo) => repo.name), calls]
330
+ * })
331
+ *
332
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
333
+ * ```
334
+ *
335
+ * @stability experimental
336
+ * @category running
337
+ * @since 4.0.0
338
+ */
339
+ A, // -----------------------------------------------------------------------------
340
+ // Running
341
+ // -----------------------------------------------------------------------------
342
+
343
+ /**
344
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
345
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
346
+ *
347
+ * **Details**
348
+ *
349
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
350
+ * are sent once per batch and share the result.
351
+ *
352
+ * **Example** (Looking up repositories by id)
353
+ *
354
+ * ```ts import.meta.vitest
355
+ * import { Effect, Layer, Schema, Stream } from "effect"
356
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
357
+ *
358
+ * const RepoNodes = GraphQL.query("RepoNodes", {
359
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
360
+ * variables: { ids: Schema.Array(Schema.String) },
361
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
362
+ * })
363
+ *
364
+ * let calls = 0
365
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
366
+ * execute: (request) => {
367
+ * calls++
368
+ * const { ids } = request.variables as { ids: Array<string> }
369
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
370
+ * },
371
+ * subscribe: () => Stream.empty
372
+ * })
373
+ *
374
+ * const program = Effect.gen(function*() {
375
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
376
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
377
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
378
+ * path: ["nodes"]
379
+ * }))
380
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
381
+ * return [repos.map((repo) => repo.name), calls]
382
+ * })
383
+ *
384
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
385
+ * ```
386
+ *
387
+ * @stability experimental
388
+ * @category running
389
+ * @since 4.0.0
390
+ */
391
+ E, // -----------------------------------------------------------------------------
392
+ // Running
393
+ // -----------------------------------------------------------------------------
394
+
395
+ /**
396
+ * Looks up a key with an {@link ordered}, {@link findById} or {@link aliased}
397
+ * resolver, using `request(resolver)(key)` or `request(key, resolver)`.
398
+ *
399
+ * **Details**
400
+ *
401
+ * Concurrent lookups are batched. Keys equal under `Equal` and `Hash`
402
+ * are sent once per batch and share the result.
403
+ *
404
+ * **Example** (Looking up repositories by id)
405
+ *
406
+ * ```ts import.meta.vitest
407
+ * import { Effect, Layer, Schema, Stream } from "effect"
408
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
409
+ *
410
+ * const RepoNodes = GraphQL.query("RepoNodes", {
411
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){... on Repository{name}}}",
412
+ * variables: { ids: Schema.Array(Schema.String) },
413
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Struct({ name: Schema.String }))) })
414
+ * })
415
+ *
416
+ * let calls = 0
417
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
418
+ * execute: (request) => {
419
+ * calls++
420
+ * const { ids } = request.variables as { ids: Array<string> }
421
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => ({ name: `repo-${id}` })) } } })
422
+ * },
423
+ * subscribe: () => Stream.empty
424
+ * })
425
+ *
426
+ * const program = Effect.gen(function*() {
427
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
428
+ * const getRepo = GraphQLResolver.request(GraphQLResolver.ordered(client.RepoNodes, {
429
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
430
+ * path: ["nodes"]
431
+ * }))
432
+ * const repos = yield* Effect.forEach(["1", "2", "1"], getRepo, { concurrency: "unbounded" })
433
+ * return [repos.map((repo) => repo.name), calls]
434
+ * })
435
+ *
436
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["repo-1", "repo-2", "repo-1"], 1]
437
+ * ```
438
+ *
439
+ * @stability experimental
440
+ * @category running
441
+ * @since 4.0.0
442
+ */
443
+ R>(key), resolver)
444
+ )
445
+
446
+ // -----------------------------------------------------------------------------
447
+ // Resolvers
448
+ // -----------------------------------------------------------------------------
449
+
450
+ /**
451
+ * Batches lookups for fields such as `nodes(ids:)` that return one entry per
452
+ * key in input order.
453
+ *
454
+ * **Details**
455
+ *
456
+ * `variables` maps batch keys to operation variables; annotate its parameter
457
+ * to set the key type. `path` selects a list through object fields, with
458
+ * autocomplete at each level. For `__typename` unions, `typename` narrows
459
+ * the success type and treats other types as missing.
460
+ *
461
+ * Null entries, typename mismatches and null parents fail with
462
+ * `Cause.NoSuchElementError`. GraphQL errors whose paths start with
463
+ * `[...path, i]` fail only entry `i`; its `ResponseError` contains those errors
464
+ * and the response status. Other GraphQL errors fail the batch with the same
465
+ * `ResponseError` as a non-partial call. Transport, encode and decode errors
466
+ * also fail the batch. A list length mismatch fails with `DecodeError`.
467
+ *
468
+ * `pageInfo.hasNextPage: true` on the list's parent fails the batch with
469
+ * `DecodeError` to avoid reporting unreturned entries as missing. Set the
470
+ * operation's page size at least as large as `RequestResolver.batchN`.
471
+ *
472
+ * A batch uses one lookup's services. Methods requiring services also require
473
+ * `batchKey`; only lookups with keys equal under `Equal` share a batch. Return
474
+ * the value that distinguishes those services, for example
475
+ * `(context) => Context.get(context, CurrentUser).tenantId`. Methods without
476
+ * services use one batch and do not accept `batchKey`.
477
+ *
478
+ * **Gotchas**
479
+ *
480
+ * Don't return the whole context from `batchKey`. It also holds the caller's
481
+ * span, so most lookups would get a batch of their own.
482
+ *
483
+ * A middleware that reads a `Context.Reference` with a default value doesn't
484
+ * add it to the method's services. `batchKey` isn't required for it, and
485
+ * lookups with different values can share a batch.
486
+ *
487
+ * **Example** (Looking up repositories in a `Node` union)
488
+ *
489
+ * ```ts import.meta.vitest
490
+ * import { Cause, Effect, Layer, RequestResolver, Schema, Stream } from "effect"
491
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
492
+ *
493
+ * const Repository = Schema.Struct({ __typename: Schema.Literal("Repository"), nameWithOwner: Schema.String })
494
+ * const User = Schema.Struct({ __typename: Schema.Literal("User"), login: Schema.String })
495
+ *
496
+ * const RepoNodes = GraphQL.query("RepoNodes", {
497
+ * document: "query RepoNodes($ids:[ID!]!){nodes(ids:$ids){__typename ...on Repository{nameWithOwner} ...on User{login}}}",
498
+ * variables: { ids: Schema.Array(Schema.String) },
499
+ * result: Schema.Struct({ nodes: Schema.Array(Schema.NullOr(Schema.Union([Repository, User]))) })
500
+ * })
501
+ *
502
+ * const store: Record<string, unknown> = {
503
+ * R_1: { __typename: "Repository", nameWithOwner: "Effect-TS/effect" },
504
+ * U_1: { __typename: "User", login: "tim" }
505
+ * }
506
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
507
+ * execute: (request) => {
508
+ * const { ids } = request.variables as { ids: Array<string> }
509
+ * return Effect.succeed({ body: { data: { nodes: ids.map((id) => store[id] ?? null) } } })
510
+ * },
511
+ * subscribe: () => Stream.empty
512
+ * })
513
+ *
514
+ * const program = Effect.gen(function*() {
515
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(RepoNodes))
516
+ * const RepoById = GraphQLResolver.ordered(client.RepoNodes, {
517
+ * variables: (ids: ReadonlyArray<string>) => ({ ids }),
518
+ * path: ["nodes"],
519
+ * typename: "Repository"
520
+ * }).pipe(RequestResolver.batchN(100))
521
+ * const getRepo = GraphQLResolver.request(RepoById)
522
+ * return yield* Effect.forEach(
523
+ * ["R_1", "U_1", "R_2"],
524
+ * (id) => getRepo(id).pipe(
525
+ * Effect.map((repo) => repo.nameWithOwner),
526
+ * Effect.catchIf(Cause.isNoSuchElementError, () => Effect.succeed("missing"))
527
+ * ),
528
+ * { concurrency: "unbounded" }
529
+ * )
530
+ * })
531
+ *
532
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => ["Effect-TS/effect", "missing", "missing"]
533
+ * ```
534
+ *
535
+ * @stability experimental
536
+ * @category resolvers
537
+ * @since 4.0.0
538
+ */
539
+ export const ordered = <
540
+ Op extends GraphQL.Any,
541
+ K,
542
+ const P extends ReadonlyArray<string>,
543
+ T extends Typenames<Item<GraphQL.Result<Op>, P>> = never
544
+ >(
545
+ method: OperationMethod<Op>,
546
+ options: {
547
+ readonly variables: (keys: ReadonlyArray<K>) => GraphQL.Variables<Op>
548
+ readonly path: Path<GraphQL.Result<Op>, P>
549
+ readonly typename?: T | undefined
550
+ } & BatchKey<Op>
551
+ ): RequestResolver.RequestResolver<
552
+ KeyRequest<
553
+ K,
554
+ Narrow<Item<GraphQL.Result<Op>, P>, T>,
555
+ GraphQL.Error<Op> | Cause.NoSuchElementError,
556
+ GraphQL.Services<Op>
557
+ >
558
+ > => {
559
+ const path = options.path as ReadonlyArray<string>
560
+ const codec = internalGraphQL.makeCodec(method.operation)
561
+ return makeResolver(
562
+ method,
563
+ options.batchKey,
564
+ (entries) =>
565
+ Effect.flatMap(callBatch(method, entries, options.variables), ({ keys, targets, result }) => {
566
+ const byIndex = new Map<number, Arr.NonEmptyArray<GraphQLError>>()
567
+ for (const error of result.errors) {
568
+ const index = entryIndex(error, path, keys.length)
569
+ if (index === undefined) return batchError(codec, result)
570
+ const errors = byIndex.get(index)
571
+ if (errors === undefined) byIndex.set(index, [error])
572
+ else errors.push(error)
573
+ }
574
+ const list = findList(result.data, path)
575
+ // An error in a non-null item nulls the whole list, so every entry lost its data to it.
576
+ if (list === undefined) return result.errors.length > 0 ? batchError(codec, result) : completeAll(targets)
577
+ if (list.truncated) return Effect.fail(codec.fail(truncated(path)))
578
+ if (list.items.length !== keys.length) {
579
+ return Effect.fail(codec.fail(
580
+ new DecodeError({
581
+ description: `Expected ${keys.length} entries at ${path.join(".")}, received ${list.items.length}`
582
+ })
583
+ ))
584
+ }
585
+ const response = internalGraphQL.partialResponses.get(result)
586
+ for (let i = 0; i < targets.length; i++) {
587
+ const errors = byIndex.get(i)
588
+ const item = list.items[i]
589
+ targets[i].completeUnsafe(
590
+ errors !== undefined
591
+ ? Exit.fail(codec.fail(
592
+ internalGraphQL.responseError({ status: response?.status, headers: response?.headers }, errors)
593
+ ))
594
+ : matchesTypename(item, options.typename)
595
+ ? Exit.succeed(item)
596
+ : constNoSuchElement
597
+ )
598
+ }
599
+ return Effect.void
600
+ })
601
+ )
602
+ }
603
+
604
+ /**
605
+ * Batches lookups by key for searches and collections that return results
606
+ * in any order.
607
+ *
608
+ * **Details**
609
+ *
610
+ * Write `key` before `variables` to infer the key type from its return type.
611
+ * `variables` maps batch keys to operation variables. Keys are compared with
612
+ * `Equal` and `Hash`. `path` and `typename` work as in {@link ordered};
613
+ * `key` receives the narrowed item type.
614
+ *
615
+ * Missing keys, null parents, pagination and `batchKey` behave as in
616
+ * {@link ordered}.
617
+ * Any GraphQL error fails the batch with the same `ResponseError` as a
618
+ * non-partial call, since error paths cannot be matched to keys.
619
+ *
620
+ * **Example** (Finding repositories through search)
621
+ *
622
+ * ```ts import.meta.vitest
623
+ * import { Effect, Layer, Schema, Stream } from "effect"
624
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
625
+ *
626
+ * const Repository = Schema.Struct({ __typename: Schema.Literal("Repository"), nameWithOwner: Schema.String })
627
+ * const User = Schema.Struct({ __typename: Schema.Literal("User"), login: Schema.String })
628
+ *
629
+ * const SearchRepos = GraphQL.query("SearchRepos", {
630
+ * document: "query SearchRepos($query:String!){search(query:$query,type:REPOSITORY,first:100){pageInfo{hasNextPage} nodes{__typename ...on Repository{nameWithOwner} ...on User{login}}}}",
631
+ * variables: { query: Schema.String },
632
+ * result: Schema.Struct({
633
+ * search: Schema.Struct({
634
+ * pageInfo: Schema.Struct({ hasNextPage: Schema.Boolean }),
635
+ * nodes: Schema.NullOr(Schema.Array(Schema.NullOr(Schema.Union([Repository, User]))))
636
+ * })
637
+ * })
638
+ * })
639
+ *
640
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
641
+ * execute: (request) => {
642
+ * const { query } = request.variables as { query: string }
643
+ * const nodes = query.split(" ").map((term) => ({ __typename: "Repository", nameWithOwner: term.slice(5) }))
644
+ * return Effect.succeed({ body: { data: { search: { pageInfo: { hasNextPage: false }, nodes: nodes.reverse() } } } })
645
+ * },
646
+ * subscribe: () => Stream.empty
647
+ * })
648
+ *
649
+ * const program = Effect.gen(function*() {
650
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(SearchRepos))
651
+ * const RepoByName = GraphQLResolver.findById(client.SearchRepos, {
652
+ * key: (repo) => repo.nameWithOwner,
653
+ * variables: (names) => ({ query: names.map((name) => `repo:${name}`).join(" ") }),
654
+ * path: ["search", "nodes"],
655
+ * typename: "Repository"
656
+ * })
657
+ * const repos = yield* Effect.forEach(
658
+ * ["Effect-TS/effect", "Effect-TS/website"],
659
+ * GraphQLResolver.request(RepoByName),
660
+ * { concurrency: "unbounded" }
661
+ * )
662
+ * return repos.map((repo) => repo.nameWithOwner)
663
+ * })
664
+ *
665
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => ["Effect-TS/effect", "Effect-TS/website"]
666
+ * ```
667
+ *
668
+ * @stability experimental
669
+ * @category resolvers
670
+ * @since 4.0.0
671
+ */
672
+ export const findById = <
673
+ Op extends GraphQL.Any,
674
+ K,
675
+ const P extends ReadonlyArray<string>,
676
+ T extends Typenames<Item<GraphQL.Result<Op>, P>> = never
677
+ >(
678
+ method: OperationMethod<Op>,
679
+ options: {
680
+ readonly variables: (keys: ReadonlyArray<K>) => GraphQL.Variables<Op>
681
+ readonly path: Path<GraphQL.Result<Op>, P>
682
+ readonly key: (item: Narrow<Item<GraphQL.Result<Op>, P>, T>) => K
683
+ readonly typename?: T | undefined
684
+ } & BatchKey<Op>
685
+ ): RequestResolver.RequestResolver<
686
+ KeyRequest<
687
+ K,
688
+ Narrow<Item<GraphQL.Result<Op>, P>, T>,
689
+ GraphQL.Error<Op> | Cause.NoSuchElementError,
690
+ GraphQL.Services<Op>
691
+ >
692
+ > => {
693
+ const path = options.path as ReadonlyArray<string>
694
+ const codec = internalGraphQL.makeCodec(method.operation)
695
+ return makeResolver(
696
+ method,
697
+ options.batchKey,
698
+ (entries) =>
699
+ Effect.flatMap(callBatch(method, entries, options.variables), ({ keys, targets, result }) => {
700
+ if (result.errors.length > 0) return batchError(codec, result)
701
+ const list = findList(result.data, path)
702
+ if (list === undefined) return completeAll(targets)
703
+ if (list.truncated) return Effect.fail(codec.fail(truncated(path)))
704
+ const pending = MutableHashMap.empty<K, Request.Entry<any>>()
705
+ for (let i = 0; i < keys.length; i++) MutableHashMap.set(pending, keys[i], targets[i])
706
+ for (const item of list.items) {
707
+ if (!matchesTypename(item, options.typename)) continue
708
+ const key = options.key(item as Narrow<Item<GraphQL.Result<Op>, P>, T>)
709
+ const target = MutableHashMap.get(pending, key)
710
+ if (target._tag === "None") continue
711
+ MutableHashMap.remove(pending, key)
712
+ target.value.completeUnsafe(Exit.succeed(item))
713
+ }
714
+ MutableHashMap.forEach(pending, (target) => target.completeUnsafe(constNoSuchElement))
715
+ return Effect.void
716
+ })
717
+ )
718
+ }
719
+
720
+ /**
721
+ * Batches calls to a generated query or mutation by repeating the operation
722
+ * under aliases in one request.
723
+ *
724
+ * **When to use**
725
+ *
726
+ * Use when the API has no list-key field, such as GitHub's pull requests by
727
+ * repository and number. Otherwise, {@link ordered} or {@link findById} send
728
+ * a smaller query.
729
+ *
730
+ * **Details**
731
+ *
732
+ * Use {@link request} to get `(variables) => Effect`. Variables that encode
733
+ * to the same JSON share a slice; each slice uses the operation's result Schema.
734
+ * Throws when the resolver is built if the operation has no document
735
+ * template, if an operation directive references a variable, or if a root
736
+ * selection is a fragment spread or inline fragment, since none of these can
737
+ * be repeated under aliases.
738
+ *
739
+ * Batches use `POST` even with `queryMethod: "GET"`. The default batch size
740
+ * is 10; change it with `RequestResolver.batchN`. Middleware run once per
741
+ * batch, and `batchKey` works as in {@link ordered}. Mock and custom methods
742
+ * are called once per slice instead.
743
+ *
744
+ * Errors with a matching `path[0]` fail only that slice, with the alias prefix
745
+ * removed from the error path. Errors without a matching path, transport errors
746
+ * and middleware failures fail the batch.
747
+ *
748
+ * **Gotchas**
749
+ *
750
+ * An error under a non-null root field can null the entire response, failing
751
+ * every slice. GitHub's `repository` and `pullRequest` are nullable.
752
+ *
753
+ * Middleware that hash queries must use `request.query`, not
754
+ * `operation.document`. Trusted-document allowlists don't support batching;
755
+ * query hashing does, since the same operation and slice count produce the
756
+ * same document.
757
+ *
758
+ * Mutations are allowed, but the caller cannot control root-field order.
759
+ *
760
+ * **Example** (Looking up pull requests by number)
761
+ *
762
+ * ```ts import.meta.vitest
763
+ * import { Effect, Layer, RequestResolver, Schema, Stream } from "effect"
764
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol, GraphQLResolver } from "effect/graphql"
765
+ *
766
+ * // Generated from:
767
+ * // query PullRequest($owner: String!, $name: String!, $number: Int!) {
768
+ * // repository(owner: $owner, name: $name) { pullRequest(number: $number) { title } }
769
+ * // }
770
+ * const PullRequest = GraphQL.query("PullRequest", {
771
+ * document: {
772
+ * variables: [
773
+ * { name: "owner", body: "String!" },
774
+ * { name: "name", body: "String!" },
775
+ * { name: "number", body: "Int!" }
776
+ * ],
777
+ * selections: [{
778
+ * name: "repository",
779
+ * body: ["(owner:$", "owner", ",name:$", "name", "){pullRequest(number:$", "number", "){title}}"]
780
+ * }],
781
+ * fragments: []
782
+ * },
783
+ * variables: { owner: Schema.String, name: Schema.String, number: Schema.Int },
784
+ * result: Schema.Struct({
785
+ * repository: Schema.NullOr(Schema.Struct({
786
+ * pullRequest: Schema.NullOr(Schema.Struct({ title: Schema.String }))
787
+ * }))
788
+ * })
789
+ * })
790
+ *
791
+ * // A stand-in server that answers every aliased slice
792
+ * const queries: Array<string> = []
793
+ * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, {
794
+ * execute: (request) => {
795
+ * queries.push(request.query)
796
+ * const data: Record<string, unknown> = {}
797
+ * for (const [key, number] of Object.entries(request.variables as Record<string, unknown>)) {
798
+ * const prefix = /^(_\d+_)number$/.exec(key)?.[1]
799
+ * if (prefix !== undefined) data[`${prefix}repository`] = { pullRequest: { title: `PR ${number}` } }
800
+ * }
801
+ * return Effect.succeed({ body: { data } })
802
+ * },
803
+ * subscribe: () => Stream.empty
804
+ * })
805
+ *
806
+ * const program = Effect.gen(function*() {
807
+ * const github = yield* GraphQLClient.make(GraphQLGroup.make(PullRequest))
808
+ * const PullRequests = GraphQLResolver.aliased(github.PullRequest).pipe(RequestResolver.batchN(20))
809
+ * const getPullRequest = GraphQLResolver.request(PullRequests)
810
+ * const results = yield* Effect.forEach(
811
+ * [1, 2, 3],
812
+ * (number) => getPullRequest({ owner: "Effect-TS", name: "effect", number }),
813
+ * { concurrency: "unbounded" }
814
+ * )
815
+ * return [results.map((result) => result.repository?.pullRequest?.title), queries.length]
816
+ * })
817
+ *
818
+ * await Effect.runPromise(program.pipe(Effect.provide(Scripted))) // => [["PR 1", "PR 2", "PR 3"], 1]
819
+ * ```
820
+ *
821
+ * @stability experimental
822
+ * @category resolvers
823
+ * @since 4.0.0
824
+ */
825
+ export const aliased = <Op extends GraphQL.Any>(
826
+ method: OperationMethod<Op>,
827
+ ...[options]: {} extends BatchKey<Op> ? [options?: BatchKey<Op>] : [options: BatchKey<Op>]
828
+ ): RequestResolver.RequestResolver<AliasedRequest<Op>> => {
829
+ const operation = method.operation
830
+ const template = operation.template
831
+ if (template === undefined) {
832
+ throw new Error(`GraphQLResolver.aliased: operation ${operation.name} has no document template`)
833
+ }
834
+ const unsliceable = internalGraphQL.templateUnsliceableReason(template)
835
+ if (unsliceable !== undefined) {
836
+ throw new Error(`GraphQLResolver.aliased: cannot batch operation ${operation.name}: ${unsliceable}`)
837
+ }
838
+ const codec = internalGraphQL.makeCodec(operation)
839
+ // The registered hook uses this operation's middleware errors and services.
840
+ const execute = internalGraphQL.rawExecutors.get(method)?.execute as
841
+ | ((request: GraphQLRequest) => Effect.Effect<ExecutionResult, GraphQL.Error<Op>, GraphQL.Services<Op>>)
842
+ | undefined
843
+ // Generic conditional parameters prevent calling the strict overload directly.
844
+ const call = method as unknown as (
845
+ variables: GraphQL.Variables<Op>
846
+ ) => Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>, GraphQL.Services<Op>>
847
+
848
+ return makeResolver<AliasedRequest<Op>>(method, options?.batchKey, (entries) =>
849
+ Effect.flatMap(
850
+ Effect.forEach(entries, (entry) => Effect.exit(codec.encodeVariables(entry.request.key))),
851
+ (encoded) => {
852
+ const slices: Array<Slice<Op>> = []
853
+ const byJson = new Map<string, Slice<Op>>()
854
+ for (let i = 0; i < entries.length; i++) {
855
+ const exit = encoded[i]
856
+ if (exit._tag === "Failure") {
857
+ entries[i].completeUnsafe(exit)
858
+ continue
859
+ }
860
+ const json = internalGraphQL.canonicalJson(exit.value)
861
+ const slice = byJson.get(json)
862
+ if (slice !== undefined) {
863
+ slice.entries.push(entries[i])
864
+ continue
865
+ }
866
+ const created = { variables: entries[i].request.key, encoded: exit.value, entries: [entries[i]] }
867
+ byJson.set(json, created)
868
+ slices.push(created)
869
+ }
870
+ const complete = <R>(slice: Slice<Op>, effect: Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>, R>) =>
871
+ Effect.map(Effect.exit(effect), (exit) => {
872
+ for (const entry of slice.entries) entry.completeUnsafe(exit)
873
+ })
874
+ if (execute === undefined) {
875
+ return Effect.forEach(slices, (slice) => complete(slice, call(slice.variables)), { discard: true })
876
+ }
877
+ if (!Arr.isReadonlyArrayNonEmpty(slices)) return Effect.void
878
+ const prefixes = Arr.map(slices, (_, i) => `_${i}_`)
879
+ return execute({
880
+ kind: operation.kind,
881
+ query: internalGraphQL.renderSlices(operation.kind, operation.name, template, prefixes),
882
+ operationName: operation.name,
883
+ variables: Object.fromEntries(
884
+ slices.flatMap((slice, i) =>
885
+ Object.entries(slice.encoded ?? {}).map(([name, value]) => [prefixes[i] + name, value])
886
+ )
887
+ ),
888
+ headers: Headers.empty,
889
+ urlParams: UrlParams.empty
890
+ }).pipe(
891
+ Effect.flatMap((result) => {
892
+ const results = internalGraphQL.splitSlices(result, template, prefixes)
893
+ return results === undefined
894
+ ? codec.failResponse(result)
895
+ : Effect.forEach(slices, (slice, i) => complete(slice, codec.decodeStrict(results[i])), { discard: true })
896
+ })
897
+ )
898
+ }
899
+ )).pipe(RequestResolver.batchN(10))
900
+ }
901
+
902
+ type AliasedRequest<Op extends GraphQL.Any> = KeyRequest<
903
+ GraphQL.Variables<Op>,
904
+ GraphQL.Result<Op>,
905
+ GraphQL.Error<Op>,
906
+ GraphQL.Services<Op>
907
+ >
908
+
909
+ interface Slice<Op extends GraphQL.Any> {
910
+ readonly variables: GraphQL.Variables<Op>
911
+ readonly encoded: unknown
912
+ readonly entries: Array<Request.Entry<AliasedRequest<Op>>>
913
+ }
914
+
915
+ // -----------------------------------------------------------------------------
916
+ // Internals
917
+ // -----------------------------------------------------------------------------
918
+
919
+ const constNoSuchElement = Exit.fail(new Cause.NoSuchElementError())
920
+
921
+ /**
922
+ * Resolves each entry's URL in its own context, then splits the entries by URL
923
+ * and `batchKey`. Each batch runs in its first entry's context, carrying the
924
+ * URL it was grouped by, so the transport doesn't resolve it again.
925
+ */
926
+ const makeResolver = <A extends Request.Any>(
927
+ method: OperationMethod<any>,
928
+ batchKey: ((context: Context.Context<any>) => unknown) | undefined,
929
+ runAll: (entries: Arr.NonEmptyArray<Request.Entry<A>>) => Effect.Effect<void, Request.Error<A>, any>
930
+ ): RequestResolver.RequestResolver<A> => {
931
+ const resolve = internalGraphQL.rawExecutors.get(method)?.url
932
+ const operation = { kind: method.operation.kind, operationName: method.operation.name }
933
+ const urlOf = (entry: Request.Entry<A>) =>
934
+ resolve === undefined ? Effect.succeed(undefined) : Effect.provideContext(resolve(operation), entry.context)
935
+ return RequestResolver.make((entries) =>
936
+ Effect.flatMap(Effect.forEach(entries, urlOf, { concurrency: "unbounded" }), (urls) => {
937
+ // Arrays are compared structurally, so equal keys share a batch.
938
+ const batches = MutableHashMap.empty<
939
+ readonly [string | undefined, unknown],
940
+ Arr.NonEmptyArray<Request.Entry<A>>
941
+ >()
942
+ entries.forEach((entry, i) => {
943
+ const key = [urls[i], batchKey?.(entry.context)] as const
944
+ const batch = MutableHashMap.get(batches, key)
945
+ if (batch._tag === "Some") batch.value.push(entry)
946
+ else MutableHashMap.set(batches, key, [entry])
947
+ })
948
+ return Effect.forEach(batches, ([[url], entries]) => {
949
+ const context: Context.Context<any> = entries[0].context
950
+ return Effect.provideContext(
951
+ runAll(entries),
952
+ url === undefined ? context : Context.add(context, internalGraphQL.ResolvedUrl, { resolve, url })
953
+ )
954
+ }, { concurrency: "unbounded", discard: true })
955
+ })
956
+ )
957
+ }
958
+
959
+ /**
960
+ * Calls the method with deduplicated keys. Each target completes all entries
961
+ * sharing its key.
962
+ */
963
+ const callBatch = <Op extends GraphQL.Any, K>(
964
+ method: OperationMethod<Op>,
965
+ entries: Arr.NonEmptyArray<Request.Entry<KeyRequest<K, any, any, any>>>,
966
+ variables: (keys: ReadonlyArray<K>) => GraphQL.Variables<Op>
967
+ ) => {
968
+ const keys: Array<K> = []
969
+ const targets: Array<Request.Entry<KeyRequest<K, any, any, any>>> = []
970
+ const indices = MutableHashMap.empty<K, number>()
971
+ for (const entry of entries) {
972
+ const index = MutableHashMap.get(indices, entry.request.key)
973
+ if (index._tag === "None") {
974
+ MutableHashMap.set(indices, entry.request.key, keys.length)
975
+ keys.push(entry.request.key)
976
+ targets.push(entry)
977
+ continue
978
+ }
979
+ const previous = targets[index.value]
980
+ targets[index.value] = {
981
+ ...previous,
982
+ completeUnsafe(exit) {
983
+ previous.completeUnsafe(exit)
984
+ entry.completeUnsafe(exit)
985
+ }
986
+ }
987
+ }
988
+ // Generic conditional parameters prevent calling the partial overload directly.
989
+ const call = method as unknown as (
990
+ variables: GraphQL.Variables<Op>,
991
+ options: { readonly partial: true }
992
+ ) => Effect.Effect<PartialResult<GraphQL.Result<Op>>, GraphQL.Error<Op>, GraphQL.Services<Op>>
993
+ return Effect.map(call(variables(keys), { partial: true }), (result) => ({ keys, targets, result }))
994
+ }
995
+
996
+ // Fails the batch with the response's errors and transport metadata.
997
+ const batchError = (
998
+ codec: { readonly failResponse: (result: ExecutionResult) => Effect.Effect<never, GraphQLClientError> },
999
+ result: PartialResult<unknown>
1000
+ ) => codec.failResponse(internalGraphQL.partialResponses.get(result) ?? { errors: result.errors })
1001
+
1002
+ const truncated = (path: ReadonlyArray<string>) =>
1003
+ new DecodeError({
1004
+ description: `pageInfo.hasNextPage is true next to ${
1005
+ path.join(".")
1006
+ }, so results beyond the page would be reported missing. Make the page size at least the RequestResolver.batchN size`
1007
+ })
1008
+
1009
+ const completeAll = (targets: ReadonlyArray<Request.Entry<any>>) => {
1010
+ for (const target of targets) target.completeUnsafe(constNoSuchElement)
1011
+ return Effect.void
1012
+ }
1013
+
1014
+ const entryIndex = (error: GraphQLError, path: ReadonlyArray<string>, length: number): number | undefined => {
1015
+ const errorPath = error.path
1016
+ if (errorPath === undefined || errorPath.length <= path.length) return undefined
1017
+ for (let i = 0; i < path.length; i++) {
1018
+ if (errorPath[i] !== path[i]) return undefined
1019
+ }
1020
+ const index = errorPath[path.length]
1021
+ return typeof index === "number" && Number.isInteger(index) && index >= 0 && index < length ? index : undefined
1022
+ }
1023
+
1024
+ const findList = (
1025
+ data: unknown,
1026
+ path: ReadonlyArray<string>
1027
+ ): { readonly items: ReadonlyArray<unknown>; readonly truncated: boolean } | undefined => {
1028
+ // Path guarantees object fields leading to a list.
1029
+ let parent = data as Record<string, any> | null | undefined
1030
+ for (let i = 0; i < path.length - 1; i++) parent = parent?.[path[i]]
1031
+ const items = parent?.[path[path.length - 1]] as ReadonlyArray<unknown> | null | undefined
1032
+ if (items === null || items === undefined) return undefined
1033
+ return { items, truncated: parent!.pageInfo?.hasNextPage === true }
1034
+ }
1035
+
1036
+ const matchesTypename = (item: unknown, typename: string | undefined): boolean =>
1037
+ item !== null && item !== undefined &&
1038
+ (typename === undefined || (item as { readonly __typename?: unknown }).__typename === typename)