effect 4.0.4 → 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 (308) hide show
  1. package/ai-docs/src/52_graphql/10_basics.ts +19 -13
  2. package/ai-docs/src/52_graphql/30_batching.ts +6 -4
  3. package/ai-docs/src/52_graphql/40_aliased-batching.ts +6 -4
  4. package/dist/Array.d.ts +90 -6
  5. package/dist/Array.d.ts.map +1 -1
  6. package/dist/Array.js +4 -2
  7. package/dist/Array.js.map +1 -1
  8. package/dist/Channel.d.ts +6 -1
  9. package/dist/Channel.d.ts.map +1 -1
  10. package/dist/Channel.js +17 -3
  11. package/dist/Channel.js.map +1 -1
  12. package/dist/ConfigProvider.d.ts.map +1 -1
  13. package/dist/ConfigProvider.js +33 -32
  14. package/dist/ConfigProvider.js.map +1 -1
  15. package/dist/Duration.d.ts.map +1 -1
  16. package/dist/Duration.js +2 -5
  17. package/dist/Duration.js.map +1 -1
  18. package/dist/Effect.d.ts +61 -19
  19. package/dist/Effect.d.ts.map +1 -1
  20. package/dist/Effect.js +58 -39
  21. package/dist/Effect.js.map +1 -1
  22. package/dist/FileSystem.d.ts +23 -9
  23. package/dist/FileSystem.d.ts.map +1 -1
  24. package/dist/FileSystem.js.map +1 -1
  25. package/dist/Match.d.ts +20 -6
  26. package/dist/Match.d.ts.map +1 -1
  27. package/dist/Match.js +2 -2
  28. package/dist/Match.js.map +1 -1
  29. package/dist/Path.d.ts +37 -5
  30. package/dist/Path.d.ts.map +1 -1
  31. package/dist/Path.js +36 -5
  32. package/dist/Path.js.map +1 -1
  33. package/dist/Schema.d.ts +7 -2
  34. package/dist/Schema.d.ts.map +1 -1
  35. package/dist/Schema.js +9 -2
  36. package/dist/Schema.js.map +1 -1
  37. package/dist/String.d.ts +12 -0
  38. package/dist/String.d.ts.map +1 -1
  39. package/dist/String.js +17 -4
  40. package/dist/String.js.map +1 -1
  41. package/dist/TxRef.d.ts.map +1 -1
  42. package/dist/TxRef.js +3 -2
  43. package/dist/TxRef.js.map +1 -1
  44. package/dist/ai/AiError.d.ts +10 -10
  45. package/dist/ai/AiError.d.ts.map +1 -1
  46. package/dist/ai/AiError.js +1 -1
  47. package/dist/ai/AiError.js.map +1 -1
  48. package/dist/ai/Decision.d.ts +70 -19
  49. package/dist/ai/Decision.d.ts.map +1 -1
  50. package/dist/ai/Decision.js +37 -3
  51. package/dist/ai/Decision.js.map +1 -1
  52. package/dist/ai/DecisionModel.js +1 -1
  53. package/dist/ai/DecisionModel.js.map +1 -1
  54. package/dist/ai/LanguageModel.d.ts.map +1 -1
  55. package/dist/ai/LanguageModel.js +47 -35
  56. package/dist/ai/LanguageModel.js.map +1 -1
  57. package/dist/ai/McpServer.d.ts +6 -52
  58. package/dist/ai/McpServer.d.ts.map +1 -1
  59. package/dist/ai/McpServer.js +2 -28
  60. package/dist/ai/McpServer.js.map +1 -1
  61. package/dist/ai/Response.d.ts +1 -1
  62. package/dist/cli/Completions.d.ts +5 -0
  63. package/dist/cli/Completions.d.ts.map +1 -1
  64. package/dist/cli/Completions.js.map +1 -1
  65. package/dist/cli/Flag.d.ts +100 -0
  66. package/dist/cli/Flag.d.ts.map +1 -1
  67. package/dist/cli/Flag.js +33 -0
  68. package/dist/cli/Flag.js.map +1 -1
  69. package/dist/cli/Param.d.ts +87 -0
  70. package/dist/cli/Param.d.ts.map +1 -1
  71. package/dist/cli/Param.js +30 -0
  72. package/dist/cli/Param.js.map +1 -1
  73. package/dist/cli/internal/command.d.ts.map +1 -1
  74. package/dist/cli/internal/command.js +2 -1
  75. package/dist/cli/internal/command.js.map +1 -1
  76. package/dist/cli/internal/completions/bash.js +4 -3
  77. package/dist/cli/internal/completions/bash.js.map +1 -1
  78. package/dist/cli/internal/completions/descriptor.js +8 -1
  79. package/dist/cli/internal/completions/descriptor.js.map +1 -1
  80. package/dist/cli/internal/completions/fish.js +4 -4
  81. package/dist/cli/internal/completions/fish.js.map +1 -1
  82. package/dist/cli/internal/completions/powershell.js +3 -2
  83. package/dist/cli/internal/completions/powershell.js.map +1 -1
  84. package/dist/cli/internal/completions/zsh.js +7 -3
  85. package/dist/cli/internal/completions/zsh.js.map +1 -1
  86. package/dist/cli/internal/parser.js +11 -3
  87. package/dist/cli/internal/parser.js.map +1 -1
  88. package/dist/cli/internal/wizard.d.ts.map +1 -1
  89. package/dist/cli/internal/wizard.js +2 -1
  90. package/dist/cli/internal/wizard.js.map +1 -1
  91. package/dist/cluster/ClusterWorkflowEngine.d.ts +1 -0
  92. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  93. package/dist/cluster/ClusterWorkflowEngine.js +67 -13
  94. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  95. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  96. package/dist/cluster/MessageStorage.js +20 -12
  97. package/dist/cluster/MessageStorage.js.map +1 -1
  98. package/dist/cluster/Runners.d.ts.map +1 -1
  99. package/dist/cluster/Runners.js +54 -34
  100. package/dist/cluster/Runners.js.map +1 -1
  101. package/dist/cluster/internal/entityManager.js +1 -1
  102. package/dist/cluster/internal/entityManager.js.map +1 -1
  103. package/dist/encoding/Sse.js +1 -1
  104. package/dist/encoding/Sse.js.map +1 -1
  105. package/dist/graphql/GraphQLClient.d.ts.map +1 -1
  106. package/dist/graphql/GraphQLClient.js +12 -5
  107. package/dist/graphql/GraphQLClient.js.map +1 -1
  108. package/dist/graphql/GraphQLProtocol.d.ts +249 -11
  109. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
  110. package/dist/graphql/GraphQLProtocol.js +98 -8
  111. package/dist/graphql/GraphQLProtocol.js.map +1 -1
  112. package/dist/graphql/GraphQLResolver.d.ts +1 -1
  113. package/dist/graphql/GraphQLResolver.d.ts.map +1 -1
  114. package/dist/graphql/GraphQLResolver.js +46 -16
  115. package/dist/graphql/GraphQLResolver.js.map +1 -1
  116. package/dist/http/FetchHttpClient.d.ts +6 -3
  117. package/dist/http/FetchHttpClient.d.ts.map +1 -1
  118. package/dist/http/FetchHttpClient.js +6 -3
  119. package/dist/http/FetchHttpClient.js.map +1 -1
  120. package/dist/http/FindMyWay/internal/router.js +2 -1
  121. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  122. package/dist/http/Headers.d.ts +3 -3
  123. package/dist/http/Headers.d.ts.map +1 -1
  124. package/dist/http/Headers.js +6 -5
  125. package/dist/http/Headers.js.map +1 -1
  126. package/dist/http/HttpClient.d.ts +12 -0
  127. package/dist/http/HttpClient.d.ts.map +1 -1
  128. package/dist/http/HttpClient.js +14 -1
  129. package/dist/http/HttpClient.js.map +1 -1
  130. package/dist/http/HttpClientError.d.ts.map +1 -1
  131. package/dist/http/HttpClientError.js +8 -6
  132. package/dist/http/HttpClientError.js.map +1 -1
  133. package/dist/http/HttpClientRequest.d.ts +147 -4
  134. package/dist/http/HttpClientRequest.d.ts.map +1 -1
  135. package/dist/http/HttpClientRequest.js +47 -1
  136. package/dist/http/HttpClientRequest.js.map +1 -1
  137. package/dist/http/HttpMiddleware.d.ts +60 -0
  138. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  139. package/dist/http/HttpMiddleware.js +67 -5
  140. package/dist/http/HttpMiddleware.js.map +1 -1
  141. package/dist/http/HttpRouter.d.ts.map +1 -1
  142. package/dist/http/HttpRouter.js +52 -13
  143. package/dist/http/HttpRouter.js.map +1 -1
  144. package/dist/http/HttpServerError.d.ts.map +1 -1
  145. package/dist/http/HttpServerError.js +6 -4
  146. package/dist/http/HttpServerError.js.map +1 -1
  147. package/dist/http/HttpServerRequest.js +2 -2
  148. package/dist/http/HttpServerRequest.js.map +1 -1
  149. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  150. package/dist/http/HttpStaticServer.js +4 -10
  151. package/dist/http/HttpStaticServer.js.map +1 -1
  152. package/dist/http/HttpTraceContext.js +1 -1
  153. package/dist/http/HttpTraceContext.js.map +1 -1
  154. package/dist/http/Url.d.ts.map +1 -1
  155. package/dist/http/Url.js +2 -1
  156. package/dist/http/Url.js.map +1 -1
  157. package/dist/http/UrlParams.d.ts +112 -8
  158. package/dist/http/UrlParams.d.ts.map +1 -1
  159. package/dist/http/UrlParams.js +141 -14
  160. package/dist/http/UrlParams.js.map +1 -1
  161. package/dist/http/internal/compression.d.ts +1 -1
  162. package/dist/http/internal/compression.d.ts.map +1 -1
  163. package/dist/http/internal/compression.js +2 -10
  164. package/dist/http/internal/compression.js.map +1 -1
  165. package/dist/http/internal/compressionBody.d.ts +2 -0
  166. package/dist/http/internal/compressionBody.d.ts.map +1 -0
  167. package/dist/http/internal/compressionBody.js +36 -0
  168. package/dist/http/internal/compressionBody.js.map +1 -0
  169. package/dist/http/internal/decompression.d.ts +2 -0
  170. package/dist/http/internal/decompression.d.ts.map +1 -0
  171. package/dist/http/internal/decompression.js +162 -0
  172. package/dist/http/internal/decompression.js.map +1 -0
  173. package/dist/http/internal/tracing.js +14 -4
  174. package/dist/http/internal/tracing.js.map +1 -1
  175. package/dist/http/internal/urlParams.d.ts +2 -0
  176. package/dist/http/internal/urlParams.d.ts.map +1 -0
  177. package/dist/http/internal/urlParams.js +4 -0
  178. package/dist/http/internal/urlParams.js.map +1 -0
  179. package/dist/http-api/HttpApiClient.d.ts.map +1 -1
  180. package/dist/http-api/HttpApiClient.js +2 -1
  181. package/dist/http-api/HttpApiClient.js.map +1 -1
  182. package/dist/internal/core.js +17 -1
  183. package/dist/internal/core.js.map +1 -1
  184. package/dist/internal/effect.js +5 -3
  185. package/dist/internal/effect.js.map +1 -1
  186. package/dist/internal/graphql.js +12 -2
  187. package/dist/internal/graphql.js.map +1 -1
  188. package/dist/internal/version.js +1 -1
  189. package/dist/observability/Otlp.d.ts +15 -0
  190. package/dist/observability/Otlp.d.ts.map +1 -1
  191. package/dist/observability/Otlp.js +3 -0
  192. package/dist/observability/Otlp.js.map +1 -1
  193. package/dist/observability/OtlpExporter.d.ts +8 -0
  194. package/dist/observability/OtlpExporter.d.ts.map +1 -1
  195. package/dist/observability/OtlpExporter.js +10 -1
  196. package/dist/observability/OtlpExporter.js.map +1 -1
  197. package/dist/observability/OtlpLogger.d.ts +17 -0
  198. package/dist/observability/OtlpLogger.d.ts.map +1 -1
  199. package/dist/observability/OtlpLogger.js +14 -1
  200. package/dist/observability/OtlpLogger.js.map +1 -1
  201. package/dist/observability/OtlpMetrics.d.ts +17 -0
  202. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  203. package/dist/observability/OtlpMetrics.js +13 -0
  204. package/dist/observability/OtlpMetrics.js.map +1 -1
  205. package/dist/observability/OtlpTracer.d.ts +17 -0
  206. package/dist/observability/OtlpTracer.d.ts.map +1 -1
  207. package/dist/observability/OtlpTracer.js +15 -2
  208. package/dist/observability/OtlpTracer.js.map +1 -1
  209. package/dist/reactivity/AsyncResult.d.ts +6 -0
  210. package/dist/reactivity/AsyncResult.d.ts.map +1 -1
  211. package/dist/reactivity/AsyncResult.js +27 -0
  212. package/dist/reactivity/AsyncResult.js.map +1 -1
  213. package/dist/reactivity/AtomRegistry.js +3 -1
  214. package/dist/reactivity/AtomRegistry.js.map +1 -1
  215. package/dist/rpc/RpcClient.d.ts.map +1 -1
  216. package/dist/rpc/RpcClient.js +6 -1
  217. package/dist/rpc/RpcClient.js.map +1 -1
  218. package/dist/sql/SqlClient.d.ts +8 -7
  219. package/dist/sql/SqlClient.d.ts.map +1 -1
  220. package/dist/sql/SqlClient.js +8 -7
  221. package/dist/sql/SqlClient.js.map +1 -1
  222. package/dist/testing/TestClock.d.ts +16 -3
  223. package/dist/testing/TestClock.d.ts.map +1 -1
  224. package/dist/testing/TestClock.js +112 -22
  225. package/dist/testing/TestClock.js.map +1 -1
  226. package/dist/workflow/Activity.js +1 -0
  227. package/dist/workflow/Activity.js.map +1 -1
  228. package/dist/workflow/Workflow.d.ts +22 -0
  229. package/dist/workflow/Workflow.d.ts.map +1 -1
  230. package/dist/workflow/Workflow.js +15 -3
  231. package/dist/workflow/Workflow.js.map +1 -1
  232. package/dist/workflow/WorkflowEngine.d.ts +20 -1
  233. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  234. package/dist/workflow/WorkflowEngine.js +35 -21
  235. package/dist/workflow/WorkflowEngine.js.map +1 -1
  236. package/package.json +1 -1
  237. package/src/Array.ts +95 -6
  238. package/src/Channel.ts +24 -3
  239. package/src/ConfigProvider.ts +40 -44
  240. package/src/Duration.ts +4 -5
  241. package/src/Effect.ts +140 -53
  242. package/src/FileSystem.ts +23 -9
  243. package/src/Match.ts +65 -12
  244. package/src/Path.ts +52 -7
  245. package/src/Schema.ts +17 -4
  246. package/src/String.ts +17 -4
  247. package/src/TxRef.ts +4 -2
  248. package/src/ai/AiError.ts +3 -1
  249. package/src/ai/Decision.ts +70 -21
  250. package/src/ai/DecisionModel.ts +1 -1
  251. package/src/ai/LanguageModel.ts +57 -51
  252. package/src/ai/McpServer.ts +15 -55
  253. package/src/cli/Completions.ts +5 -0
  254. package/src/cli/Flag.ts +101 -0
  255. package/src/cli/Param.ts +96 -0
  256. package/src/cli/internal/command.ts +2 -1
  257. package/src/cli/internal/completions/bash.ts +4 -3
  258. package/src/cli/internal/completions/descriptor.ts +10 -1
  259. package/src/cli/internal/completions/fish.ts +4 -4
  260. package/src/cli/internal/completions/powershell.ts +5 -2
  261. package/src/cli/internal/completions/zsh.ts +7 -3
  262. package/src/cli/internal/parser.ts +12 -3
  263. package/src/cli/internal/wizard.ts +2 -1
  264. package/src/cluster/ClusterWorkflowEngine.ts +92 -24
  265. package/src/cluster/MessageStorage.ts +14 -3
  266. package/src/cluster/Runners.ts +88 -66
  267. package/src/cluster/internal/entityManager.ts +1 -1
  268. package/src/encoding/Sse.ts +1 -1
  269. package/src/graphql/GraphQLClient.ts +17 -5
  270. package/src/graphql/GraphQLProtocol.ts +274 -20
  271. package/src/graphql/GraphQLResolver.ts +45 -17
  272. package/src/http/FetchHttpClient.ts +6 -3
  273. package/src/http/FindMyWay/internal/router.ts +2 -1
  274. package/src/http/Headers.ts +6 -9
  275. package/src/http/HttpClient.ts +18 -1
  276. package/src/http/HttpClientError.ts +10 -6
  277. package/src/http/HttpClientRequest.ts +173 -7
  278. package/src/http/HttpMiddleware.ts +85 -5
  279. package/src/http/HttpRouter.ts +60 -13
  280. package/src/http/HttpServerError.ts +8 -4
  281. package/src/http/HttpServerRequest.ts +2 -2
  282. package/src/http/HttpStaticServer.ts +4 -10
  283. package/src/http/HttpTraceContext.ts +1 -1
  284. package/src/http/Url.ts +2 -1
  285. package/src/http/UrlParams.ts +225 -26
  286. package/src/http/internal/compression.ts +3 -17
  287. package/src/http/internal/compressionBody.ts +56 -0
  288. package/src/http/internal/decompression.ts +226 -0
  289. package/src/http/internal/tracing.ts +21 -4
  290. package/src/http/internal/urlParams.ts +5 -0
  291. package/src/http-api/HttpApiClient.ts +2 -1
  292. package/src/internal/core.ts +21 -1
  293. package/src/internal/effect.ts +5 -4
  294. package/src/internal/graphql.ts +30 -7
  295. package/src/internal/version.ts +1 -1
  296. package/src/observability/Otlp.ts +18 -0
  297. package/src/observability/OtlpExporter.ts +14 -3
  298. package/src/observability/OtlpLogger.ts +31 -2
  299. package/src/observability/OtlpMetrics.ts +30 -1
  300. package/src/observability/OtlpTracer.ts +32 -3
  301. package/src/reactivity/AsyncResult.ts +31 -0
  302. package/src/reactivity/AtomRegistry.ts +3 -1
  303. package/src/rpc/RpcClient.ts +6 -1
  304. package/src/sql/SqlClient.ts +9 -7
  305. package/src/testing/TestClock.ts +144 -29
  306. package/src/workflow/Activity.ts +1 -0
  307. package/src/workflow/Workflow.ts +31 -5
  308. package/src/workflow/WorkflowEngine.ts +60 -22
@@ -378,15 +378,18 @@ const makeMethod = <Op extends GraphQL.Any>(
378
378
  // Type boundary: a middleware sees the errors of the middleware further in
379
379
  // as `Types.unhandled` (as in `RpcMiddleware`), and none of their
380
380
  // requirements. Those errors are part of `GraphQL.Error<Op>`, so the chain
381
- // restates its type here. Force POST only at the transport boundary.
381
+ // restates its type here. Force POST and the resolved URL apply only at the
382
+ // transport boundary.
382
383
  const execute = (
383
384
  index: number,
384
385
  request: GraphQLRequest,
385
- forcePost: boolean
386
+ forcePost: boolean,
387
+ resolvedUrl: internalGraphQL.ResolvedUrl
386
388
  ): Effect.Effect<ExecutionResult, GraphQL.Error<Op>, GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>> =>
387
389
  index === chain.length
388
390
  ? protocol.execute(request).pipe(
389
391
  forcePost ? Effect.provideService(internalGraphQL.ForcePost, true) : identity,
392
+ resolvedUrl === undefined ? identity : Effect.provideService(internalGraphQL.ResolvedUrl, resolvedUrl),
390
393
  Effect.mapError(fail),
391
394
  Effect.flatMap(decodeResponse)
392
395
  )
@@ -394,12 +397,21 @@ const makeMethod = <Op extends GraphQL.Any>(
394
397
  operation,
395
398
  request,
396
399
  next: (request) =>
397
- execute(index + 1, request, forcePost) as Effect.Effect<
400
+ execute(index + 1, request, forcePost, resolvedUrl) as Effect.Effect<
398
401
  ExecutionResult,
399
402
  GraphQLClientError | Types.unhandled
400
403
  >
401
404
  }) as Effect.Effect<ExecutionResult, GraphQL.Error<Op>, GraphQLMiddleware.Requires<GraphQL.Middleware<Op>>>
402
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
+
403
415
  const subscribe = (
404
416
  index: number,
405
417
  request: GraphQLRequest
@@ -441,7 +453,7 @@ const makeMethod = <Op extends GraphQL.Any>(
441
453
  Exclude<GraphQL.Services<Op>, R2>
442
454
  > {
443
455
  return buildRequest(variables, options).pipe(
444
- Effect.flatMap((request) => execute(0, request, false)),
456
+ Effect.flatMap((request) => run(request, false)),
445
457
  Effect.flatMap(
446
458
  (result): Effect.Effect<
447
459
  GraphQL.Result<Op> | PartialResult<GraphQL.Result<Op>>,
@@ -452,7 +464,7 @@ const makeMethod = <Op extends GraphQL.Any>(
452
464
  Effect.provideContext(callContext(options))
453
465
  )
454
466
  }
455
- internalGraphQL.rawExecutors.set(method, (request) => execute(0, request, true))
467
+ internalGraphQL.rawExecutors.set(method, { execute: (request) => run(request, true), url: protocol.url })
456
468
  return Object.assign(method, { operation })
457
469
  }
458
470
 
@@ -20,6 +20,7 @@ import * as Duration from "../Duration.ts"
20
20
  import * as Effect from "../Effect.ts"
21
21
  import * as Sse from "../encoding/Sse.ts"
22
22
  import * as Fiber from "../Fiber.ts"
23
+ import { constant } from "../Function.ts"
23
24
  import * as Headers from "../http/Headers.ts"
24
25
  import * as HttpClient from "../http/HttpClient.ts"
25
26
  import * as HttpClientError from "../http/HttpClientError.ts"
@@ -152,8 +153,33 @@ export class GraphQLProtocol extends Context.Service<GraphQLProtocol, {
152
153
  readonly status?: number | undefined
153
154
  readonly headers?: Headers.Headers | undefined
154
155
  }, TransportError>
156
+ /**
157
+ * Resolves the transport URL for resolver batching. Set by `layerHttp`;
158
+ * optional for custom transports.
159
+ */
160
+ readonly url?:
161
+ | ((options: { readonly kind: Kind; readonly operationName: string }) => Effect.Effect<string>)
162
+ | undefined
155
163
  }>()("effect/graphql/GraphQLProtocol") {}
156
164
 
165
+ /**
166
+ * The URL of an HTTP transport: fixed, resolved for each operation from the
167
+ * calling context, `kind` and `operationName`, or an `Effect` with no errors
168
+ * or requirements. See {@link layerHttp}.
169
+ *
170
+ * @stability experimental
171
+ * @category models
172
+ * @since 4.0.0
173
+ */
174
+ export type Url =
175
+ | string
176
+ | Effect.Effect<string>
177
+ | ((options: {
178
+ readonly context: Context.Context<never>
179
+ readonly kind: Kind
180
+ readonly operationName: string
181
+ }) => string)
182
+
157
183
  const graphqlResponseContentType = "application/graphql-response+json"
158
184
  const jsonContentType = "application/json"
159
185
  const eventStreamContentType = "text/event-stream"
@@ -318,7 +344,7 @@ const sseEvents = (
318
344
  })
319
345
 
320
346
  interface HttpOptions<R = never> {
321
- readonly url: string
347
+ readonly url: Url
322
348
  readonly queryMethod?: "GET" | "POST" | undefined
323
349
  readonly subscriptions?: {
324
350
  readonly webSocket: WebSocketOptions<R>
@@ -330,23 +356,29 @@ const makeHttpWith = <R>(options: HttpOptions<R>): Effect.Effect<
330
356
  never,
331
357
  HttpClient.HttpClient | Socket.WebSocketConstructor | Scope.Scope | R
332
358
  > => {
359
+ const resolve = urlResolver(options.url)
360
+ // A resolver batch carries the URL it was grouped by.
361
+ const urlOf = (request: GraphQLRequest) =>
362
+ internalGraphQL.ResolvedUrl.use((resolved) =>
363
+ resolved?.resolve === resolve ? Effect.succeed(resolved.url) : resolve(request)
364
+ )
333
365
  const http = Clock.clockWith((clock) =>
334
366
  HttpClient.HttpClient.useSync((client) => {
335
367
  const execute = (request: GraphQLRequest) =>
336
- Effect.flatMap(Effect.service(internalGraphQL.ForcePost), (forcePost) => {
368
+ Effect.flatMap(Effect.zip(Effect.service(internalGraphQL.ForcePost), urlOf(request)), ([forcePost, url]) => {
337
369
  const toRequest = options.queryMethod === "GET" && request.kind === "query" && !forcePost
338
370
  ? getRequest
339
371
  : postRequest
340
372
  return client.execute(
341
- toRequest(options.url, request, `${graphqlResponseContentType}, ${jsonContentType}`)
373
+ toRequest(url, request, `${graphqlResponseContentType}, ${jsonContentType}`)
342
374
  ).pipe(
343
375
  Effect.flatMap(readAnswer),
344
376
  fromHttpClientError(clock)
345
377
  )
346
378
  })
347
379
  const subscribe = (request: GraphQLRequest) =>
348
- Stream.unwrap(Effect.suspend(() =>
349
- client.execute(postRequest(options.url, request, eventStreamContentType)).pipe(
380
+ Stream.unwrap(Effect.flatMap(urlOf(request), (url) =>
381
+ client.execute(postRequest(url, request, eventStreamContentType)).pipe(
350
382
  Effect.flatMap((response) => {
351
383
  const contentType = contentTypeOf(response)
352
384
  if (contentType.includes(eventStreamContentType) && response.status >= 200 && response.status < 300) {
@@ -357,9 +389,8 @@ const makeHttpWith = <R>(options: HttpOptions<R>): Effect.Effect<
357
389
  return Effect.map(readAnswer(response), Stream.succeed)
358
390
  }),
359
391
  fromHttpClientError(clock)
360
- )
361
- ))
362
- return GraphQLProtocol.of({ execute, subscribe })
392
+ )))
393
+ return GraphQLProtocol.of({ execute, subscribe, url: resolve })
363
394
  })
364
395
  )
365
396
  const webSocket = options.subscriptions?.webSocket
@@ -368,10 +399,17 @@ const makeHttpWith = <R>(options: HttpOptions<R>): Effect.Effect<
368
399
  : Effect.zipWith(
369
400
  http,
370
401
  makeWebSocket(webSocket),
371
- (http, ws) => GraphQLProtocol.of({ execute: http.execute, subscribe: ws.subscribe })
402
+ (http, ws) => GraphQLProtocol.of({ execute: http.execute, subscribe: ws.subscribe, url: http.url })
372
403
  )
373
404
  }
374
405
 
406
+ const urlResolver = (url: Url): NonNullable<GraphQLProtocol["Service"]["url"]> => {
407
+ if (typeof url === "string") return constant(Effect.succeed(url))
408
+ if (Effect.isEffect(url)) return constant(url)
409
+ return ({ kind, operationName }) =>
410
+ Effect.contextWith((context: Context.Context<never>) => Effect.succeed(url({ context, kind, operationName })))
411
+ }
412
+
375
413
  /**
376
414
  * Builds the HTTP transport as an effect, for composing transports by hand.
377
415
  * Most code uses {@link layerHttp}.
@@ -380,9 +418,9 @@ const makeHttpWith = <R>(options: HttpOptions<R>): Effect.Effect<
380
418
  *
381
419
  * Takes the same options as {@link layerHttp}, including `queryMethod`.
382
420
  * Without `subscriptions`, subscriptions use graphql-sse distinct mode on
383
- * `url`. With `subscriptions.webSocket`, they go over graphql-ws instead, and
384
- * the effect also builds the graphql-ws transport, so it needs a
385
- * `Socket.WebSocketConstructor` and a `Scope`.
421
+ * `url`. With `subscriptions.webSocket`, `url` must be a string, subscriptions
422
+ * go over graphql-ws instead, and the effect also builds the graphql-ws
423
+ * transport, so it needs a `Socket.WebSocketConstructor` and a `Scope`.
386
424
  *
387
425
  * @stability experimental
388
426
  * @category constructors
@@ -397,9 +435,9 @@ export const makeHttp: {
397
435
  *
398
436
  * Takes the same options as {@link layerHttp}, including `queryMethod`.
399
437
  * Without `subscriptions`, subscriptions use graphql-sse distinct mode on
400
- * `url`. With `subscriptions.webSocket`, they go over graphql-ws instead, and
401
- * the effect also builds the graphql-ws transport, so it needs a
402
- * `Socket.WebSocketConstructor` and a `Scope`.
438
+ * `url`. With `subscriptions.webSocket`, `url` must be a string, subscriptions
439
+ * go over graphql-ws instead, and the effect also builds the graphql-ws
440
+ * transport, so it needs a `Socket.WebSocketConstructor` and a `Scope`.
403
441
  *
404
442
  * @stability experimental
405
443
  * @category constructors
@@ -433,9 +471,9 @@ export const makeHttp: {
433
471
  *
434
472
  * Takes the same options as {@link layerHttp}, including `queryMethod`.
435
473
  * Without `subscriptions`, subscriptions use graphql-sse distinct mode on
436
- * `url`. With `subscriptions.webSocket`, they go over graphql-ws instead, and
437
- * the effect also builds the graphql-ws transport, so it needs a
438
- * `Socket.WebSocketConstructor` and a `Scope`.
474
+ * `url`. With `subscriptions.webSocket`, `url` must be a string, subscriptions
475
+ * go over graphql-ws instead, and the effect also builds the graphql-ws
476
+ * transport, so it needs a `Socket.WebSocketConstructor` and a `Scope`.
439
477
  *
440
478
  * @stability experimental
441
479
  * @category constructors
@@ -443,7 +481,7 @@ export const makeHttp: {
443
481
  */
444
482
  (
445
483
  options: {
446
- readonly url: string
484
+ readonly url: Url
447
485
  readonly queryMethod?: "GET" | "POST" | undefined
448
486
  readonly subscriptions?: undefined
449
487
  }
@@ -498,6 +536,28 @@ export const makeHttp: {
498
536
  * With `subscriptions: { webSocket }`, subscriptions go over graphql-ws as in
499
537
  * {@link layerWebSocket}, and the layer needs a `Socket.WebSocketConstructor`.
500
538
  *
539
+ * To route each query, mutation and graphql-sse subscription, `url` can be a
540
+ * function of the calling fiber's context (including the call's `context`
541
+ * option), `kind` and `operationName`, or an `Effect<string>` with no errors
542
+ * or requirements that runs in that context. Use a defaulted
543
+ * `Context.Reference` to select the server. Reading a missing service with
544
+ * `Context.get` is a defect, not a `TransportError`. Neither form can be
545
+ * combined with `subscriptions.webSocket`: the socket URL is fixed at
546
+ * connection time.
547
+ *
548
+ * **Gotchas**
549
+ *
550
+ * `GraphQLResolver` resolves each lookup's URL once, before middleware run and
551
+ * before the batched request exists, then sends each batch to the URL it was
552
+ * grouped by. A single call resolves it in the transport, after middleware.
553
+ * So the URL must depend only on the context and the operation, and
554
+ * middleware must not change `kind` or `operationName` or provide services
555
+ * the URL reads.
556
+ *
557
+ * `RequestResolver.withCache` and `RequestResolver.persisted` key on the lookup,
558
+ * not the URL, so results can leak across servers. For caching, use a separate
559
+ * client and resolver per server, for example with `LayerMap`.
560
+ *
501
561
  * **Example** (Building a client over HTTP)
502
562
  *
503
563
  * ```ts import.meta.vitest
@@ -571,6 +631,56 @@ export const makeHttp: {
571
631
  * await Effect.runPromise(program) // => { viewer: { login: "GET draft-token" } }
572
632
  * ```
573
633
  *
634
+ * **Example** (One client for github.com, GHE.com and GitHub Enterprise Server)
635
+ *
636
+ * ```ts import.meta.vitest
637
+ * import { Context, Effect, Layer, Schema } from "effect"
638
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
639
+ * import { HttpClient, HttpClientResponse } from "effect/http"
640
+ *
641
+ * const Viewer = GraphQL.query("Viewer", {
642
+ * document: "query Viewer{viewer{login}}",
643
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
644
+ * })
645
+ *
646
+ * const GitHubHost = Context.Reference<string>("GitHubHost", { defaultValue: () => "github.com" })
647
+ *
648
+ * const Protocol = GraphQLProtocol.layerHttp({
649
+ * url: ({ context }) => {
650
+ * const host = Context.get(context, GitHubHost)
651
+ * if (host === "github.com") return "https://api.github.com/graphql"
652
+ * if (host.endsWith(".ghe.com")) return `https://api.${host}/graphql`
653
+ * return `https://${host}/api/graphql`
654
+ * }
655
+ * })
656
+ *
657
+ * // Echo the request URL instead of calling GitHub.
658
+ * const FakeServer = Layer.succeed(
659
+ * HttpClient.HttpClient,
660
+ * HttpClient.make((request, url) =>
661
+ * Effect.succeed(HttpClientResponse.fromWeb(
662
+ * request,
663
+ * new Response(JSON.stringify({ data: { viewer: { login: url.href } } }), {
664
+ * headers: { "content-type": "application/graphql-response+json" }
665
+ * })
666
+ * ))
667
+ * )
668
+ * )
669
+ *
670
+ * const program = Effect.gen(function*() {
671
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer))
672
+ * const dotcom = yield* client.Viewer()
673
+ * const cloud = yield* client.Viewer().pipe(Effect.provideService(GitHubHost, "acme.ghe.com"))
674
+ * const server = yield* client.Viewer(undefined, { context: Context.make(GitHubHost, "github.example.com") })
675
+ * return [dotcom, cloud, server].map((result) => result.viewer.login)
676
+ * }).pipe(
677
+ * Effect.provide(Protocol),
678
+ * Effect.provide(FakeServer)
679
+ * )
680
+ *
681
+ * await Effect.runPromise(program) // => ["https://api.github.com/graphql", "https://api.acme.ghe.com/graphql", "https://github.example.com/api/graphql"]
682
+ * ```
683
+ *
574
684
  * **Example** (Subscriptions over graphql-ws, everything else over POST)
575
685
  *
576
686
  * ```ts import.meta.vitest
@@ -641,6 +751,28 @@ export const layerHttp: {
641
751
  * With `subscriptions: { webSocket }`, subscriptions go over graphql-ws as in
642
752
  * {@link layerWebSocket}, and the layer needs a `Socket.WebSocketConstructor`.
643
753
  *
754
+ * To route each query, mutation and graphql-sse subscription, `url` can be a
755
+ * function of the calling fiber's context (including the call's `context`
756
+ * option), `kind` and `operationName`, or an `Effect<string>` with no errors
757
+ * or requirements that runs in that context. Use a defaulted
758
+ * `Context.Reference` to select the server. Reading a missing service with
759
+ * `Context.get` is a defect, not a `TransportError`. Neither form can be
760
+ * combined with `subscriptions.webSocket`: the socket URL is fixed at
761
+ * connection time.
762
+ *
763
+ * **Gotchas**
764
+ *
765
+ * `GraphQLResolver` resolves each lookup's URL once, before middleware run and
766
+ * before the batched request exists, then sends each batch to the URL it was
767
+ * grouped by. A single call resolves it in the transport, after middleware.
768
+ * So the URL must depend only on the context and the operation, and
769
+ * middleware must not change `kind` or `operationName` or provide services
770
+ * the URL reads.
771
+ *
772
+ * `RequestResolver.withCache` and `RequestResolver.persisted` key on the lookup,
773
+ * not the URL, so results can leak across servers. For caching, use a separate
774
+ * client and resolver per server, for example with `LayerMap`.
775
+ *
644
776
  * **Example** (Building a client over HTTP)
645
777
  *
646
778
  * ```ts import.meta.vitest
@@ -714,6 +846,56 @@ export const layerHttp: {
714
846
  * await Effect.runPromise(program) // => { viewer: { login: "GET draft-token" } }
715
847
  * ```
716
848
  *
849
+ * **Example** (One client for github.com, GHE.com and GitHub Enterprise Server)
850
+ *
851
+ * ```ts import.meta.vitest
852
+ * import { Context, Effect, Layer, Schema } from "effect"
853
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
854
+ * import { HttpClient, HttpClientResponse } from "effect/http"
855
+ *
856
+ * const Viewer = GraphQL.query("Viewer", {
857
+ * document: "query Viewer{viewer{login}}",
858
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
859
+ * })
860
+ *
861
+ * const GitHubHost = Context.Reference<string>("GitHubHost", { defaultValue: () => "github.com" })
862
+ *
863
+ * const Protocol = GraphQLProtocol.layerHttp({
864
+ * url: ({ context }) => {
865
+ * const host = Context.get(context, GitHubHost)
866
+ * if (host === "github.com") return "https://api.github.com/graphql"
867
+ * if (host.endsWith(".ghe.com")) return `https://api.${host}/graphql`
868
+ * return `https://${host}/api/graphql`
869
+ * }
870
+ * })
871
+ *
872
+ * // Echo the request URL instead of calling GitHub.
873
+ * const FakeServer = Layer.succeed(
874
+ * HttpClient.HttpClient,
875
+ * HttpClient.make((request, url) =>
876
+ * Effect.succeed(HttpClientResponse.fromWeb(
877
+ * request,
878
+ * new Response(JSON.stringify({ data: { viewer: { login: url.href } } }), {
879
+ * headers: { "content-type": "application/graphql-response+json" }
880
+ * })
881
+ * ))
882
+ * )
883
+ * )
884
+ *
885
+ * const program = Effect.gen(function*() {
886
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer))
887
+ * const dotcom = yield* client.Viewer()
888
+ * const cloud = yield* client.Viewer().pipe(Effect.provideService(GitHubHost, "acme.ghe.com"))
889
+ * const server = yield* client.Viewer(undefined, { context: Context.make(GitHubHost, "github.example.com") })
890
+ * return [dotcom, cloud, server].map((result) => result.viewer.login)
891
+ * }).pipe(
892
+ * Effect.provide(Protocol),
893
+ * Effect.provide(FakeServer)
894
+ * )
895
+ *
896
+ * await Effect.runPromise(program) // => ["https://api.github.com/graphql", "https://api.acme.ghe.com/graphql", "https://github.example.com/api/graphql"]
897
+ * ```
898
+ *
717
899
  * **Example** (Subscriptions over graphql-ws, everything else over POST)
718
900
  *
719
901
  * ```ts import.meta.vitest
@@ -799,6 +981,28 @@ export const layerHttp: {
799
981
  * With `subscriptions: { webSocket }`, subscriptions go over graphql-ws as in
800
982
  * {@link layerWebSocket}, and the layer needs a `Socket.WebSocketConstructor`.
801
983
  *
984
+ * To route each query, mutation and graphql-sse subscription, `url` can be a
985
+ * function of the calling fiber's context (including the call's `context`
986
+ * option), `kind` and `operationName`, or an `Effect<string>` with no errors
987
+ * or requirements that runs in that context. Use a defaulted
988
+ * `Context.Reference` to select the server. Reading a missing service with
989
+ * `Context.get` is a defect, not a `TransportError`. Neither form can be
990
+ * combined with `subscriptions.webSocket`: the socket URL is fixed at
991
+ * connection time.
992
+ *
993
+ * **Gotchas**
994
+ *
995
+ * `GraphQLResolver` resolves each lookup's URL once, before middleware run and
996
+ * before the batched request exists, then sends each batch to the URL it was
997
+ * grouped by. A single call resolves it in the transport, after middleware.
998
+ * So the URL must depend only on the context and the operation, and
999
+ * middleware must not change `kind` or `operationName` or provide services
1000
+ * the URL reads.
1001
+ *
1002
+ * `RequestResolver.withCache` and `RequestResolver.persisted` key on the lookup,
1003
+ * not the URL, so results can leak across servers. For caching, use a separate
1004
+ * client and resolver per server, for example with `LayerMap`.
1005
+ *
802
1006
  * **Example** (Building a client over HTTP)
803
1007
  *
804
1008
  * ```ts import.meta.vitest
@@ -872,6 +1076,56 @@ export const layerHttp: {
872
1076
  * await Effect.runPromise(program) // => { viewer: { login: "GET draft-token" } }
873
1077
  * ```
874
1078
  *
1079
+ * **Example** (One client for github.com, GHE.com and GitHub Enterprise Server)
1080
+ *
1081
+ * ```ts import.meta.vitest
1082
+ * import { Context, Effect, Layer, Schema } from "effect"
1083
+ * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLProtocol } from "effect/graphql"
1084
+ * import { HttpClient, HttpClientResponse } from "effect/http"
1085
+ *
1086
+ * const Viewer = GraphQL.query("Viewer", {
1087
+ * document: "query Viewer{viewer{login}}",
1088
+ * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) })
1089
+ * })
1090
+ *
1091
+ * const GitHubHost = Context.Reference<string>("GitHubHost", { defaultValue: () => "github.com" })
1092
+ *
1093
+ * const Protocol = GraphQLProtocol.layerHttp({
1094
+ * url: ({ context }) => {
1095
+ * const host = Context.get(context, GitHubHost)
1096
+ * if (host === "github.com") return "https://api.github.com/graphql"
1097
+ * if (host.endsWith(".ghe.com")) return `https://api.${host}/graphql`
1098
+ * return `https://${host}/api/graphql`
1099
+ * }
1100
+ * })
1101
+ *
1102
+ * // Echo the request URL instead of calling GitHub.
1103
+ * const FakeServer = Layer.succeed(
1104
+ * HttpClient.HttpClient,
1105
+ * HttpClient.make((request, url) =>
1106
+ * Effect.succeed(HttpClientResponse.fromWeb(
1107
+ * request,
1108
+ * new Response(JSON.stringify({ data: { viewer: { login: url.href } } }), {
1109
+ * headers: { "content-type": "application/graphql-response+json" }
1110
+ * })
1111
+ * ))
1112
+ * )
1113
+ * )
1114
+ *
1115
+ * const program = Effect.gen(function*() {
1116
+ * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer))
1117
+ * const dotcom = yield* client.Viewer()
1118
+ * const cloud = yield* client.Viewer().pipe(Effect.provideService(GitHubHost, "acme.ghe.com"))
1119
+ * const server = yield* client.Viewer(undefined, { context: Context.make(GitHubHost, "github.example.com") })
1120
+ * return [dotcom, cloud, server].map((result) => result.viewer.login)
1121
+ * }).pipe(
1122
+ * Effect.provide(Protocol),
1123
+ * Effect.provide(FakeServer)
1124
+ * )
1125
+ *
1126
+ * await Effect.runPromise(program) // => ["https://api.github.com/graphql", "https://api.acme.ghe.com/graphql", "https://github.example.com/api/graphql"]
1127
+ * ```
1128
+ *
875
1129
  * **Example** (Subscriptions over graphql-ws, everything else over POST)
876
1130
  *
877
1131
  * ```ts import.meta.vitest
@@ -899,7 +1153,7 @@ export const layerHttp: {
899
1153
  */
900
1154
  (
901
1155
  options: {
902
- readonly url: string
1156
+ readonly url: Url
903
1157
  readonly queryMethod?: "GET" | "POST" | undefined
904
1158
  readonly subscriptions?: undefined
905
1159
  }
@@ -9,12 +9,15 @@
9
9
  * in separate batches. Compose `RequestResolver.setDelay` and
10
10
  * `RequestResolver.batchN` with `.pipe` to control batching.
11
11
  *
12
+ * Lookups with different protocol URLs are batched separately. See
13
+ * `GraphQLProtocol.layerHttp` for middleware and caching constraints.
14
+ *
12
15
  * @stability experimental
13
16
  * @since 4.0.0
14
17
  */
15
18
  import * as Arr from "../Array.ts"
16
19
  import * as Cause from "../Cause.ts"
17
- import type * as Context from "../Context.ts"
20
+ import * as Context from "../Context.ts"
18
21
  import * as Effect from "../Effect.ts"
19
22
  import * as Exit from "../Exit.ts"
20
23
  import { dual } from "../Function.ts"
@@ -556,6 +559,7 @@ export const ordered = <
556
559
  const path = options.path as ReadonlyArray<string>
557
560
  const codec = internalGraphQL.makeCodec(method.operation)
558
561
  return makeResolver(
562
+ method,
559
563
  options.batchKey,
560
564
  (entries) =>
561
565
  Effect.flatMap(callBatch(method, entries, options.variables), ({ keys, targets, result }) => {
@@ -689,6 +693,7 @@ export const findById = <
689
693
  const path = options.path as ReadonlyArray<string>
690
694
  const codec = internalGraphQL.makeCodec(method.operation)
691
695
  return makeResolver(
696
+ method,
692
697
  options.batchKey,
693
698
  (entries) =>
694
699
  Effect.flatMap(callBatch(method, entries, options.variables), ({ keys, targets, result }) => {
@@ -832,7 +837,7 @@ export const aliased = <Op extends GraphQL.Any>(
832
837
  }
833
838
  const codec = internalGraphQL.makeCodec(operation)
834
839
  // The registered hook uses this operation's middleware errors and services.
835
- const execute = internalGraphQL.rawExecutors.get(method) as
840
+ const execute = internalGraphQL.rawExecutors.get(method)?.execute as
836
841
  | ((request: GraphQLRequest) => Effect.Effect<ExecutionResult, GraphQL.Error<Op>, GraphQL.Services<Op>>)
837
842
  | undefined
838
843
  // Generic conditional parameters prevent calling the strict overload directly.
@@ -840,7 +845,7 @@ export const aliased = <Op extends GraphQL.Any>(
840
845
  variables: GraphQL.Variables<Op>
841
846
  ) => Effect.Effect<GraphQL.Result<Op>, GraphQL.Error<Op>, GraphQL.Services<Op>>
842
847
 
843
- return makeResolver<AliasedRequest<Op>>(options?.batchKey, (entries) =>
848
+ return makeResolver<AliasedRequest<Op>>(method, options?.batchKey, (entries) =>
844
849
  Effect.flatMap(
845
850
  Effect.forEach(entries, (entry) => Effect.exit(codec.encodeVariables(entry.request.key))),
846
851
  (encoded) => {
@@ -891,7 +896,7 @@ export const aliased = <Op extends GraphQL.Any>(
891
896
  })
892
897
  )
893
898
  }
894
- ).pipe(Effect.provideContext(entries[0].context))).pipe(RequestResolver.batchN(10))
899
+ )).pipe(RequestResolver.batchN(10))
895
900
  }
896
901
 
897
902
  type AliasedRequest<Op extends GraphQL.Any> = KeyRequest<
@@ -914,20 +919,46 @@ interface Slice<Op extends GraphQL.Any> {
914
919
  const constNoSuchElement = Exit.fail(new Cause.NoSuchElementError())
915
920
 
916
921
  /**
917
- * Without `batchKey`, all entries share one batch. With it, each batch holds
918
- * entries with equal keys, so the first entry's context stands for the batch.
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.
919
925
  */
920
926
  const makeResolver = <A extends Request.Any>(
927
+ method: OperationMethod<any>,
921
928
  batchKey: ((context: Context.Context<any>) => unknown) | undefined,
922
- runAll: (entries: Arr.NonEmptyArray<Request.Entry<A>>) => Effect.Effect<void, Request.Error<A>>
923
- ): RequestResolver.RequestResolver<A> =>
924
- batchKey === undefined
925
- ? RequestResolver.make(runAll)
926
- : RequestResolver.makeGrouped({ key: (entry) => batchKey(entry.context), resolver: runAll })
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
+ }
927
958
 
928
959
  /**
929
- * Calls the method in the first entry's context with deduplicated keys.
930
- * Each target completes all entries sharing its key.
960
+ * Calls the method with deduplicated keys. Each target completes all entries
961
+ * sharing its key.
931
962
  */
932
963
  const callBatch = <Op extends GraphQL.Any, K>(
933
964
  method: OperationMethod<Op>,
@@ -959,10 +990,7 @@ const callBatch = <Op extends GraphQL.Any, K>(
959
990
  variables: GraphQL.Variables<Op>,
960
991
  options: { readonly partial: true }
961
992
  ) => Effect.Effect<PartialResult<GraphQL.Result<Op>>, GraphQL.Error<Op>, GraphQL.Services<Op>>
962
- return call(variables(keys), { partial: true }).pipe(
963
- Effect.map((result) => ({ keys, targets, result })),
964
- Effect.provideContext(entries[0].context)
965
- )
993
+ return Effect.map(call(variables(keys), { partial: true }), (result) => ({ keys, targets, result }))
966
994
  }
967
995
 
968
996
  // Fails the batch with the response's errors and transport metadata.
@@ -115,12 +115,15 @@ const fetch: HttpClient.HttpClient = HttpClient.make((request, url, signal, fibe
115
115
  * **Gotchas**
116
116
  *
117
117
  * Fetch behavior comes from the runtime's implementation, so CORS, cookies,
118
- * redirects, abort handling, and streaming support can vary by platform. Stream
119
- * request bodies are sent as Web streams with `duplex: "half"`, and any
120
- * `content-length` header is removed before calling `fetch`.
118
+ * redirects, abort handling, and streaming support can vary by platform. Trace
119
+ * propagation is on by default, and a browser preflight fails unless the
120
+ * target allows `b3` and `traceparent`. Stream request bodies are sent as Web
121
+ * streams with `duplex: "half"`, and any `content-length` header is removed
122
+ * before calling `fetch`.
121
123
  *
122
124
  * @see {@link Fetch} for supplying the fetch implementation used by this layer
123
125
  * @see {@link RequestInit} for default `RequestInit` options applied before request-specific fields
126
+ * @see {@link HttpClient.TracerPropagationEnabled} for leaving `b3` and `traceparent` off a browser request
124
127
  *
125
128
  * @stability unstable
126
129
  * @category layers
@@ -857,7 +857,8 @@ function makeCreateParams(
857
857
  }
858
858
  }
859
859
 
860
- function getClosingParenthensePosition(path: string, idx: number) {
860
+ /** @internal */
861
+ export function getClosingParenthensePosition(path: string, idx: number) {
861
862
  // `path.indexOf()` will always return the first position of the closing parenthese,
862
863
  // but it's inefficient for grouped or wrong regexp expressions.
863
864
  // see issues #62 and #63 for more info