@powerhousedao/reactor-api 6.2.2-dev.9 → 6.2.2
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/dist/{adapter-gateway-apollo-CxV_hMTv.mjs → adapter-gateway-apollo-DdamNLPT.mjs} +10 -4
- package/dist/adapter-gateway-apollo-DdamNLPT.mjs.map +1 -0
- package/dist/adapter-http-express-CgVPgEMm.mjs +225 -0
- package/dist/adapter-http-express-CgVPgEMm.mjs.map +1 -0
- package/dist/{adapter-http-fastify-B248RJtm.mjs → adapter-http-fastify-D98iRljY.mjs} +38 -26
- package/dist/adapter-http-fastify-D98iRljY.mjs.map +1 -0
- package/dist/index.d.mts +584 -50
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1258 -174
- package/dist/index.mjs.map +1 -1
- package/dist/path-normalize-DWWFLiy6.mjs +23 -0
- package/dist/path-normalize-DWWFLiy6.mjs.map +1 -0
- package/dist/src/packages/https-hooks.mjs +1 -1
- package/dist/src/packages/vite-loader.d.mts +3 -2
- package/dist/src/packages/vite-loader.d.mts.map +1 -1
- package/dist/src/packages/vite-loader.mjs +33 -8
- package/dist/src/packages/vite-loader.mjs.map +1 -1
- package/dist/{types-DnHgkahK.d.mts → types-Bz2niABC.d.mts} +13 -2
- package/dist/types-Bz2niABC.d.mts.map +1 -0
- package/dist/{utils-iibOQ50e.mjs → utils-lHoPB-ft.mjs} +130 -23
- package/dist/utils-lHoPB-ft.mjs.map +1 -0
- package/package.json +22 -19
- package/dist/adapter-gateway-apollo-CxV_hMTv.mjs.map +0 -1
- package/dist/adapter-http-express-D5OpUIbV.mjs +0 -137
- package/dist/adapter-http-express-D5OpUIbV.mjs.map +0 -1
- package/dist/adapter-http-fastify-B248RJtm.mjs.map +0 -1
- package/dist/types-DnHgkahK.d.mts.map +0 -1
- package/dist/utils-iibOQ50e.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="77c68ea0-45a9-5982-9dc4-6194ce0c3ef5")}catch(e){}}();
|
|
3
3
|
import { t as useServer } from "./websocket-DncAB9XK.mjs";
|
|
4
4
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
5
5
|
import { ApolloGateway, LocalCompose, RemoteGraphQLDataSource } from "@apollo/gateway";
|
|
@@ -21,7 +21,7 @@ import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin
|
|
|
21
21
|
function filterComposableSubgraphs(serviceList, logger) {
|
|
22
22
|
return serviceList.filter((service) => {
|
|
23
23
|
try {
|
|
24
|
-
buildSubgraphSchema({ typeDefs: service.typeDefs });
|
|
24
|
+
buildSubgraphSchema([{ typeDefs: service.typeDefs }]);
|
|
25
25
|
return true;
|
|
26
26
|
} catch (error) {
|
|
27
27
|
logger?.error(`Excluding subgraph "${service.name}" from supergraph composition: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -30,6 +30,12 @@ function filterComposableSubgraphs(serviceList, logger) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
var AuthenticatedDataSource = class extends RemoteGraphQLDataSource {
|
|
33
|
+
constructor(config) {
|
|
34
|
+
super({
|
|
35
|
+
fetcher: fetch,
|
|
36
|
+
...config
|
|
37
|
+
});
|
|
38
|
+
}
|
|
33
39
|
willSendRequest(options) {
|
|
34
40
|
const { authorization } = options.context.headers;
|
|
35
41
|
if (authorization && options?.request.http) options.request.http.headers.set("authorization", authorization);
|
|
@@ -152,5 +158,5 @@ function createApolloFetchHandler(server, contextFactory) {
|
|
|
152
158
|
//#endregion
|
|
153
159
|
export { ApolloGatewayAdapter };
|
|
154
160
|
|
|
155
|
-
//# sourceMappingURL=adapter-gateway-apollo-
|
|
156
|
-
//# debugId=
|
|
161
|
+
//# sourceMappingURL=adapter-gateway-apollo-DdamNLPT.mjs.map
|
|
162
|
+
//# debugId=77c68ea0-45a9-5982-9dc4-6194ce0c3ef5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-gateway-apollo-DdamNLPT.mjs","sources":["../src/graphql/gateway/adapter-gateway-apollo.ts"],"sourcesContent":["import type {\n GetDataSourceFunction,\n GraphQLDataSourceProcessOptions,\n ServiceDefinition,\n SubgraphHealthCheckFunction,\n SupergraphSdlUpdateFunction,\n} from \"@apollo/gateway\";\nimport {\n ApolloGateway,\n LocalCompose,\n RemoteGraphQLDataSource,\n} from \"@apollo/gateway\";\nimport { ApolloServer, HeaderMap } from \"@apollo/server\";\nimport { ApolloServerPluginInlineTraceDisabled } from \"@apollo/server/plugin/disabled\";\nimport { ApolloServerPluginDrainHttpServer } from \"@apollo/server/plugin/drainHttpServer\";\nimport { ApolloServerPluginLandingPageLocalDefault } from \"@apollo/server/plugin/landingPage/default\";\nimport { buildSubgraphSchema } from \"@apollo/subgraph\";\nimport type { ILogger } from \"document-model\";\nimport type { GraphQLSchema } from \"graphql\";\nimport type http from \"node:http\";\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 * Drop subgraphs whose typeDefs cannot be built into a valid subgraph schema in\n * isolation — e.g. a duplicate type definition within a single subgraph (Sentry\n * #917) — so one broken subgraph is excluded instead of failing the build for\n * everyone. Each excluded subgraph is logged; the rest are composed.\n *\n * Scope: this only catches per-subgraph build failures. Errors that surface only\n * when subgraphs are composed together (cross-subgraph type or `@key` conflicts)\n * are still thrown by `LocalCompose.initialize()` and are NOT handled here.\n */\nexport function filterComposableSubgraphs(\n serviceList: ServiceDefinition[],\n logger?: Pick<ILogger, \"error\">,\n): ServiceDefinition[] {\n return serviceList.filter((service) => {\n try {\n // Array form: @apollo/subgraph 2.15 dropped the bare-module overload.\n buildSubgraphSchema([{ typeDefs: service.typeDefs }]);\n return true;\n } catch (error) {\n logger?.error(\n `Excluding subgraph \"${service.name}\" from supergraph composition: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return false;\n }\n });\n}\n\n// Forwards the incoming authorization header to federated subgraph requests.\nclass AuthenticatedDataSource extends RemoteGraphQLDataSource {\n constructor(\n config?: ConstructorParameters<typeof RemoteGraphQLDataSource>[0],\n ) {\n // Node's fetch retires a pooled connection before the subgraph server's advertised keep-alive\n // timeout; Apollo's default make-fetch-happen agent holds it until the server closes it under us.\n super({\n fetcher: fetch as RemoteGraphQLDataSource[\"fetcher\"],\n ...config,\n });\n }\n\n willSendRequest(options: GraphQLDataSourceProcessOptions) {\n const { authorization } = options.context.headers as {\n authorization: string;\n };\n if (authorization && options?.request.http) {\n options.request.http.headers.set(\"authorization\", authorization);\n }\n }\n}\n\nexport class ApolloGatewayAdapter implements IGatewayAdapter<Context> {\n readonly #logger: ILogger;\n readonly #servers: ApolloServer<Context>[] = [];\n\n #supergraphServer: ApolloServer<Context> | null = null;\n #gatewayOptions: {\n update: SupergraphSdlUpdateFunction;\n healthCheck: SubgraphHealthCheckFunction;\n getDataSource: GetDataSourceFunction;\n } | null = null;\n #getSubgraphs: (() => SubgraphDefinition[]) | null = null;\n\n constructor(logger: ILogger) {\n this.#logger = logger;\n }\n\n async start(_httpServer: http.Server): Promise<void> {\n // Per-subgraph Apollo servers start lazily in createHandler.\n // Nothing to do here.\n }\n\n async createHandler(\n schema: GraphQLSchema,\n contextFactory: GatewayContextFactory<Context>,\n ): Promise<FetchHandler> {\n const server = new ApolloServer<Context>({\n schema,\n logger: this.#logger,\n introspection: true,\n // Each ApolloServer otherwise registers its own SIGINT/SIGTERM handler\n // that re-emits the signal via process.kill after server.stop() resolves.\n // With many subgraph servers that re-emit lands while the reactor's\n // graceful-shutdown chain is mid-flight, looking like a \"second SIGINT\"\n // to the reactor and aborting cleanup. The reactor drives our shutdown\n // end-to-end (see ReactorBuilder.attachSignalHandlers), so opt out.\n stopOnTerminationSignals: false,\n plugins: [\n ApolloServerPluginInlineTraceDisabled(),\n ApolloServerPluginLandingPageLocalDefault(),\n ],\n });\n await server.start();\n this.#servers.push(server);\n return createApolloFetchHandler(server, contextFactory);\n }\n\n async createSupergraphHandler(\n getSubgraphs: () => SubgraphDefinition[],\n httpServer: http.Server,\n contextFactory: GatewayContextFactory<Context>,\n ): Promise<FetchHandler> {\n if (this.#supergraphServer) {\n throw new Error(\"Supergraph server is already running\");\n }\n\n this.#getSubgraphs = getSubgraphs;\n\n const gateway = new ApolloGateway({\n supergraphSdl: async (options) => {\n this.#gatewayOptions = options;\n return await this.#buildSupergraphSdl();\n },\n buildService: (serviceConfig) =>\n new AuthenticatedDataSource(serviceConfig),\n });\n\n this.#supergraphServer = new ApolloServer<Context>({\n gateway,\n logger: this.#logger,\n introspection: true,\n stopOnTerminationSignals: false,\n plugins: [\n ApolloServerPluginDrainHttpServer({ httpServer }),\n ApolloServerPluginInlineTraceDisabled(),\n ApolloServerPluginLandingPageLocalDefault(),\n ],\n });\n\n await this.#supergraphServer.start();\n return createApolloFetchHandler(this.#supergraphServer, contextFactory);\n }\n\n async updateSupergraph(): Promise<void> {\n if (!this.#gatewayOptions || !this.#getSubgraphs) {\n // Not yet initialized - no-op.\n return;\n }\n const { supergraphSdl } = await this.#buildSupergraphSdl();\n this.#gatewayOptions.update(supergraphSdl);\n }\n\n async #buildSupergraphSdl() {\n if (!this.#gatewayOptions || !this.#getSubgraphs) {\n throw new Error(\"Gateway is not ready\");\n }\n const serviceList: ServiceDefinition[] = this.#getSubgraphs().map((s) => ({\n name: s.name,\n typeDefs: s.typeDefs,\n url: s.url,\n }));\n const composableServiceList = filterComposableSubgraphs(\n serviceList,\n this.#logger,\n );\n const localCompose = new LocalCompose({\n localServiceList: composableServiceList,\n });\n return localCompose.initialize(this.#gatewayOptions);\n }\n\n attachWebSocket(\n wsServer: WebSocketServer,\n schema: GraphQLSchema,\n contextFactory: WsContextFactory<Context>,\n ): WsDisposer {\n return useServer(\n {\n schema,\n context: async (ctx: {\n connectionParams?: Record<string, unknown>;\n }) => {\n const connectionParams = (ctx.connectionParams ?? {}) as Record<\n string,\n unknown\n >;\n return contextFactory(connectionParams);\n },\n },\n wsServer,\n );\n }\n\n async stop(): Promise<void> {\n await Promise.all(this.#servers.map((s) => s.stop()));\n this.#servers.length = 0;\n\n if (this.#supergraphServer) {\n await this.#supergraphServer.stop();\n this.#supergraphServer = null;\n }\n this.#gatewayOptions = null;\n this.#getSubgraphs = null;\n }\n}\n\n/**\n * Wrap an existing ApolloServer as a Fetch API handler.\n * Does not manage server lifecycle; caller is responsible for start/stop.\n */\nexport function createApolloFetchHandler(\n server: ApolloServer<Context>,\n contextFactory: GatewayContextFactory<Context>,\n): FetchHandler {\n return async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n const headers = new HeaderMap();\n request.headers.forEach((value, key) => headers.set(key, value));\n\n let body: unknown = undefined;\n if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n try {\n body = (await request.json()) as unknown;\n } catch {\n // body may be empty for some requests\n }\n }\n\n const result = await server.executeHTTPGraphQLRequest({\n httpGraphQLRequest: {\n method: request.method.toUpperCase(),\n headers,\n search: url.search,\n body,\n },\n context: () => contextFactory(request),\n });\n\n const responseHeaders = new Headers();\n for (const [key, value] of result.headers) {\n responseHeaders.set(key, value);\n }\n\n let responseBody: string;\n if (result.body.kind === \"complete\") {\n responseBody = result.body.string;\n } else {\n const chunks: string[] = [];\n for await (const chunk of result.body.asyncIterator) {\n chunks.push(chunk);\n }\n responseBody = chunks.join(\"\");\n }\n\n return new Response(responseBody, {\n status: result.status ?? 200,\n headers: responseHeaders,\n });\n };\n}\n"],"names":["#logger","#servers","#supergraphServer","#getSubgraphs","#gatewayOptions","#buildSupergraphSdl"],"mappings":"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","debug_id":"77c68ea0-45a9-5982-9dc4-6194ce0c3ef5"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d26eb9a2-eb77-534d-8262-7614f536bac3")}catch(e){}}();
|
|
3
|
+
import { t as normalizePath } from "./path-normalize-DWWFLiy6.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { match } from "path-to-regexp";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import http from "node:http";
|
|
8
|
+
import https from "node:https";
|
|
9
|
+
import bodyParser from "body-parser";
|
|
10
|
+
import cors from "cors";
|
|
11
|
+
import devcert from "devcert";
|
|
12
|
+
import expressLib, { Router } from "express";
|
|
13
|
+
import { Readable } from "node:stream";
|
|
14
|
+
//#region src/graphql/gateway/adapter-http-express.ts
|
|
15
|
+
var ExpressHttpAdapter = class {
|
|
16
|
+
#app;
|
|
17
|
+
#router;
|
|
18
|
+
/**
|
|
19
|
+
* Every route lives in this registry and is served by one
|
|
20
|
+
* permanently-registered dispatcher (installed by setupMiddleware), because
|
|
21
|
+
* Express 4 has no route-removal API: routes registered directly on the
|
|
22
|
+
* app or router can never be taken back. Entries iterate in registration
|
|
23
|
+
* order; mounting a path that already holds a route of the same kind (and
|
|
24
|
+
* method, for node routes) replaces the old entry, so re-mounting is
|
|
25
|
+
* last-write-wins without accumulating.
|
|
26
|
+
*/
|
|
27
|
+
#routes = /* @__PURE__ */ new Map();
|
|
28
|
+
#nextHandle = 0;
|
|
29
|
+
constructor(existingApp) {
|
|
30
|
+
this.#app = existingApp ?? expressLib();
|
|
31
|
+
this.#router = Router();
|
|
32
|
+
this.#app.use(this.#router);
|
|
33
|
+
}
|
|
34
|
+
setupSentryErrorHandler(sentry) {
|
|
35
|
+
sentry.setupExpressErrorHandler(this.#app);
|
|
36
|
+
}
|
|
37
|
+
get handle() {
|
|
38
|
+
return this.#app;
|
|
39
|
+
}
|
|
40
|
+
mountRawMiddleware(middleware) {
|
|
41
|
+
this.#app.use(middleware);
|
|
42
|
+
}
|
|
43
|
+
mountNodeRoute(method, path, handler) {
|
|
44
|
+
this.#replaceDuplicates("node", path, method.toUpperCase());
|
|
45
|
+
const handle = this.#nextHandle++;
|
|
46
|
+
this.#routes.set(handle, {
|
|
47
|
+
kind: "node",
|
|
48
|
+
method: method.toUpperCase(),
|
|
49
|
+
path,
|
|
50
|
+
matcher: match(normalizePath(path)),
|
|
51
|
+
handler
|
|
52
|
+
});
|
|
53
|
+
return handle;
|
|
54
|
+
}
|
|
55
|
+
setupMiddleware({ corsOptions, bodyLimit = "50mb" }) {
|
|
56
|
+
this.#router.use(cors(corsOptions));
|
|
57
|
+
this.#router.use(bodyParser.json({ limit: bodyLimit }));
|
|
58
|
+
this.#router.use(bodyParser.urlencoded({
|
|
59
|
+
extended: true,
|
|
60
|
+
limit: bodyLimit
|
|
61
|
+
}));
|
|
62
|
+
this.#router.use((req, res, next) => {
|
|
63
|
+
const pathname = req.path;
|
|
64
|
+
for (const entry of this.#routes.values()) {
|
|
65
|
+
if (entry.kind !== "fetch" || !entry.matcher(pathname)) continue;
|
|
66
|
+
this.#serveFetchHandler(entry.handler, req, res, next);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (req.method === "GET" || req.method === "HEAD") for (const entry of this.#routes.values()) {
|
|
70
|
+
if (entry.kind !== "get" || !entry.matcher(pathname)) continue;
|
|
71
|
+
this.#serveGetEntry(entry, req, res);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const entry of this.#routes.values()) {
|
|
75
|
+
if (entry.kind !== "node") continue;
|
|
76
|
+
if (entry.method !== req.method) continue;
|
|
77
|
+
const matched = entry.matcher(pathname);
|
|
78
|
+
if (!matched) continue;
|
|
79
|
+
req.params = matched.params;
|
|
80
|
+
entry.handler(req, res, req.body);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
next();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
mount(path, handler, { exact = false } = {}) {
|
|
87
|
+
this.#replaceDuplicates("fetch", path);
|
|
88
|
+
const handle = this.#nextHandle++;
|
|
89
|
+
this.#routes.set(handle, {
|
|
90
|
+
kind: "fetch",
|
|
91
|
+
path,
|
|
92
|
+
matcher: match(normalizePath(path), { end: !exact }),
|
|
93
|
+
prefix: exact,
|
|
94
|
+
handler
|
|
95
|
+
});
|
|
96
|
+
return handle;
|
|
97
|
+
}
|
|
98
|
+
getRoute(path, handler) {
|
|
99
|
+
this.#replaceDuplicates("get", path);
|
|
100
|
+
const handle = this.#nextHandle++;
|
|
101
|
+
this.#routes.set(handle, {
|
|
102
|
+
kind: "get",
|
|
103
|
+
path,
|
|
104
|
+
matcher: match(normalizePath(path)),
|
|
105
|
+
handler
|
|
106
|
+
});
|
|
107
|
+
return handle;
|
|
108
|
+
}
|
|
109
|
+
unmount(handle) {
|
|
110
|
+
this.#routes.delete(handle);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Drop any existing entry the new registration would duplicate (same kind
|
|
114
|
+
* and path, plus method for node routes) so a re-mount replaces instead of
|
|
115
|
+
* shadowing. Map iteration tolerates concurrent deletion.
|
|
116
|
+
*/
|
|
117
|
+
#replaceDuplicates(kind, path, method) {
|
|
118
|
+
for (const [handle, entry] of this.#routes) {
|
|
119
|
+
if (entry.kind !== kind || entry.path !== path) continue;
|
|
120
|
+
if (kind === "node" && entry.kind === "node" && entry.method !== method) continue;
|
|
121
|
+
this.#routes.delete(handle);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async listen(port, tls) {
|
|
125
|
+
let server;
|
|
126
|
+
if (tls === true) {
|
|
127
|
+
const { cert, key } = await devcert.certificateFor("localhost");
|
|
128
|
+
if (!cert || !key) throw new Error("Invalid certificate generated");
|
|
129
|
+
server = https.createServer({
|
|
130
|
+
cert,
|
|
131
|
+
key
|
|
132
|
+
}, this.#app);
|
|
133
|
+
} else if (tls && "keyPath" in tls) {
|
|
134
|
+
const currentDir = process.cwd();
|
|
135
|
+
server = https.createServer({
|
|
136
|
+
key: fs.readFileSync(path.join(currentDir, tls.keyPath)),
|
|
137
|
+
cert: fs.readFileSync(path.join(currentDir, tls.certPath))
|
|
138
|
+
}, this.#app);
|
|
139
|
+
} else if (tls && "cert" in tls) server = https.createServer({
|
|
140
|
+
cert: tls.cert,
|
|
141
|
+
key: tls.key
|
|
142
|
+
}, this.#app);
|
|
143
|
+
else server = http.createServer(this.#app);
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
server.once("error", reject);
|
|
146
|
+
server.listen(port, () => {
|
|
147
|
+
server.off("error", reject);
|
|
148
|
+
resolve(server);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
#serveFetchHandler(handler, req, res, next) {
|
|
153
|
+
const url = `${req.protocol}://${req.get("host") ?? "localhost"}${req.originalUrl}`;
|
|
154
|
+
const headers = new Headers();
|
|
155
|
+
for (const [key, value] of Object.entries(req.headers)) if (typeof value === "string") headers.set(key, value);
|
|
156
|
+
else if (Array.isArray(value)) headers.set(key, value.join(", "));
|
|
157
|
+
let body;
|
|
158
|
+
if (req.method !== "GET" && req.method !== "HEAD" && req.body !== void 0) body = JSON.stringify(req.body);
|
|
159
|
+
const fetchRequest = new Request(url, {
|
|
160
|
+
method: req.method,
|
|
161
|
+
headers,
|
|
162
|
+
body
|
|
163
|
+
});
|
|
164
|
+
Promise.resolve(handler(fetchRequest)).then(async (response) => {
|
|
165
|
+
res.status(response.status);
|
|
166
|
+
response.headers.forEach((value, key) => {
|
|
167
|
+
res.setHeader(key, value);
|
|
168
|
+
});
|
|
169
|
+
if (response.body === null) {
|
|
170
|
+
res.send(await response.text());
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const bodyStream = response.body;
|
|
174
|
+
const nodeStream = Readable.fromWeb(bodyStream);
|
|
175
|
+
req.on("close", () => {
|
|
176
|
+
nodeStream.destroy();
|
|
177
|
+
});
|
|
178
|
+
nodeStream.on("error", (err) => {
|
|
179
|
+
if (res.headersSent) res.destroy();
|
|
180
|
+
else next(err);
|
|
181
|
+
});
|
|
182
|
+
nodeStream.pipe(res);
|
|
183
|
+
}).catch(next);
|
|
184
|
+
}
|
|
185
|
+
#serveGetEntry(entry, req, res) {
|
|
186
|
+
const url = `${req.protocol}://${req.get("host") ?? "localhost"}${req.originalUrl}`;
|
|
187
|
+
const headers = new Headers();
|
|
188
|
+
for (const [key, value] of Object.entries(req.headers)) if (typeof value === "string") headers.set(key, value);
|
|
189
|
+
else if (Array.isArray(value)) headers.set(key, value.join(", "));
|
|
190
|
+
const fetchRequest = new Request(url, {
|
|
191
|
+
method: "GET",
|
|
192
|
+
headers
|
|
193
|
+
});
|
|
194
|
+
Promise.resolve(entry.handler(fetchRequest)).then(async (response) => {
|
|
195
|
+
res.status(response.status);
|
|
196
|
+
response.headers.forEach((value, key) => {
|
|
197
|
+
res.setHeader(key, value);
|
|
198
|
+
});
|
|
199
|
+
if (response.body === null) {
|
|
200
|
+
res.send(await response.text());
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const bodyStream = response.body;
|
|
204
|
+
const nodeStream = Readable.fromWeb(bodyStream);
|
|
205
|
+
req.on("close", () => {
|
|
206
|
+
nodeStream.destroy();
|
|
207
|
+
});
|
|
208
|
+
nodeStream.on("error", (err) => {
|
|
209
|
+
if (res.headersSent) res.destroy();
|
|
210
|
+
else res.status(500).send(String(err));
|
|
211
|
+
});
|
|
212
|
+
nodeStream.pipe(res);
|
|
213
|
+
}).catch((err) => {
|
|
214
|
+
res.status(500).send(String(err));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
function createExpressHttpAdapter(existingApp) {
|
|
219
|
+
return { adapter: new ExpressHttpAdapter(existingApp) };
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { createExpressHttpAdapter };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=adapter-http-express-CgVPgEMm.mjs.map
|
|
225
|
+
//# debugId=d26eb9a2-eb77-534d-8262-7614f536bac3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-http-express-CgVPgEMm.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 { Readable } from \"node:stream\";\nimport type { ReadableStream as WebReadableStream } from \"node:stream/web\";\nimport { match, type MatchFunction, type ParamData } from \"path-to-regexp\";\nimport type {\n FetchHandler,\n IHttpAdapter,\n RouteHandle,\n TlsOptions,\n} from \"./types.js\";\nimport { normalizePath } from \"./path-normalize.js\";\n\ntype GetHandler = (r: Request) => Response | Promise<Response>;\n\ntype NodeHandler = (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body?: unknown,\n) => void | Promise<void>;\n\ntype RouteEntry =\n | {\n kind: \"fetch\";\n path: string;\n matcher: MatchFunction<ParamData>;\n prefix: boolean;\n handler: FetchHandler;\n }\n | {\n kind: \"get\";\n path: string;\n matcher: MatchFunction<ParamData>;\n handler: GetHandler;\n }\n | {\n kind: \"node\";\n method: string;\n path: string;\n matcher: MatchFunction<ParamData>;\n handler: NodeHandler;\n };\n\nexport class ExpressHttpAdapter implements IHttpAdapter {\n readonly #app: Express;\n readonly #router: IRouter;\n\n /**\n * Every route lives in this registry and is served by one\n * permanently-registered dispatcher (installed by setupMiddleware), because\n * Express 4 has no route-removal API: routes registered directly on the\n * app or router can never be taken back. Entries iterate in registration\n * order; mounting a path that already holds a route of the same kind (and\n * method, for node routes) replaces the old entry, so re-mounting is\n * last-write-wins without accumulating.\n */\n readonly #routes = new Map<RouteHandle, RouteEntry>();\n #nextHandle = 0;\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 // Connect/Express middleware shape; the caller owns its typing.\n this.#app.use(middleware as express.RequestHandler);\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 ): RouteHandle {\n this.#replaceDuplicates(\"node\", path, method.toUpperCase());\n const handle = this.#nextHandle++;\n this.#routes.set(handle, {\n kind: \"node\",\n method: method.toUpperCase(),\n path,\n matcher: match(normalizePath(path)),\n handler,\n });\n return handle;\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 // The single dispatch point for every registered route, registered\n // AFTER the body parsers so node routes receive a parsed req.body.\n // A request matching no route falls through to app-level middleware\n // (mountRawMiddleware) and then Express's default 404.\n this.#router.use((req, res, next) => {\n const pathname = req.path;\n\n // Fetch routes dispatch first: the pre-refactor in-router dispatcher\n // also took precedence over the app-level routes.\n for (const entry of this.#routes.values()) {\n if (entry.kind !== \"fetch\" || !entry.matcher(pathname)) continue;\n this.#serveFetchHandler(entry.handler, req, res, next);\n return;\n }\n\n if (req.method === \"GET\" || req.method === \"HEAD\") {\n for (const entry of this.#routes.values()) {\n if (entry.kind !== \"get\" || !entry.matcher(pathname)) continue;\n this.#serveGetEntry(entry, req, res);\n return;\n }\n }\n\n for (const entry of this.#routes.values()) {\n if (entry.kind !== \"node\") continue;\n if (entry.method !== req.method) continue;\n const matched = entry.matcher(pathname);\n if (!matched) continue;\n req.params = matched.params as Record<string, string>;\n // Fire-and-forget, as before: the node handler manages its own\n // response and the adapter does not await its promise.\n void entry.handler(req, res, req.body as unknown);\n return;\n }\n\n next();\n });\n }\n\n mount(\n path: string,\n handler: FetchHandler,\n { exact = false }: { exact?: boolean } = {},\n ): RouteHandle {\n this.#replaceDuplicates(\"fetch\", path);\n const handle = this.#nextHandle++;\n this.#routes.set(handle, {\n kind: \"fetch\",\n path,\n // exact=false → exact path match; exact=true → prefix match.\n matcher: match(normalizePath(path), { end: !exact }),\n prefix: exact,\n handler,\n });\n return handle;\n }\n\n getRoute(\n path: string,\n handler: (request: Request) => Response | Promise<Response>,\n ): RouteHandle {\n this.#replaceDuplicates(\"get\", path);\n const handle = this.#nextHandle++;\n this.#routes.set(handle, {\n kind: \"get\",\n path,\n matcher: match(normalizePath(path)),\n handler,\n });\n return handle;\n }\n\n unmount(handle: RouteHandle): void {\n this.#routes.delete(handle);\n }\n\n /**\n * Drop any existing entry the new registration would duplicate (same kind\n * and path, plus method for node routes) so a re-mount replaces instead of\n * shadowing. Map iteration tolerates concurrent deletion.\n */\n #replaceDuplicates(\n kind: RouteEntry[\"kind\"],\n path: string,\n method?: string,\n ): void {\n for (const [handle, entry] of this.#routes) {\n if (entry.kind !== kind || entry.path !== path) continue;\n if (kind === \"node\" && entry.kind === \"node\" && entry.method !== method) {\n continue;\n }\n this.#routes.delete(handle);\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 if (response.body === null) {\n res.send(await response.text());\n return;\n }\n // Stream the body to the socket instead of awaiting .text(): a\n // stream-backed body (e.g. graphql-sse) only closes when the\n // subscription completes, so awaiting would hang the request.\n // `response.body` is typed against the global (lib) ReadableStream\n // declaration, while Readable.fromWeb wants node:stream/web's; at\n // runtime they are the same stream, so the cast is safe.\n const bodyStream = response.body as WebReadableStream;\n const nodeStream = Readable.fromWeb(bodyStream);\n req.on(\"close\", () => {\n // The client went away mid-stream; stop pulling from the source.\n nodeStream.destroy();\n });\n nodeStream.on(\"error\", (err) => {\n if (res.headersSent) {\n // The response has already started; nothing left to say.\n res.destroy();\n } else {\n next(err);\n }\n });\n nodeStream.pipe(res);\n })\n .catch(next);\n }\n\n #serveGetEntry(\n entry: Extract<RouteEntry, { kind: \"get\" }>,\n req: express.Request,\n res: express.Response,\n ): void {\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 // GET is forced (matching the pre-refactor behavior, which also\n // normalized HEAD requests through the GET route handler).\n const fetchRequest = new Request(url, { method: \"GET\", headers });\n Promise.resolve(entry.handler(fetchRequest))\n .then(async (response) => {\n res.status(response.status);\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n if (response.body === null) {\n res.send(await response.text());\n return;\n }\n // Stream the body to the socket instead of awaiting .text(): a\n // stream-backed body (e.g. graphql-sse) only closes when the\n // subscription completes, so awaiting would hang the request.\n // `response.body` is typed against the global (lib) ReadableStream\n // declaration, while Readable.fromWeb wants node:stream/web's; at\n // runtime they are the same stream, so the cast is safe.\n const bodyStream = response.body as WebReadableStream;\n const nodeStream = Readable.fromWeb(bodyStream);\n req.on(\"close\", () => {\n // The client went away mid-stream; stop pulling from the source.\n nodeStream.destroy();\n });\n nodeStream.on(\"error\", (err) => {\n if (res.headersSent) {\n // The response has already started; nothing left to say.\n res.destroy();\n } else {\n res.status(500).send(String(err));\n }\n });\n nodeStream.pipe(res);\n })\n .catch((err: unknown) => {\n res.status(500).send(String(err));\n });\n }\n}\n\nexport function createExpressHttpAdapter(existingApp?: Express): {\n adapter: IHttpAdapter;\n} {\n return { adapter: new ExpressHttpAdapter(existingApp) };\n}\n"],"names":["#app","#router","#routes","#replaceDuplicates","#nextHandle","#serveFetchHandler","#serveGetEntry"],"mappings":";;;;;;;;;;;;;;AAsDA,IAAa,qBAAb,MAAwD;CACtD;CACA;;;;;;;;;;CAWA,0BAAmB,IAAI,KAA8B;CACrD,cAAc;CAEd,YAAY,aAAuB;AACjC,QAAA,MAAY,eAAe,YAAY;AACvC,QAAA,SAAe,QAAQ;AACvB,QAAA,IAAU,IAAI,MAAA,OAAa;;CAG7B,wBAAwB,QAAsB;AAClC,SAGR,yBAAyB,MAAA,IAAU;;CAGvC,IAAI,SAAkB;AACpB,SAAO,MAAA;;CAGT,mBAAmB,YAA2B;AAE5C,QAAA,IAAU,IAAI,WAAqC;;CAGrD,eACE,QACA,MACA,SAKa;AACb,QAAA,kBAAwB,QAAQ,MAAM,OAAO,aAAa,CAAC;EAC3D,MAAM,SAAS,MAAA;AACf,QAAA,OAAa,IAAI,QAAQ;GACvB,MAAM;GACN,QAAQ,OAAO,aAAa;GAC5B;GACA,SAAS,MAAM,cAAc,KAAK,CAAC;GACnC;GACD,CAAC;AACF,SAAO;;CAGT,gBAAgB,EACd,aACA,YAAY,UAIL;AACP,QAAA,OAAa,IAAI,KAAK,YAAY,CAAC;AACnC,QAAA,OAAa,IAAI,WAAW,KAAK,EAAE,OAAO,WAAW,CAAC,CAAC;AACvD,QAAA,OAAa,IACX,WAAW,WAAW;GAAE,UAAU;GAAM,OAAO;GAAW,CAAC,CAC5D;AAMD,QAAA,OAAa,KAAK,KAAK,KAAK,SAAS;GACnC,MAAM,WAAW,IAAI;AAIrB,QAAK,MAAM,SAAS,MAAA,OAAa,QAAQ,EAAE;AACzC,QAAI,MAAM,SAAS,WAAW,CAAC,MAAM,QAAQ,SAAS,CAAE;AACxD,UAAA,kBAAwB,MAAM,SAAS,KAAK,KAAK,KAAK;AACtD;;AAGF,OAAI,IAAI,WAAW,SAAS,IAAI,WAAW,OACzC,MAAK,MAAM,SAAS,MAAA,OAAa,QAAQ,EAAE;AACzC,QAAI,MAAM,SAAS,SAAS,CAAC,MAAM,QAAQ,SAAS,CAAE;AACtD,UAAA,cAAoB,OAAO,KAAK,IAAI;AACpC;;AAIJ,QAAK,MAAM,SAAS,MAAA,OAAa,QAAQ,EAAE;AACzC,QAAI,MAAM,SAAS,OAAQ;AAC3B,QAAI,MAAM,WAAW,IAAI,OAAQ;IACjC,MAAM,UAAU,MAAM,QAAQ,SAAS;AACvC,QAAI,CAAC,QAAS;AACd,QAAI,SAAS,QAAQ;AAGhB,UAAM,QAAQ,KAAK,KAAK,IAAI,KAAgB;AACjD;;AAGF,SAAM;IACN;;CAGJ,MACE,MACA,SACA,EAAE,QAAQ,UAA+B,EAAE,EAC9B;AACb,QAAA,kBAAwB,SAAS,KAAK;EACtC,MAAM,SAAS,MAAA;AACf,QAAA,OAAa,IAAI,QAAQ;GACvB,MAAM;GACN;GAEA,SAAS,MAAM,cAAc,KAAK,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC;GACpD,QAAQ;GACR;GACD,CAAC;AACF,SAAO;;CAGT,SACE,MACA,SACa;AACb,QAAA,kBAAwB,OAAO,KAAK;EACpC,MAAM,SAAS,MAAA;AACf,QAAA,OAAa,IAAI,QAAQ;GACvB,MAAM;GACN;GACA,SAAS,MAAM,cAAc,KAAK,CAAC;GACnC;GACD,CAAC;AACF,SAAO;;CAGT,QAAQ,QAA2B;AACjC,QAAA,OAAa,OAAO,OAAO;;;;;;;CAQ7B,mBACE,MACA,MACA,QACM;AACN,OAAK,MAAM,CAAC,QAAQ,UAAU,MAAA,QAAc;AAC1C,OAAI,MAAM,SAAS,QAAQ,MAAM,SAAS,KAAM;AAChD,OAAI,SAAS,UAAU,MAAM,SAAS,UAAU,MAAM,WAAW,OAC/D;AAEF,SAAA,OAAa,OAAO,OAAO;;;CAI/B,MAAM,OAAO,MAAc,KAAwC;EACjE,IAAI;AAEJ,MAAI,QAAQ,MAAM;GAChB,MAAM,EAAE,MAAM,QAAS,MAAM,QAAQ,eAAe,YAAY;AAIhE,OAAI,CAAC,QAAQ,CAAC,IACZ,OAAM,IAAI,MAAM,gCAAgC;AAElD,YAAS,MAAM,aAAa;IAAE;IAAM;IAAK,EAAE,MAAA,IAAU;aAC5C,OAAO,aAAa,KAAK;GAClC,MAAM,aAAa,QAAQ,KAAK;AAChC,YAAS,MAAM,aACb;IACE,KAAK,GAAG,aAAa,KAAK,KAAK,YAAY,IAAI,QAAQ,CAAC;IACxD,MAAM,GAAG,aAAa,KAAK,KAAK,YAAY,IAAI,SAAS,CAAC;IAC3D,EACD,MAAA,IACD;aACQ,OAAO,UAAU,IAC1B,UAAS,MAAM,aAAa;GAAE,MAAM,IAAI;GAAM,KAAK,IAAI;GAAK,EAAE,MAAA,IAAU;MAExE,UAAS,KAAK,aAAa,MAAA,IAAU;AAGvC,SAAO,IAAI,SAAsB,SAAS,WAAW;AACnD,UAAO,KAAK,SAAS,OAAO;AAC5B,UAAO,OAAO,YAAY;AACxB,WAAO,IAAI,SAAS,OAAO;AAC3B,YAAQ,OAAO;KACf;IACF;;CAGJ,mBACE,SACA,KACA,KACA,MACM;EAIN,MAAM,MAAM,GAFK,IAAI,SAEG,KADX,IAAI,IAAI,OAAO,IAAI,cACI,IAAI;EAGxC,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,QAAQ,CACpD,KAAI,OAAO,UAAU,SACnB,SAAQ,IAAI,KAAK,MAAM;WACd,MAAM,QAAQ,MAAM,CAC7B,SAAQ,IAAI,KAAK,MAAM,KAAK,KAAK,CAAC;EAMtC,IAAI;AACJ,MACE,IAAI,WAAW,SACf,IAAI,WAAW,UACf,IAAI,SAAS,KAAA,EAEb,QAAO,KAAK,UAAU,IAAI,KAAK;EAGjC,MAAM,eAAe,IAAI,QAAQ,KAAK;GACpC,QAAQ,IAAI;GACZ;GACA;GACD,CAAC;AAEF,UAAQ,QAAQ,QAAQ,aAAa,CAAC,CACnC,KAAK,OAAO,aAAa;AACxB,OAAI,OAAO,SAAS,OAAO;AAC3B,YAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,QAAI,UAAU,KAAK,MAAM;KACzB;AACF,OAAI,SAAS,SAAS,MAAM;AAC1B,QAAI,KAAK,MAAM,SAAS,MAAM,CAAC;AAC/B;;GAQF,MAAM,aAAa,SAAS;GAC5B,MAAM,aAAa,SAAS,QAAQ,WAAW;AAC/C,OAAI,GAAG,eAAe;AAEpB,eAAW,SAAS;KACpB;AACF,cAAW,GAAG,UAAU,QAAQ;AAC9B,QAAI,IAAI,YAEN,KAAI,SAAS;QAEb,MAAK,IAAI;KAEX;AACF,cAAW,KAAK,IAAI;IACpB,CACD,MAAM,KAAK;;CAGhB,eACE,OACA,KACA,KACM;EAGN,MAAM,MAAM,GAFK,IAAI,SAEG,KADX,IAAI,IAAI,OAAO,IAAI,cACI,IAAI;EACxC,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,QAAQ,CACpD,KAAI,OAAO,UAAU,SACnB,SAAQ,IAAI,KAAK,MAAM;WACd,MAAM,QAAQ,MAAM,CAC7B,SAAQ,IAAI,KAAK,MAAM,KAAK,KAAK,CAAC;EAKtC,MAAM,eAAe,IAAI,QAAQ,KAAK;GAAE,QAAQ;GAAO;GAAS,CAAC;AACjE,UAAQ,QAAQ,MAAM,QAAQ,aAAa,CAAC,CACzC,KAAK,OAAO,aAAa;AACxB,OAAI,OAAO,SAAS,OAAO;AAC3B,YAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,QAAI,UAAU,KAAK,MAAM;KACzB;AACF,OAAI,SAAS,SAAS,MAAM;AAC1B,QAAI,KAAK,MAAM,SAAS,MAAM,CAAC;AAC/B;;GAQF,MAAM,aAAa,SAAS;GAC5B,MAAM,aAAa,SAAS,QAAQ,WAAW;AAC/C,OAAI,GAAG,eAAe;AAEpB,eAAW,SAAS;KACpB;AACF,cAAW,GAAG,UAAU,QAAQ;AAC9B,QAAI,IAAI,YAEN,KAAI,SAAS;QAEb,KAAI,OAAO,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC;KAEnC;AACF,cAAW,KAAK,IAAI;IACpB,CACD,OAAO,QAAiB;AACvB,OAAI,OAAO,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC;IACjC;;;AAIR,SAAgB,yBAAyB,aAEvC;AACA,QAAO,EAAE,SAAS,IAAI,mBAAmB,YAAY,EAAE","debug_id":"d26eb9a2-eb77-534d-8262-7614f536bac3"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="370b6142-c566-5ae8-a8e4-8e442cb3e761")}catch(e){}}();
|
|
3
|
+
import { t as normalizePath } from "./path-normalize-DWWFLiy6.mjs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { match } from "path-to-regexp";
|
|
5
6
|
import fs from "node:fs";
|
|
@@ -7,18 +8,11 @@ import http from "node:http";
|
|
|
7
8
|
import https from "node:https";
|
|
8
9
|
import Fastify from "fastify";
|
|
9
10
|
import devcert from "devcert";
|
|
11
|
+
import { Readable } from "node:stream";
|
|
10
12
|
import fastifyCors from "@fastify/cors";
|
|
11
13
|
import fastifyFormbody from "@fastify/formbody";
|
|
12
14
|
import fastifyMiddie from "@fastify/middie";
|
|
13
15
|
//#region src/graphql/gateway/adapter-http-fastify.ts
|
|
14
|
-
/**
|
|
15
|
-
* Normalises a route path for path-to-regexp v8:
|
|
16
|
-
* - Collapses duplicate slashes (e.g. "//explorer" → "/explorer")
|
|
17
|
-
* - Converts legacy optional-param syntax ":param?" → "{/:param}?"
|
|
18
|
-
*/
|
|
19
|
-
function normalizePath(path) {
|
|
20
|
-
return path.replace(/\/+/g, "/").replace(/:(\w+)\?/g, "{/:$1}");
|
|
21
|
-
}
|
|
22
16
|
/** Parses body-limit strings like "50mb" to bytes. */
|
|
23
17
|
function parseBodyLimit(limit) {
|
|
24
18
|
const m = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i.exec(limit.trim());
|
|
@@ -32,9 +26,10 @@ function parseBodyLimit(limit) {
|
|
|
32
26
|
}[(m[2] ?? "b").toLowerCase()] ?? 1));
|
|
33
27
|
}
|
|
34
28
|
var FastifyHttpAdapter = class {
|
|
35
|
-
#fetchRoutes =
|
|
29
|
+
#fetchRoutes = /* @__PURE__ */ new Map();
|
|
36
30
|
#getRoutes = /* @__PURE__ */ new Map();
|
|
37
|
-
#nodeRoutes =
|
|
31
|
+
#nodeRoutes = /* @__PURE__ */ new Map();
|
|
32
|
+
#nextHandle = 0;
|
|
38
33
|
#setupOps = [];
|
|
39
34
|
#instance;
|
|
40
35
|
get handle() {
|
|
@@ -48,24 +43,41 @@ var FastifyHttpAdapter = class {
|
|
|
48
43
|
});
|
|
49
44
|
}
|
|
50
45
|
mount(path, handler, { exact = false } = {}) {
|
|
51
|
-
this.#fetchRoutes.
|
|
46
|
+
for (const [handle, entry] of this.#fetchRoutes) if (entry.path === path) this.#fetchRoutes.delete(handle);
|
|
47
|
+
const handle = this.#nextHandle++;
|
|
48
|
+
this.#fetchRoutes.set(handle, {
|
|
49
|
+
path,
|
|
52
50
|
handler,
|
|
53
51
|
matcher: match(normalizePath(path), { end: !exact }),
|
|
54
52
|
prefix: exact
|
|
55
53
|
});
|
|
54
|
+
return handle;
|
|
56
55
|
}
|
|
57
56
|
getRoute(path, handler) {
|
|
58
|
-
this.#getRoutes.
|
|
57
|
+
for (const [handle, entry] of this.#getRoutes) if (entry.path === path) this.#getRoutes.delete(handle);
|
|
58
|
+
const handle = this.#nextHandle++;
|
|
59
|
+
this.#getRoutes.set(handle, {
|
|
60
|
+
path,
|
|
59
61
|
handler,
|
|
60
62
|
matcher: match(normalizePath(path))
|
|
61
63
|
});
|
|
64
|
+
return handle;
|
|
62
65
|
}
|
|
63
66
|
mountNodeRoute(method, path, handler) {
|
|
64
|
-
this.#nodeRoutes.
|
|
67
|
+
for (const [handle, entry] of this.#nodeRoutes) if (entry.path === path && entry.method === method) this.#nodeRoutes.delete(handle);
|
|
68
|
+
const handle = this.#nextHandle++;
|
|
69
|
+
this.#nodeRoutes.set(handle, {
|
|
70
|
+
path,
|
|
65
71
|
method,
|
|
66
72
|
matcher: match(normalizePath(path)),
|
|
67
73
|
handler
|
|
68
74
|
});
|
|
75
|
+
return handle;
|
|
76
|
+
}
|
|
77
|
+
unmount(handle) {
|
|
78
|
+
this.#fetchRoutes.delete(handle);
|
|
79
|
+
this.#getRoutes.delete(handle);
|
|
80
|
+
this.#nodeRoutes.delete(handle);
|
|
69
81
|
}
|
|
70
82
|
mountRawMiddleware(middleware) {
|
|
71
83
|
if (this.#instance) this.#instance.use(middleware);
|
|
@@ -135,7 +147,7 @@ var FastifyHttpAdapter = class {
|
|
|
135
147
|
#dispatch(req, reply) {
|
|
136
148
|
const pathname = new URL(req.url, "http://localhost").pathname;
|
|
137
149
|
const method = req.method.toUpperCase();
|
|
138
|
-
for (const entry of this.#nodeRoutes) {
|
|
150
|
+
for (const entry of this.#nodeRoutes.values()) {
|
|
139
151
|
if (entry.method !== method) continue;
|
|
140
152
|
const result = entry.matcher(pathname);
|
|
141
153
|
if (!result) continue;
|
|
@@ -147,10 +159,7 @@ var FastifyHttpAdapter = class {
|
|
|
147
159
|
if (method === "GET") {
|
|
148
160
|
for (const entry of this.#getRoutes.values()) if (entry.matcher(pathname)) return this.#serveGetEntry(entry, req, reply);
|
|
149
161
|
}
|
|
150
|
-
for (
|
|
151
|
-
const entry = this.#fetchRoutes[i];
|
|
152
|
-
if (entry.matcher(pathname)) return serveFetchHandler(entry.handler, req, reply);
|
|
153
|
-
}
|
|
162
|
+
for (const entry of [...this.#fetchRoutes.values()].reverse()) if (entry.matcher(pathname)) return serveFetchHandler(entry.handler, req, reply);
|
|
154
163
|
reply.status(404).send({
|
|
155
164
|
message: `Route ${req.method}:${pathname} not found`,
|
|
156
165
|
error: "Not Found",
|
|
@@ -166,7 +175,9 @@ var FastifyHttpAdapter = class {
|
|
|
166
175
|
});
|
|
167
176
|
const response = await entry.handler(fetchReq);
|
|
168
177
|
writeResponse(reply, response);
|
|
169
|
-
return reply.send(await response.text());
|
|
178
|
+
if (response.body === null) return reply.send(await response.text());
|
|
179
|
+
const bodyStream = response.body;
|
|
180
|
+
return reply.send(Readable.fromWeb(bodyStream));
|
|
170
181
|
}
|
|
171
182
|
};
|
|
172
183
|
async function serveFetchHandler(handler, req, reply) {
|
|
@@ -180,11 +191,12 @@ async function serveFetchHandler(handler, req, reply) {
|
|
|
180
191
|
body
|
|
181
192
|
}));
|
|
182
193
|
writeResponse(reply, response);
|
|
183
|
-
return reply.send(await response.text());
|
|
194
|
+
if (response.body === null) return reply.send(await response.text());
|
|
195
|
+
const bodyStream = response.body;
|
|
196
|
+
return reply.send(Readable.fromWeb(bodyStream));
|
|
184
197
|
}
|
|
185
198
|
function buildUrl(req) {
|
|
186
|
-
|
|
187
|
-
return `${req.protocol}://${host}${req.url}`;
|
|
199
|
+
return `${req.protocol}://${req.headers.host ?? "localhost"}${req.url}`;
|
|
188
200
|
}
|
|
189
201
|
function buildHeaders(req) {
|
|
190
202
|
const headers = new Headers();
|
|
@@ -193,7 +205,7 @@ function buildHeaders(req) {
|
|
|
193
205
|
return headers;
|
|
194
206
|
}
|
|
195
207
|
function writeResponse(reply, response) {
|
|
196
|
-
reply.
|
|
208
|
+
reply.statusCode = response.status;
|
|
197
209
|
response.headers.forEach((value, key) => {
|
|
198
210
|
reply.header(key, value);
|
|
199
211
|
});
|
|
@@ -204,5 +216,5 @@ function createFastifyHttpAdapter() {
|
|
|
204
216
|
//#endregion
|
|
205
217
|
export { createFastifyHttpAdapter };
|
|
206
218
|
|
|
207
|
-
//# sourceMappingURL=adapter-http-fastify-
|
|
208
|
-
//# debugId=
|
|
219
|
+
//# sourceMappingURL=adapter-http-fastify-D98iRljY.mjs.map
|
|
220
|
+
//# debugId=370b6142-c566-5ae8-a8e4-8e442cb3e761
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-http-fastify-D98iRljY.mjs","sources":["../src/graphql/gateway/adapter-http-fastify.ts"],"sourcesContent":["import fastifyCors from \"@fastify/cors\";\nimport fastifyFormbody from \"@fastify/formbody\";\nimport fastifyMiddie from \"@fastify/middie\";\nimport type { CorsOptions } from \"cors\";\nimport devcert from \"devcert\";\nimport Fastify from \"fastify\";\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport nodePath from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as WebReadableStream } from \"node:stream/web\";\nimport { match, type MatchFunction, type ParamData } from \"path-to-regexp\";\nimport type {\n FetchHandler,\n IHttpAdapter,\n RouteHandle,\n TlsOptions,\n} from \"./types.js\";\nimport { normalizePath } from \"./path-normalize.js\";\n\n/** Parses body-limit strings like \"50mb\" to bytes. */\nfunction parseBodyLimit(limit: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb|gb)?$/i.exec(limit.trim());\n if (!m) return 52_428_800; // 50 MB fallback\n const n = parseFloat(m[1]);\n const units: Record<string, number> = {\n b: 1,\n kb: 1_024,\n mb: 1_048_576,\n gb: 1_073_741_824,\n };\n return Math.round(n * (units[(m[2] ?? \"b\").toLowerCase()] ?? 1));\n}\n\ntype FetchEntry = {\n path: string;\n handler: FetchHandler;\n matcher: MatchFunction<ParamData>;\n prefix: boolean;\n};\n\ntype GetEntry = {\n path: string;\n handler: (r: Request) => Response | Promise<Response>;\n matcher: MatchFunction<ParamData>;\n};\n\ntype NodeHandler = (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body?: unknown,\n) => void;\n\ntype NodeEntry = {\n path: string;\n method: \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\";\n matcher: MatchFunction<ParamData>;\n handler: NodeHandler;\n};\n\n// Pre-listen configuration ops (need the Fastify instance to apply).\ntype SetupOp =\n | { kind: \"cors\"; options?: CorsOptions; bodyLimit: number }\n | { kind: \"middie\"; middleware: unknown };\n\nexport class FastifyHttpAdapter implements IHttpAdapter {\n // Dispatch maps, keyed by route handle and iterated in registration\n // order — populated at any time (before or after listen). Mounting a\n // path that already holds a route of the same kind (and method, for\n // node routes) replaces the old entry, so re-mounting is last-write-\n // wins without accumulating; unmount() removes any entry.\n readonly #fetchRoutes = new Map<RouteHandle, FetchEntry>();\n readonly #getRoutes = new Map<RouteHandle, GetEntry>();\n readonly #nodeRoutes = new Map<RouteHandle, NodeEntry>();\n #nextHandle = 0;\n\n // Ops that need the Fastify instance (CORS config, Connect middleware).\n readonly #setupOps: SetupOp[] = [];\n\n #instance: FastifyInstance | undefined;\n\n get handle(): unknown {\n return this.#instance;\n }\n\n setupMiddleware({\n corsOptions,\n bodyLimit = \"50mb\",\n }: {\n corsOptions?: CorsOptions;\n bodyLimit?: string;\n }): void {\n this.#setupOps.push({\n kind: \"cors\",\n options: corsOptions,\n bodyLimit: parseBodyLimit(bodyLimit),\n });\n }\n\n mount(\n path: string,\n handler: FetchHandler,\n { exact = false }: { exact?: boolean } = {},\n ): RouteHandle {\n for (const [handle, entry] of this.#fetchRoutes) {\n if (entry.path === path) this.#fetchRoutes.delete(handle);\n }\n const handle = this.#nextHandle++;\n this.#fetchRoutes.set(handle, {\n path,\n handler,\n // exact=false → exact path match; exact=true → prefix match.\n matcher: match(normalizePath(path), { end: !exact }),\n prefix: exact,\n });\n return handle;\n }\n\n getRoute(\n path: string,\n handler: (r: Request) => Response | Promise<Response>,\n ): RouteHandle {\n for (const [handle, entry] of this.#getRoutes) {\n if (entry.path === path) this.#getRoutes.delete(handle);\n }\n const handle = this.#nextHandle++;\n this.#getRoutes.set(handle, {\n path,\n handler,\n matcher: match(normalizePath(path)),\n });\n return handle;\n }\n\n mountNodeRoute(\n method: \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\",\n path: string,\n handler: NodeHandler,\n ): RouteHandle {\n for (const [handle, entry] of this.#nodeRoutes) {\n if (entry.path === path && entry.method === method) {\n this.#nodeRoutes.delete(handle);\n }\n }\n const handle = this.#nextHandle++;\n this.#nodeRoutes.set(handle, {\n path,\n method,\n matcher: match(normalizePath(path)),\n handler,\n });\n return handle;\n }\n\n unmount(handle: RouteHandle): void {\n this.#fetchRoutes.delete(handle);\n this.#getRoutes.delete(handle);\n this.#nodeRoutes.delete(handle);\n }\n\n mountRawMiddleware(middleware: unknown): void {\n if (this.#instance) {\n // @fastify/middie is always registered, so .use() is available post-listen.\n (\n this.#instance as FastifyInstance & {\n use(middleware: unknown): FastifyInstance;\n }\n ).use(middleware);\n } else {\n this.#setupOps.push({ kind: \"middie\", middleware });\n }\n }\n\n setupSentryErrorHandler(sentry: object): void {\n if (!this.#instance) return;\n const s = sentry as {\n setupFastifyErrorHandler(app: FastifyInstance): void;\n };\n s.setupFastifyErrorHandler(this.#instance);\n }\n\n async listen(port: number, tls?: TlsOptions): Promise<http.Server> {\n let httpServer: 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) throw new Error(\"Invalid certificate generated\");\n httpServer = https.createServer({ cert, key });\n } else if (tls && \"keyPath\" in tls) {\n const cwd = process.cwd();\n httpServer = https.createServer({\n key: fs.readFileSync(nodePath.join(cwd, tls.keyPath)),\n cert: fs.readFileSync(nodePath.join(cwd, tls.certPath)),\n });\n } else if (tls && \"cert\" in tls) {\n httpServer = https.createServer({ cert: tls.cert, key: tls.key });\n } else {\n httpServer = http.createServer();\n }\n\n const corsOp = this.#setupOps.find(\n (op): op is Extract<SetupOp, { kind: \"cors\" }> => op.kind === \"cors\",\n );\n const bodyLimit = corsOp?.bodyLimit ?? 52_428_800;\n\n const instance = Fastify({\n serverFactory: (handler) => {\n httpServer.on(\"request\", handler);\n return httpServer;\n },\n bodyLimit,\n logger: false,\n });\n\n this.#instance = instance;\n\n // Always register middie first so .use() is available post-listen.\n await instance.register(fastifyMiddie);\n await instance.register(fastifyFormbody);\n\n for (const op of this.#setupOps) {\n if (op.kind === \"cors\") {\n await instance.register(\n fastifyCors,\n op.options as Parameters<typeof fastifyCors>[1],\n );\n } else {\n (\n instance as FastifyInstance & {\n use(middleware: unknown): FastifyInstance;\n }\n ).use(op.middleware);\n }\n }\n\n // Single catch-all route — all dispatching is done via the Maps above so\n // that routes registered after listen() are picked up automatically.\n // OPTIONS is excluded because @fastify/cors registers its own OPTIONS /*\n // handler for preflight; including it here would cause a duplicate-route\n // conflict at startup.\n instance.route({\n method: [\"DELETE\", \"GET\", \"HEAD\", \"PATCH\", \"POST\", \"PUT\"],\n url: \"/*\",\n handler: (req, reply) => this.#dispatch(req, reply),\n });\n\n await instance.ready();\n\n return new Promise<http.Server>((resolve, reject) => {\n httpServer.once(\"error\", reject);\n httpServer.listen(port, () => {\n httpServer.off(\"error\", reject);\n resolve(httpServer);\n });\n });\n }\n\n #dispatch(req: FastifyRequest, reply: FastifyReply): void | Promise<void> {\n const pathname = new URL(req.url, \"http://localhost\").pathname;\n const method = req.method.toUpperCase() as\n | \"DELETE\"\n | \"GET\"\n | \"HEAD\"\n | \"PATCH\"\n | \"POST\"\n | \"PUT\";\n\n // 1. Node routes — path-to-regexp match + method, handler manages raw\n // response. Attach decoded params onto `req.raw` so downstream handlers\n // can read them via the same `req.params` API the Express adapter exposes.\n for (const entry of this.#nodeRoutes.values()) {\n if (entry.method !== method) continue;\n const result = entry.matcher(pathname);\n if (!result) continue;\n (req.raw as http.IncomingMessage & { params?: ParamData }).params =\n result.params;\n reply.hijack();\n entry.handler(req.raw, reply.raw, req.body);\n return;\n }\n\n // 2. GET-specific routes (health, explorer, etc.).\n if (method === \"GET\") {\n for (const entry of this.#getRoutes.values()) {\n if (entry.matcher(pathname)) {\n return this.#serveGetEntry(entry, req, reply);\n }\n }\n }\n\n // 3. Fetch routes (GraphQL handlers, SSE, etc.).\n // Iterate in reverse so that the last-mounted handler wins when the same\n // path is mounted more than once (e.g. supergraph remount after reload).\n for (const entry of [...this.#fetchRoutes.values()].reverse()) {\n if (entry.matcher(pathname)) {\n return serveFetchHandler(entry.handler, req, reply);\n }\n }\n\n void reply.status(404).send({\n message: `Route ${req.method}:${pathname} not found`,\n error: \"Not Found\",\n statusCode: 404,\n });\n }\n\n async #serveGetEntry(\n entry: GetEntry,\n req: FastifyRequest,\n reply: FastifyReply,\n ): Promise<void> {\n const url = buildUrl(req);\n const headers = buildHeaders(req);\n const fetchReq = new Request(url, { method: \"GET\", headers });\n const response = await entry.handler(fetchReq);\n writeResponse(reply, response);\n if (response.body === null) {\n return reply.send(await response.text());\n }\n // Stream the body instead of awaiting .text(): a stream-backed body\n // (e.g. graphql-sse) only closes when the subscription completes, so\n // awaiting would hang the request. Fastify tears the source down on\n // client disconnect and routes pre-header stream errors to the\n // error handler.\n // `response.body` is typed against the global (lib) ReadableStream\n // declaration, while Readable.fromWeb wants node:stream/web's; at\n // runtime they are the same stream, so the cast is safe.\n const bodyStream = response.body as WebReadableStream;\n return reply.send(Readable.fromWeb(bodyStream));\n }\n}\n\nasync function serveFetchHandler(\n handler: FetchHandler,\n req: FastifyRequest,\n reply: FastifyReply,\n): Promise<void> {\n const url = buildUrl(req);\n const headers = buildHeaders(req);\n let body: string | undefined;\n if (req.method !== \"GET\" && req.method !== \"HEAD\" && req.body !== undefined) {\n body = JSON.stringify(req.body);\n }\n const response = await handler(\n new Request(url, { method: req.method, headers, body }),\n );\n writeResponse(reply, response);\n if (response.body === null) {\n return reply.send(await response.text());\n }\n // Stream the body instead of awaiting .text(): a stream-backed body\n // (e.g. graphql-sse) only closes when the subscription completes, so\n // awaiting would hang the request. Fastify tears the source down on\n // client disconnect and routes pre-header stream errors to the\n // error handler.\n // `response.body` is typed against the global (lib) ReadableStream\n // declaration, while Readable.fromWeb wants node:stream/web's; at\n // runtime they are the same stream, so the cast is safe.\n const bodyStream = response.body as WebReadableStream;\n return reply.send(Readable.fromWeb(bodyStream));\n}\n\nfunction buildUrl(req: FastifyRequest): string {\n const protocol = req.protocol;\n const host = req.headers.host ?? \"localhost\";\n return `${protocol}://${host}${req.url}`;\n}\n\nfunction buildHeaders(req: FastifyRequest): 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 return headers;\n}\n\nfunction writeResponse(reply: FastifyReply, response: Response): void {\n reply.statusCode = response.status;\n response.headers.forEach((value, key) => {\n reply.header(key, value);\n });\n}\n\nexport function createFastifyHttpAdapter(): { adapter: IHttpAdapter } {\n return { adapter: new FastifyHttpAdapter() };\n}\n"],"names":["#fetchRoutes","#getRoutes","#nodeRoutes","#setupOps","#instance","#nextHandle","nodePath","#dispatch","#serveGetEntry"],"mappings":"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","debug_id":"370b6142-c566-5ae8-a8e4-8e442cb3e761"}
|