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.
- package/ai-docs/src/52_graphql/10_basics.ts +19 -13
- package/ai-docs/src/52_graphql/30_batching.ts +6 -4
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +6 -4
- package/dist/Array.d.ts +90 -6
- package/dist/Array.d.ts.map +1 -1
- package/dist/Array.js +4 -2
- package/dist/Array.js.map +1 -1
- package/dist/Channel.d.ts +6 -1
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Channel.js +17 -3
- package/dist/Channel.js.map +1 -1
- package/dist/ConfigProvider.d.ts.map +1 -1
- package/dist/ConfigProvider.js +33 -32
- package/dist/ConfigProvider.js.map +1 -1
- package/dist/Duration.d.ts.map +1 -1
- package/dist/Duration.js +2 -5
- package/dist/Duration.js.map +1 -1
- package/dist/Effect.d.ts +61 -19
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +58 -39
- package/dist/Effect.js.map +1 -1
- package/dist/FileSystem.d.ts +23 -9
- package/dist/FileSystem.d.ts.map +1 -1
- package/dist/FileSystem.js.map +1 -1
- package/dist/Match.d.ts +20 -6
- package/dist/Match.d.ts.map +1 -1
- package/dist/Match.js +2 -2
- package/dist/Match.js.map +1 -1
- package/dist/Path.d.ts +37 -5
- package/dist/Path.d.ts.map +1 -1
- package/dist/Path.js +36 -5
- package/dist/Path.js.map +1 -1
- package/dist/Schema.d.ts +7 -2
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +9 -2
- package/dist/Schema.js.map +1 -1
- package/dist/String.d.ts +12 -0
- package/dist/String.d.ts.map +1 -1
- package/dist/String.js +17 -4
- package/dist/String.js.map +1 -1
- package/dist/TxRef.d.ts.map +1 -1
- package/dist/TxRef.js +3 -2
- package/dist/TxRef.js.map +1 -1
- package/dist/ai/AiError.d.ts +10 -10
- package/dist/ai/AiError.d.ts.map +1 -1
- package/dist/ai/AiError.js +1 -1
- package/dist/ai/AiError.js.map +1 -1
- package/dist/ai/Decision.d.ts +70 -19
- package/dist/ai/Decision.d.ts.map +1 -1
- package/dist/ai/Decision.js +37 -3
- package/dist/ai/Decision.js.map +1 -1
- package/dist/ai/DecisionModel.js +1 -1
- package/dist/ai/DecisionModel.js.map +1 -1
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +47 -35
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +6 -52
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +2 -28
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Response.d.ts +1 -1
- package/dist/cli/Completions.d.ts +5 -0
- package/dist/cli/Completions.d.ts.map +1 -1
- package/dist/cli/Completions.js.map +1 -1
- package/dist/cli/Flag.d.ts +100 -0
- package/dist/cli/Flag.d.ts.map +1 -1
- package/dist/cli/Flag.js +33 -0
- package/dist/cli/Flag.js.map +1 -1
- package/dist/cli/Param.d.ts +87 -0
- package/dist/cli/Param.d.ts.map +1 -1
- package/dist/cli/Param.js +30 -0
- package/dist/cli/Param.js.map +1 -1
- package/dist/cli/internal/command.d.ts.map +1 -1
- package/dist/cli/internal/command.js +2 -1
- package/dist/cli/internal/command.js.map +1 -1
- package/dist/cli/internal/completions/bash.js +4 -3
- package/dist/cli/internal/completions/bash.js.map +1 -1
- package/dist/cli/internal/completions/descriptor.js +8 -1
- package/dist/cli/internal/completions/descriptor.js.map +1 -1
- package/dist/cli/internal/completions/fish.js +4 -4
- package/dist/cli/internal/completions/fish.js.map +1 -1
- package/dist/cli/internal/completions/powershell.js +3 -2
- package/dist/cli/internal/completions/powershell.js.map +1 -1
- package/dist/cli/internal/completions/zsh.js +7 -3
- package/dist/cli/internal/completions/zsh.js.map +1 -1
- package/dist/cli/internal/parser.js +11 -3
- package/dist/cli/internal/parser.js.map +1 -1
- package/dist/cli/internal/wizard.d.ts.map +1 -1
- package/dist/cli/internal/wizard.js +2 -1
- package/dist/cli/internal/wizard.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts +1 -0
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +67 -13
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +20 -12
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Runners.d.ts.map +1 -1
- package/dist/cluster/Runners.js +54 -34
- package/dist/cluster/Runners.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +1 -1
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/encoding/Sse.js +1 -1
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQLClient.d.ts.map +1 -1
- package/dist/graphql/GraphQLClient.js +12 -5
- package/dist/graphql/GraphQLClient.js.map +1 -1
- package/dist/graphql/GraphQLProtocol.d.ts +249 -11
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
- package/dist/graphql/GraphQLProtocol.js +98 -8
- package/dist/graphql/GraphQLProtocol.js.map +1 -1
- package/dist/graphql/GraphQLResolver.d.ts +1 -1
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -1
- package/dist/graphql/GraphQLResolver.js +46 -16
- package/dist/graphql/GraphQLResolver.js.map +1 -1
- package/dist/http/FetchHttpClient.d.ts +6 -3
- package/dist/http/FetchHttpClient.d.ts.map +1 -1
- package/dist/http/FetchHttpClient.js +6 -3
- package/dist/http/FetchHttpClient.js.map +1 -1
- package/dist/http/FindMyWay/internal/router.js +2 -1
- package/dist/http/FindMyWay/internal/router.js.map +1 -1
- package/dist/http/Headers.d.ts +3 -3
- package/dist/http/Headers.d.ts.map +1 -1
- package/dist/http/Headers.js +6 -5
- package/dist/http/Headers.js.map +1 -1
- package/dist/http/HttpClient.d.ts +12 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +14 -1
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpClientError.d.ts.map +1 -1
- package/dist/http/HttpClientError.js +8 -6
- package/dist/http/HttpClientError.js.map +1 -1
- package/dist/http/HttpClientRequest.d.ts +147 -4
- package/dist/http/HttpClientRequest.d.ts.map +1 -1
- package/dist/http/HttpClientRequest.js +47 -1
- package/dist/http/HttpClientRequest.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts +60 -0
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +67 -5
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +52 -13
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerError.d.ts.map +1 -1
- package/dist/http/HttpServerError.js +6 -4
- package/dist/http/HttpServerError.js.map +1 -1
- package/dist/http/HttpServerRequest.js +2 -2
- package/dist/http/HttpServerRequest.js.map +1 -1
- package/dist/http/HttpStaticServer.d.ts.map +1 -1
- package/dist/http/HttpStaticServer.js +4 -10
- package/dist/http/HttpStaticServer.js.map +1 -1
- package/dist/http/HttpTraceContext.js +1 -1
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http/Url.d.ts.map +1 -1
- package/dist/http/Url.js +2 -1
- package/dist/http/Url.js.map +1 -1
- package/dist/http/UrlParams.d.ts +112 -8
- package/dist/http/UrlParams.d.ts.map +1 -1
- package/dist/http/UrlParams.js +141 -14
- package/dist/http/UrlParams.js.map +1 -1
- package/dist/http/internal/compression.d.ts +1 -1
- package/dist/http/internal/compression.d.ts.map +1 -1
- package/dist/http/internal/compression.js +2 -10
- package/dist/http/internal/compression.js.map +1 -1
- package/dist/http/internal/compressionBody.d.ts +2 -0
- package/dist/http/internal/compressionBody.d.ts.map +1 -0
- package/dist/http/internal/compressionBody.js +36 -0
- package/dist/http/internal/compressionBody.js.map +1 -0
- package/dist/http/internal/decompression.d.ts +2 -0
- package/dist/http/internal/decompression.d.ts.map +1 -0
- package/dist/http/internal/decompression.js +162 -0
- package/dist/http/internal/decompression.js.map +1 -0
- package/dist/http/internal/tracing.js +14 -4
- package/dist/http/internal/tracing.js.map +1 -1
- package/dist/http/internal/urlParams.d.ts +2 -0
- package/dist/http/internal/urlParams.d.ts.map +1 -0
- package/dist/http/internal/urlParams.js +4 -0
- package/dist/http/internal/urlParams.js.map +1 -0
- package/dist/http-api/HttpApiClient.d.ts.map +1 -1
- package/dist/http-api/HttpApiClient.js +2 -1
- package/dist/http-api/HttpApiClient.js.map +1 -1
- package/dist/internal/core.js +17 -1
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/effect.js +5 -3
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.js +12 -2
- package/dist/internal/graphql.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/observability/Otlp.d.ts +15 -0
- package/dist/observability/Otlp.d.ts.map +1 -1
- package/dist/observability/Otlp.js +3 -0
- package/dist/observability/Otlp.js.map +1 -1
- package/dist/observability/OtlpExporter.d.ts +8 -0
- package/dist/observability/OtlpExporter.d.ts.map +1 -1
- package/dist/observability/OtlpExporter.js +10 -1
- package/dist/observability/OtlpExporter.js.map +1 -1
- package/dist/observability/OtlpLogger.d.ts +17 -0
- package/dist/observability/OtlpLogger.d.ts.map +1 -1
- package/dist/observability/OtlpLogger.js +14 -1
- package/dist/observability/OtlpLogger.js.map +1 -1
- package/dist/observability/OtlpMetrics.d.ts +17 -0
- package/dist/observability/OtlpMetrics.d.ts.map +1 -1
- package/dist/observability/OtlpMetrics.js +13 -0
- package/dist/observability/OtlpMetrics.js.map +1 -1
- package/dist/observability/OtlpTracer.d.ts +17 -0
- package/dist/observability/OtlpTracer.d.ts.map +1 -1
- package/dist/observability/OtlpTracer.js +15 -2
- package/dist/observability/OtlpTracer.js.map +1 -1
- package/dist/reactivity/AsyncResult.d.ts +6 -0
- package/dist/reactivity/AsyncResult.d.ts.map +1 -1
- package/dist/reactivity/AsyncResult.js +27 -0
- package/dist/reactivity/AsyncResult.js.map +1 -1
- package/dist/reactivity/AtomRegistry.js +3 -1
- package/dist/reactivity/AtomRegistry.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +6 -1
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +8 -7
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +8 -7
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/testing/TestClock.d.ts +16 -3
- package/dist/testing/TestClock.d.ts.map +1 -1
- package/dist/testing/TestClock.js +112 -22
- package/dist/testing/TestClock.js.map +1 -1
- package/dist/workflow/Activity.js +1 -0
- package/dist/workflow/Activity.js.map +1 -1
- package/dist/workflow/Workflow.d.ts +22 -0
- package/dist/workflow/Workflow.d.ts.map +1 -1
- package/dist/workflow/Workflow.js +15 -3
- package/dist/workflow/Workflow.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +20 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +35 -21
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Array.ts +95 -6
- package/src/Channel.ts +24 -3
- package/src/ConfigProvider.ts +40 -44
- package/src/Duration.ts +4 -5
- package/src/Effect.ts +140 -53
- package/src/FileSystem.ts +23 -9
- package/src/Match.ts +65 -12
- package/src/Path.ts +52 -7
- package/src/Schema.ts +17 -4
- package/src/String.ts +17 -4
- package/src/TxRef.ts +4 -2
- package/src/ai/AiError.ts +3 -1
- package/src/ai/Decision.ts +70 -21
- package/src/ai/DecisionModel.ts +1 -1
- package/src/ai/LanguageModel.ts +57 -51
- package/src/ai/McpServer.ts +15 -55
- package/src/cli/Completions.ts +5 -0
- package/src/cli/Flag.ts +101 -0
- package/src/cli/Param.ts +96 -0
- package/src/cli/internal/command.ts +2 -1
- package/src/cli/internal/completions/bash.ts +4 -3
- package/src/cli/internal/completions/descriptor.ts +10 -1
- package/src/cli/internal/completions/fish.ts +4 -4
- package/src/cli/internal/completions/powershell.ts +5 -2
- package/src/cli/internal/completions/zsh.ts +7 -3
- package/src/cli/internal/parser.ts +12 -3
- package/src/cli/internal/wizard.ts +2 -1
- package/src/cluster/ClusterWorkflowEngine.ts +92 -24
- package/src/cluster/MessageStorage.ts +14 -3
- package/src/cluster/Runners.ts +88 -66
- package/src/cluster/internal/entityManager.ts +1 -1
- package/src/encoding/Sse.ts +1 -1
- package/src/graphql/GraphQLClient.ts +17 -5
- package/src/graphql/GraphQLProtocol.ts +274 -20
- package/src/graphql/GraphQLResolver.ts +45 -17
- package/src/http/FetchHttpClient.ts +6 -3
- package/src/http/FindMyWay/internal/router.ts +2 -1
- package/src/http/Headers.ts +6 -9
- package/src/http/HttpClient.ts +18 -1
- package/src/http/HttpClientError.ts +10 -6
- package/src/http/HttpClientRequest.ts +173 -7
- package/src/http/HttpMiddleware.ts +85 -5
- package/src/http/HttpRouter.ts +60 -13
- package/src/http/HttpServerError.ts +8 -4
- package/src/http/HttpServerRequest.ts +2 -2
- package/src/http/HttpStaticServer.ts +4 -10
- package/src/http/HttpTraceContext.ts +1 -1
- package/src/http/Url.ts +2 -1
- package/src/http/UrlParams.ts +225 -26
- package/src/http/internal/compression.ts +3 -17
- package/src/http/internal/compressionBody.ts +56 -0
- package/src/http/internal/decompression.ts +226 -0
- package/src/http/internal/tracing.ts +21 -4
- package/src/http/internal/urlParams.ts +5 -0
- package/src/http-api/HttpApiClient.ts +2 -1
- package/src/internal/core.ts +21 -1
- package/src/internal/effect.ts +5 -4
- package/src/internal/graphql.ts +30 -7
- package/src/internal/version.ts +1 -1
- package/src/observability/Otlp.ts +18 -0
- package/src/observability/OtlpExporter.ts +14 -3
- package/src/observability/OtlpLogger.ts +31 -2
- package/src/observability/OtlpMetrics.ts +30 -1
- package/src/observability/OtlpTracer.ts +32 -3
- package/src/reactivity/AsyncResult.ts +31 -0
- package/src/reactivity/AtomRegistry.ts +3 -1
- package/src/rpc/RpcClient.ts +6 -1
- package/src/sql/SqlClient.ts +9 -7
- package/src/testing/TestClock.ts +144 -29
- package/src/workflow/Activity.ts +1 -0
- package/src/workflow/Workflow.ts +31 -5
- 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
|
|
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) =>
|
|
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) =>
|
|
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:
|
|
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(
|
|
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.
|
|
349
|
-
client.execute(postRequest(
|
|
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`,
|
|
384
|
-
* the effect also builds the graphql-ws
|
|
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`,
|
|
401
|
-
* the effect also builds the graphql-ws
|
|
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`,
|
|
437
|
-
* the effect also builds the graphql-ws
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
)
|
|
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
|
-
*
|
|
918
|
-
*
|
|
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
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
|
930
|
-
*
|
|
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 })
|
|
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.
|
|
119
|
-
*
|
|
120
|
-
* `
|
|
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
|
-
|
|
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
|