{"version":3,"file":"adapter-http-express-D5OpUIbV.mjs","sources":["../src/graphql/gateway/adapter-http-express.ts"],"sourcesContent":["import bodyParser from \"body-parser\";\nimport cors from \"cors\";\nimport type { CorsOptions } from \"cors\";\nimport devcert from \"devcert\";\nimport type express from \"express\";\nimport type { Express } from \"express\";\nimport { Router } from \"express\";\nimport expressLib from \"express\";\nimport type { IRouter } from \"express\";\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport path from \"node:path\";\nimport { match, type MatchFunction, type ParamData } from \"path-to-regexp\";\nimport type { FetchHandler, IHttpAdapter, TlsOptions } from \"./types.js\";\n\nexport class ExpressHttpAdapter implements IHttpAdapter {\n  readonly #app: Express;\n  readonly #router: IRouter;\n  readonly #handlers = new Map<\n    string,\n    { handler: FetchHandler; matcher: MatchFunction<ParamData> }\n  >();\n\n  constructor(existingApp?: Express) {\n    this.#app = existingApp ?? expressLib();\n    this.#router = Router();\n    this.#app.use(this.#router);\n  }\n\n  setupSentryErrorHandler(sentry: object): void {\n    const s = sentry as {\n      setupExpressErrorHandler(app: Express): void;\n    };\n    s.setupExpressErrorHandler(this.#app);\n  }\n\n  get handle(): unknown {\n    return this.#app;\n  }\n\n  mountRawMiddleware(middleware: unknown): void {\n    this.#app.use(middleware as any);\n  }\n\n  mountNodeRoute(\n    method: \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\",\n    path: string,\n    handler: (\n      req: http.IncomingMessage,\n      res: http.ServerResponse,\n      body?: unknown,\n    ) => void | Promise<void>,\n  ): void {\n    const m = method.toLowerCase() as\n      | \"delete\"\n      | \"get\"\n      | \"head\"\n      | \"post\"\n      | \"put\";\n    this.#app[m](path, (req: express.Request, res: express.Response) => {\n      void handler(req, res, req.body as unknown);\n    });\n  }\n\n  setupMiddleware({\n    corsOptions,\n    bodyLimit = \"50mb\",\n  }: {\n    corsOptions?: CorsOptions;\n    bodyLimit?: string;\n  }): void {\n    this.#router.use(cors(corsOptions));\n    this.#router.use(bodyParser.json({ limit: bodyLimit }));\n    this.#router.use(\n      bodyParser.urlencoded({ extended: true, limit: bodyLimit }),\n    );\n\n    // Dispatcher registered AFTER bodyParser so req.body is populated when it fires.\n    this.#router.use((req, res, next) => {\n      for (const { handler, matcher } of this.#handlers.values()) {\n        if (matcher(req.path)) {\n          this.#serveFetchHandler(handler, req, res, next);\n          return;\n        }\n      }\n      next();\n    });\n  }\n\n  mount(\n    path: string,\n    handler: FetchHandler,\n    { exact = false }: { exact?: boolean } = {},\n  ): void {\n    if (exact) {\n      this.#router.use(path, (req, res, next) =>\n        this.#serveFetchHandler(handler, req, res, next),\n      );\n    } else {\n      // Exact match - stored in the internal dispatch map\n      this.#handlers.set(path, {\n        handler,\n        matcher: match(path),\n      });\n    }\n  }\n\n  getRoute(\n    routePath: string,\n    handler: (request: Request) => Response | Promise<Response>,\n  ): void {\n    this.#app.get(routePath, (req, res) => {\n      const protocol = req.protocol;\n      const host = req.get(\"host\") ?? \"localhost\";\n      const url = `${protocol}://${host}${req.originalUrl}`;\n      const headers = new Headers();\n      for (const [key, value] of Object.entries(req.headers)) {\n        if (typeof value === \"string\") {\n          headers.set(key, value);\n        } else if (Array.isArray(value)) {\n          headers.set(key, value.join(\", \"));\n        }\n      }\n      const fetchRequest = new Request(url, { method: \"GET\", headers });\n      Promise.resolve(handler(fetchRequest))\n        .then(async (response) => {\n          res.status(response.status);\n          response.headers.forEach((value, key) => {\n            res.setHeader(key, value);\n          });\n          res.send(await response.text());\n        })\n        .catch((err: unknown) => {\n          res.status(500).send(String(err));\n        });\n    });\n  }\n\n  async listen(port: number, tls?: TlsOptions): Promise<http.Server> {\n    let server: http.Server;\n\n    if (tls === true) {\n      const { cert, key } = (await devcert.certificateFor(\"localhost\")) as {\n        cert: Buffer;\n        key: Buffer;\n      };\n      if (!cert || !key) {\n        throw new Error(\"Invalid certificate generated\");\n      }\n      server = https.createServer({ cert, key }, this.#app);\n    } else if (tls && \"keyPath\" in tls) {\n      const currentDir = process.cwd();\n      server = https.createServer(\n        {\n          key: fs.readFileSync(path.join(currentDir, tls.keyPath)),\n          cert: fs.readFileSync(path.join(currentDir, tls.certPath)),\n        },\n        this.#app,\n      );\n    } else if (tls && \"cert\" in tls) {\n      server = https.createServer({ cert: tls.cert, key: tls.key }, this.#app);\n    } else {\n      server = http.createServer(this.#app);\n    }\n\n    return new Promise<http.Server>((resolve, reject) => {\n      server.once(\"error\", reject);\n      server.listen(port, () => {\n        server.off(\"error\", reject);\n        resolve(server);\n      });\n    });\n  }\n\n  #serveFetchHandler(\n    handler: FetchHandler,\n    req: express.Request,\n    res: express.Response,\n    next: express.NextFunction,\n  ): void {\n    // Build the full URL for the Fetch Request\n    const protocol = req.protocol;\n    const host = req.get(\"host\") ?? \"localhost\";\n    const url = `${protocol}://${host}${req.originalUrl}`;\n\n    // Convert Node.js incoming headers to Fetch Headers\n    const headers = new Headers();\n    for (const [key, value] of Object.entries(req.headers)) {\n      if (typeof value === \"string\") {\n        headers.set(key, value);\n      } else if (Array.isArray(value)) {\n        headers.set(key, value.join(\", \"));\n      }\n    }\n\n    // bodyParser has already run, so req.body is a parsed JS value.\n    // Re-serialize it so the Fetch Request body stream is readable.\n    let body: string | undefined;\n    if (\n      req.method !== \"GET\" &&\n      req.method !== \"HEAD\" &&\n      req.body !== undefined\n    ) {\n      body = JSON.stringify(req.body);\n    }\n\n    const fetchRequest = new Request(url, {\n      method: req.method,\n      headers,\n      body,\n    });\n\n    Promise.resolve(handler(fetchRequest))\n      .then(async (response) => {\n        res.status(response.status);\n        response.headers.forEach((value, key) => {\n          res.setHeader(key, value);\n        });\n        const responseBody = await response.text();\n        res.send(responseBody);\n      })\n      .catch(next);\n  }\n}\n\nexport function createExpressHttpAdapter(existingApp?: Express): {\n  adapter: IHttpAdapter;\n} {\n  return { adapter: new ExpressHttpAdapter(existingApp) };\n}\n"],"names":["#app","#router","#handlers","#serveFetchHandler"],"mappings":"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","debug_id":"45603953-04e5-5972-a877-8620773dcfbd"}