{"version":3,"file":"adapter-gateway-mercurius-CLmTtNJC.mjs","sources":["../src/graphql/gateway/adapter-gateway-mercurius.ts"],"sourcesContent":["import type { MercuriusGatewayOptions } from \"@mercuriusjs/gateway\";\nimport mercuriusGateway from \"@mercuriusjs/gateway\";\nimport Fastify from \"fastify\";\nimport type {\n  FastifyInstance,\n  FastifyPluginCallback,\n  FastifyReply,\n  FastifyRequest,\n} from \"fastify\";\nimport mercurius from \"mercurius\";\nimport type { MercuriusOptions } from \"mercurius\";\nimport type { ILogger } from \"document-model\";\nimport type { GraphQLSchema } from \"graphql\";\nimport type http from \"node:http\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { WebSocketServer } from \"ws\";\nimport type { Context } from \"../types.js\";\nimport { useServer } from \"../websocket.js\";\nimport type {\n  FetchHandler,\n  GatewayContextFactory,\n  IGatewayAdapter,\n  SubgraphDefinition,\n  WsContextFactory,\n  WsDisposer,\n} from \"./types.js\";\n\n/**\n * Threads the original Fetch API Request through Mercurius's internal\n * fastify.inject() call so that contextFactory (which uses the auth WeakMap\n * from auth-middleware.ts) receives the same Request object that auth\n * middleware populated before calling the handler.\n */\nconst requestAls = new AsyncLocalStorage<Request>();\n\n// @mercuriusjs/gateway exports a plain `(instance, opts) => void` rather than\n// a typed FastifyPluginCallback — cast once here.\n// The gateway plugin's accepted options are the intersection of the base\n// MercuriusOptions and MercuriusGatewayOptions (mirroring the unexported\n// MercuriusFederationOptions type in the package).\ntype GatewayPluginOptions = MercuriusOptions & MercuriusGatewayOptions;\nconst mercuriusGatewayPlugin =\n  mercuriusGateway as unknown as FastifyPluginCallback<GatewayPluginOptions>;\n\nexport class MercuriusGatewayAdapter implements IGatewayAdapter<Context> {\n  readonly #logger: ILogger;\n  readonly #subgraphApps: FastifyInstance[] = [];\n\n  #supergraphApp: FastifyInstance | null = null;\n  #getSubgraphs: (() => SubgraphDefinition[]) | null = null;\n  #supergraphContextFactory: GatewayContextFactory<Context> | null = null;\n\n  constructor(logger: ILogger) {\n    this.#logger = logger;\n  }\n\n  async start(_httpServer: http.Server): Promise<void> {\n    // Mercurius instances are started lazily in createHandler /\n    // createSupergraphHandler — nothing to do here.\n  }\n\n  async createHandler(\n    schema: GraphQLSchema,\n    contextFactory: GatewayContextFactory<Context>,\n  ): Promise<FetchHandler> {\n    const app = await buildMercuriusApp(schema, contextFactory, this.#logger);\n    this.#subgraphApps.push(app);\n    return buildFetchHandler(app);\n  }\n\n  async createSupergraphHandler(\n    getSubgraphs: () => SubgraphDefinition[],\n    _httpServer: http.Server,\n    contextFactory: GatewayContextFactory<Context>,\n  ): Promise<FetchHandler> {\n    if (this.#supergraphApp) {\n      throw new Error(\"Supergraph is already running\");\n    }\n    this.#getSubgraphs = getSubgraphs;\n    this.#supergraphContextFactory = contextFactory;\n    this.#supergraphApp = await buildGatewayApp(\n      getSubgraphs(),\n      contextFactory,\n      this.#logger,\n    );\n\n    // Capture `this` so the returned handler always delegates to the *current*\n    // #supergraphApp, allowing updateSupergraph() to swap it atomically.\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    const adapter = this;\n    return (request: Request): Promise<Response> => {\n      if (!adapter.#supergraphApp) {\n        return Promise.resolve(\n          new Response(\"Gateway not ready\", { status: 503 }),\n        );\n      }\n      return requestAls.run(request, () =>\n        injectRequest(adapter.#supergraphApp!, request),\n      );\n    };\n  }\n\n  async updateSupergraph(): Promise<void> {\n    if (!this.#getSubgraphs || !this.#supergraphContextFactory) return;\n    const newApp = await buildGatewayApp(\n      this.#getSubgraphs(),\n      this.#supergraphContextFactory,\n      this.#logger,\n    );\n    const oldApp = this.#supergraphApp;\n    // Swap atomically — in-flight requests on the old app finish normally.\n    this.#supergraphApp = newApp;\n    if (oldApp) await oldApp.close();\n  }\n\n  attachWebSocket(\n    wsServer: WebSocketServer,\n    schema: GraphQLSchema,\n    contextFactory: WsContextFactory<Context>,\n  ): WsDisposer {\n    // Use graphql-ws directly; Mercurius's own subscription transport is\n    // Fastify-specific and not applicable here.\n    return useServer(\n      {\n        schema,\n        context: async (ctx: { connectionParams?: Record<string, unknown> }) =>\n          contextFactory(ctx.connectionParams ?? {}),\n      },\n      wsServer,\n    );\n  }\n\n  async stop(): Promise<void> {\n    await Promise.all(this.#subgraphApps.map((app) => app.close()));\n    this.#subgraphApps.length = 0;\n    if (this.#supergraphApp) {\n      await this.#supergraphApp.close();\n      this.#supergraphApp = null;\n    }\n    this.#getSubgraphs = null;\n    this.#supergraphContextFactory = null;\n  }\n}\n\n// ── Fastify instance factories ────────────────────────────────────────────────\n\nfunction makeContextFn(\n  contextFactory: GatewayContextFactory<Context>,\n  logger: ILogger,\n) {\n  return (_req: FastifyRequest, _reply: FastifyReply) => {\n    const request = requestAls.getStore();\n    if (!request) {\n      logger.error(\"[mercurius] No Fetch Request in AsyncLocalStorage\");\n      throw new Error(\"No Fetch Request in AsyncLocalStorage\");\n    }\n    return contextFactory(request);\n  };\n}\n\nasync function buildMercuriusApp(\n  schema: GraphQLSchema,\n  contextFactory: GatewayContextFactory<Context>,\n  logger: ILogger,\n): Promise<FastifyInstance> {\n  const app = Fastify({ logger: false });\n\n  await app.register(mercurius, {\n    schema,\n    graphiql: false,\n    context: makeContextFn(contextFactory, logger),\n    // Override _Service.sdl to rewrite \"type Query/Mutation/Subscription {\"\n    // as \"extend type …  {\" so that @mercuriusjs/gateway v5 (which follows the\n    // Federation v1 convention of using extensionTypeMap) correctly maps root\n    // operation fields to this service during query planning.\n    resolvers: {\n      _Service: {\n        sdl: (parent: { sdl?: string }) =>\n          (parent.sdl ?? \"\").replace(\n            /\\btype\\s+(Query|Mutation|Subscription)\\s*\\{/g,\n            \"extend type $1 {\",\n          ),\n      },\n    },\n  } satisfies MercuriusOptions);\n\n  await app.ready();\n  return app;\n}\n\n/**\n * Builds a Mercurius federation gateway that composes the given subgraph\n * services. Each service URL must be reachable so that the gateway can fetch\n * its SDL via `_service { sdl }` (Apollo Federation protocol).\n */\nasync function buildGatewayApp(\n  subgraphs: SubgraphDefinition[],\n  contextFactory: GatewayContextFactory<Context>,\n  logger: ILogger,\n): Promise<FastifyInstance> {\n  const app = Fastify({ logger: false });\n\n  await app.register(mercuriusGatewayPlugin, {\n    gateway: {\n      services: subgraphs.map((s) => ({ name: s.name, url: s.url })),\n    },\n    graphiql: false,\n    context: makeContextFn(contextFactory, logger),\n  });\n\n  await app.ready();\n  return app;\n}\n\n// ── Fetch API bridge ──────────────────────────────────────────────────────────\n\nfunction buildFetchHandler(app: FastifyInstance): FetchHandler {\n  return (request: Request): Promise<Response> =>\n    requestAls.run(request, () => injectRequest(app, request));\n}\n\nasync function injectRequest(\n  app: FastifyInstance,\n  request: Request,\n): Promise<Response> {\n  const body =\n    request.method !== \"GET\" && request.method !== \"HEAD\"\n      ? await request.text()\n      : undefined;\n\n  const headers: Record<string, string> = {};\n  request.headers.forEach((value, key) => {\n    headers[key] = value;\n  });\n\n  const response = await app.inject({\n    method: request.method as\n      | \"DELETE\"\n      | \"GET\"\n      | \"HEAD\"\n      | \"OPTIONS\"\n      | \"PATCH\"\n      | \"POST\"\n      | \"PUT\",\n    url: \"/graphql\",\n    headers,\n    payload: body,\n  });\n\n  const responseHeaders: Record<string, string> = {};\n  for (const [key, value] of Object.entries(response.headers)) {\n    if (value !== undefined) responseHeaders[key] = String(value);\n  }\n\n  return new Response(response.payload, {\n    status: response.statusCode,\n    headers: responseHeaders,\n  });\n}\n"],"names":["#logger","#subgraphApps","#supergraphApp","#getSubgraphs","#supergraphContextFactory"],"mappings":"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","debug_id":"b5ea3d3a-7b76-5891-97bf-dc76e50f3387"}