/** * GraphQL-level middleware provided as `Context.Service` implementations. * Middleware can rewrite requests and inspect raw results, including response * extensions and HTTP headers. Provide implementations with `Layer.succeed` * or `Layer.effect`. * * @stability experimental * @since 4.0.0 */ import * as Context from "../Context.ts"; import * as Effect from "../Effect.ts"; import * as Stream from "../Stream.ts"; import type * as Types from "../Types.ts"; import type * as GraphQL from "./GraphQL.ts"; import type { GraphQLClientError } from "./GraphQLClientError.ts"; import type { ExecutionResult, GraphQLRequest } from "./GraphQLProtocol.ts"; declare const TypeId = "~effect/graphql/GraphQLMiddleware"; /** * Middleware handlers for queries, mutations and subscriptions. Both * `execute` and `subscribe` are required. * * **Details** * * `execute` runs once per query or mutation. `subscribe` runs once per * subscription attempt (the client re-runs the whole chain when it retries a * subscription) and can map or tap the event stream. * * Both receive the operation being run, the request so far, and `next`, which * runs the rest of the chain with the given request and returns the raw * `ExecutionResult` (or, for `subscribe`, the stream of them). Its `status` * and `headers` are the HTTP status and response headers the transport * reported, if any; a middleware may rewrite them, and a failed call's * `ResponseError` carries the final values. Request and response headers * are `Headers.Headers` values; use `Headers.set` to rewrite them. * * `next` fails with `GraphQLClientError`, or with the error of a middleware * further in, which is typed as `Types.unhandled`, as in `RpcMiddleware`. * Narrow it, for example with `Effect.catchTag("GraphQLClientError", ...)`, * before reading `reason`. * * Use {@link mapRequest} when the middleware only needs to transform the * request. * * **Example** (Reading a rate-limit header) * * ```ts import.meta.vitest * import { Effect } from "effect" * import { GraphQLMiddleware } from "effect/graphql" * import { Headers } from "effect/http" * * const rateLimit: GraphQLMiddleware.Implementation = { * execute: ({ next, request }) => * Effect.tap(next(request), (result) => * Effect.log(result.headers === undefined ? undefined : Headers.get(result.headers, "x-ratelimit-remaining")) * ), * subscribe: ({ next, request }) => next(request) * } * ``` * * @stability experimental * @category models * @since 4.0.0 */ export interface Implementation { readonly execute: (options: { readonly operation: GraphQL.Any; readonly request: GraphQLRequest; readonly next: (request: GraphQLRequest) => Effect.Effect; }) => Effect.Effect; readonly subscribe: (options: { readonly operation: GraphQL.Any; readonly request: GraphQLRequest; readonly next: (request: GraphQLRequest) => Stream.Stream; }) => Stream.Stream; } /** * The class returned by {@link Service}: a `Context.Service` for an * {@link Implementation}, carrying the declared `error` and `requires` types. * * @stability experimental * @category services * @since 4.0.0 */ export interface ServiceClass extends Context.Service> { new (_: never): Context.ServiceClass.Shape> & { readonly [TypeId]: { readonly error: E; readonly requires: R; }; }; readonly [TypeId]: { readonly error: E; readonly requires: R; }; readonly key: Key; } /** * Any middleware service class, with its error and requirement types erased. * * @stability experimental * @category utility types * @since 4.0.0 */ export interface AnyService extends Context.Service> { readonly [TypeId]: { readonly error: any; readonly requires: any; }; } /** * Extracts the service identifier of a middleware, which is what * `GraphQLClient.make` requires. * * @stability experimental * @category utility types * @since 4.0.0 */ export type Identifier = M extends Context.Service ? I : never; /** * Extracts the error type a middleware declared. * * @stability experimental * @category utility types * @since 4.0.0 */ export type Error = M extends { readonly [TypeId]: { readonly error: infer E; }; } ? E : never; /** * Extracts the services a middleware declared it requires per call. * * @stability experimental * @category utility types * @since 4.0.0 */ export type Requires = M extends { readonly [TypeId]: { readonly requires: infer R; }; } ? R : never; /** * Declares a middleware tag. `requires` are services the implementation needs * on every call, and `error` is what it can fail with; both show up on the * requirements and error type of every client method the middleware is * attached to. * * **Example** (An auth middleware that needs the current user) * * ```ts import.meta.vitest * import { Context, Effect, Layer, Schema, Stream } from "effect" * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLMiddleware, GraphQLProtocol } from "effect/graphql" * import { Headers } from "effect/http" * * class CurrentUser extends Context.Service()("CurrentUser") {} * class TokenExpired extends Schema.TaggedError()("TokenExpired", {}) {} * * class Auth extends GraphQLMiddleware.Service()("app/Auth") {} * * const AuthLive = Layer.succeed( * Auth, * GraphQLMiddleware.mapRequest(Effect.fnUntraced(function*(request) { * const user = yield* CurrentUser * if (user.token === "") return yield* new TokenExpired() * return { ...request, headers: Headers.set(request.headers, "authorization", `Bearer ${user.token}`) } * })) * ) * * const Viewer = GraphQL.query("Viewer", { * document: "query Viewer{viewer{login}}", * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) }) * }) * * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, { * execute: (request) => Effect.succeed({ body: { data: { viewer: { login: request.headers.authorization } } } }), * subscribe: () => Stream.empty * }) * * const program = Effect.gen(function*() { * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer).middleware(Auth)) * // CurrentUser is required by the method because Auth is attached * return yield* client.Viewer().pipe(Effect.provideService(CurrentUser, { token: "abc" })) * }) * * await Effect.runPromise(program.pipe(Effect.provide([Scripted, AuthLive]))) // => { viewer: { login: "Bearer abc" } } * ``` * * @stability experimental * @category constructors * @since 4.0.0 */ export declare const Service: () => (key: Key) => ServiceClass; /** * Builds an {@link Implementation} that transforms requests for queries, * mutations and subscriptions. Use it to set headers, auth or extensions. * * **Example** (Adding a header to every request) * * ```ts import.meta.vitest * import { Effect, Layer, Schema, Stream } from "effect" * import { GraphQL, GraphQLClient, GraphQLGroup, GraphQLMiddleware, GraphQLProtocol } from "effect/graphql" * import { Headers } from "effect/http" * * class RequestId extends GraphQLMiddleware.Service()("app/RequestId") {} * * const RequestIdLive = Layer.succeed( * RequestId, * GraphQLMiddleware.mapRequest((request) => * Effect.succeed({ ...request, headers: Headers.set(request.headers, "x-request-id", "abc") }) * ) * ) * * const Viewer = GraphQL.query("Viewer", { * document: "query Viewer{viewer{login}}", * result: Schema.Struct({ viewer: Schema.Struct({ login: Schema.String }) }) * }) * * const Scripted = Layer.succeed(GraphQLProtocol.GraphQLProtocol, { * execute: (request) => * Effect.succeed({ body: { data: { viewer: { login: request.headers["x-request-id"] } } } }), * subscribe: () => Stream.empty * }) * * const program = Effect.gen(function*() { * const client = yield* GraphQLClient.make(GraphQLGroup.make(Viewer).middleware(RequestId)) * return yield* client.Viewer() * }) * * await Effect.runPromise(program.pipe(Effect.provide([Scripted, RequestIdLive]))) // => { viewer: { login: "abc" } } * ``` * * @stability experimental * @category constructors * @since 4.0.0 */ export declare const mapRequest: (f: (request: GraphQLRequest, operation: GraphQL.Any) => Effect.Effect) => Implementation; export {}; //# sourceMappingURL=GraphQLMiddleware.d.ts.map