@dotpress/adapter-express 2.0.0-alpha.3 → 2.0.0-alpha.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/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  ```sh
8
8
  npm install @dotpress/core @dotpress/adapter-express express zod
9
+ npm install --save-dev @types/express
9
10
  ```
10
11
 
11
12
  ```ts
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse, Server } from 'node:http';
2
- import { HttpAdapter } from '@dotpress/core';
2
+ import { HttpAdapter, ListenAddress } from '@dotpress/core';
3
3
  import { Express } from 'express';
4
4
 
5
5
  /**
@@ -9,7 +9,7 @@ interface ExpressApplication {
9
9
  (req: IncomingMessage, res: ServerResponse): void;
10
10
  use(...handlers: unknown[]): unknown;
11
11
  }
12
- interface ExpressAdapterOptions<TApp extends ExpressApplication> {
12
+ interface ExpressAdapterOptions<TApp extends ExpressApplication = Express> {
13
13
  /**
14
14
  * Express application (Express 4 or 5) to mount the routes on. Defaults to
15
15
  * a new application, created with `express()`.
@@ -29,11 +29,13 @@ interface ExpressAdapterOptions<TApp extends ExpressApplication> {
29
29
  bodyLimit?: number;
30
30
  }
31
31
  interface ExpressAdapter<TApp extends ExpressApplication = Express> extends HttpAdapter<TApp> {
32
- /** Starts an HTTP server. */
33
- listen(port: number, host?: string): Promise<void>;
32
+ /** Starts an HTTP server, and returns its address. */
33
+ listen(port: number, host?: string): Promise<ListenAddress>;
34
34
  /**
35
- * Stops the HTTP server: waits for pending requests, and closes the
36
- * responses being streamed (their `req.signal` is aborted).
35
+ * Stops the HTTP server started by `listen()`: waits for pending
36
+ * requests. Closes the responses being streamed (their `req.signal` is
37
+ * aborted), also when the app serves the Express application with its own
38
+ * server.
37
39
  */
38
40
  close(): Promise<void>;
39
41
  /** HTTP server started by `listen()`, if any. */
package/dist/index.js CHANGED
@@ -3,17 +3,37 @@ import {
3
3
  createServer
4
4
  } from "node:http";
5
5
  import {
6
- DEFAULT_BODY_LIMIT,
7
6
  HttpError,
7
+ isHttpError
8
+ } from "@dotpress/core";
9
+ import {
10
+ DEFAULT_BODY_LIMIT,
8
11
  createRouteMatcher,
9
- isHttpError,
10
12
  malformedBodyError,
11
13
  parseQueryString,
12
14
  parseRequestBody,
13
15
  payloadTooLargeError,
14
16
  unsupportedMediaTypeError
15
- } from "@dotpress/core";
17
+ } from "@dotpress/core/adapter";
16
18
  import express from "express";
19
+
20
+ // src/options.ts
21
+ var assertKnownOptions = (label, options, known, prefix = "") => {
22
+ if (typeof options !== "object" || options === null) {
23
+ throw new TypeError(`${label}: options must be an object.`);
24
+ }
25
+ for (const key of Object.keys(options)) {
26
+ if (!known.includes(key)) {
27
+ throw new TypeError(`${label}: unknown option "${prefix}${key}".`);
28
+ }
29
+ }
30
+ };
31
+
32
+ // src/index.ts
33
+ var addressOf = (server) => {
34
+ const address = server.address();
35
+ return { port: address.port, host: address.address };
36
+ };
17
37
  var trackConnections = (server) => {
18
38
  const sockets = /* @__PURE__ */ new Set();
19
39
  let closing = false;
@@ -156,7 +176,6 @@ var fromNativeError = (error) => {
156
176
  code,
157
177
  ERROR_CODES[code] ?? "CLIENT_ERROR",
158
178
  expose === true && typeof message === "string" && message ? message : "Bad Request",
159
- void 0,
160
179
  { cause: error }
161
180
  );
162
181
  };
@@ -170,10 +189,16 @@ var waitForDrain = (res) => new Promise((resolve) => {
170
189
  res.on("close", done);
171
190
  });
172
191
  function expressAdapter(options = {}) {
192
+ assertKnownOptions("expressAdapter()", options, [
193
+ "app",
194
+ "beforeRoutes",
195
+ "afterRoutes",
196
+ "bodyLimit"
197
+ ]);
173
198
  const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
174
199
  if (!Number.isInteger(bodyLimit) || bodyLimit < 0) {
175
200
  throw new RangeError(
176
- "Invalid bodyLimit: expected a non-negative integer (bytes)."
201
+ "expressAdapter(): bodyLimit must be a non-negative integer (bytes)."
177
202
  );
178
203
  }
179
204
  let app = options.app;
@@ -352,15 +377,22 @@ function expressAdapter(options = {}) {
352
377
  });
353
378
  server = created;
354
379
  closeConnections = trackConnections(created);
380
+ return addressOf(created);
355
381
  },
356
382
  close: async () => {
357
383
  const current = server;
358
- if (!current) return;
359
384
  server = void 0;
385
+ const stopStreams = () => {
386
+ for (const stop of activeStreams) stop();
387
+ };
388
+ if (!current) {
389
+ stopStreams();
390
+ return;
391
+ }
360
392
  await new Promise((resolve, reject) => {
361
393
  current.close((error) => error ? reject(error) : resolve());
362
394
  closeConnections();
363
- for (const stop of activeStreams) stop();
395
+ stopStreams();
364
396
  });
365
397
  },
366
398
  native: () => application,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n createServer,\n type IncomingMessage,\n type RequestListener,\n type Server,\n type ServerResponse,\n} from 'node:http'\nimport type { Socket } from 'node:net'\nimport {\n DEFAULT_BODY_LIMIT,\n HttpError,\n createRouteMatcher,\n isHttpError,\n malformedBodyError,\n parseQueryString,\n parseRequestBody,\n payloadTooLargeError,\n unsupportedMediaTypeError,\n type CompiledRoute,\n type DotpressRuntime,\n type HttpAdapter,\n type NormalizedRequest,\n type NormalizedResponse,\n type RouteMatch,\n type UploadedFiles,\n} from '@dotpress/core'\nimport express, { type Express } from 'express'\n\n/**\n * Lets `close()` complete as soon as the pending requests are served, and\n * returns the function to call on close. Before Node.js 26, `server.close()`\n * waits for the connections that have sent nothing yet (a browser\n * preconnect…) until `headersTimeout`; and, whatever the version, the\n * connections of the pending requests stay open once served, until\n * `keepAliveTimeout`.\n */\nconst trackConnections = (server: Server) => {\n const sockets = new Set<Socket>()\n let closing = false\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.once('close', () => sockets.delete(socket))\n })\n server.on(\n 'request',\n (_request: IncomingMessage, response: ServerResponse) => {\n response.once('finish', () => {\n if (closing) server.closeIdleConnections()\n })\n }\n )\n return () => {\n closing = true\n for (const socket of sockets) {\n if (socket.bytesRead === 0) socket.destroy()\n }\n }\n}\n\n/**\n * Minimal shape of an Express application, compatible with Express 4 and 5.\n */\nexport interface ExpressApplication {\n (req: IncomingMessage, res: ServerResponse): void\n use(...handlers: unknown[]): unknown\n}\n\nexport interface ExpressAdapterOptions<TApp extends ExpressApplication> {\n /**\n * Express application (Express 4 or 5) to mount the routes on. Defaults to\n * a new application, created with `express()`.\n */\n app?: TApp\n /** Registers native middlewares before the Dotpress routes (helmet…). */\n beforeRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Registers native middlewares and routes after the Dotpress routes,\n * before the not-found handler.\n */\n afterRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Maximum request body size, in bytes. Defaults to 1 MiB. Routes can\n * override it with their own `bodyLimit`.\n */\n bodyLimit?: number\n}\n\nexport interface ExpressAdapter<\n TApp extends ExpressApplication = Express,\n> extends HttpAdapter<TApp> {\n /** Starts an HTTP server. */\n listen(port: number, host?: string): Promise<void>\n /**\n * Stops the HTTP server: waits for pending requests, and closes the\n * responses being streamed (their `req.signal` is aborted).\n */\n close(): Promise<void>\n /** HTTP server started by `listen()`, if any. */\n server(): Server | undefined\n}\n\n/** Express request fields used by the adapter (Express 4 and 5). */\ninterface ExpressRequest extends IncomingMessage {\n originalUrl?: string\n protocol?: string\n /** Client address, honoring the `trust proxy` setting. */\n ip?: string\n body?: unknown\n /** Set by body-parser and similar middlewares once the body is read. */\n _body?: boolean\n}\n\ntype NextFunction = (error?: unknown) => void\n\n/** Rejection reason when the client goes away while the body is read. */\nconst CLIENT_GONE = Symbol('client gone')\n\nconst pathnameOf = (url: string) => {\n const index = url.indexOf('?')\n return index === -1 ? url : url.slice(0, index)\n}\n\nconst searchOf = (url: string) => {\n const index = url.indexOf('?')\n return index === -1 ? '' : url.slice(index)\n}\n\nconst fullUrl = (req: ExpressRequest): string | undefined => {\n const host = req.headers.host\n if (!host) return undefined\n const protocol = req.protocol ?? 'http'\n return `${protocol}://${host}${req.originalUrl ?? req.url ?? '/'}`\n}\n\nconst hasBody = (req: IncomingMessage) =>\n req.headers['transfer-encoding'] !== undefined ||\n (req.headers['content-length'] !== undefined &&\n req.headers['content-length'] !== '0')\n\n/** Discards the rest of the request body. */\nconst discard = (req: IncomingMessage) => {\n req.resume()\n}\n\ninterface RequestBody {\n readonly body: unknown\n readonly files: UploadedFiles | undefined\n /** Undefined when a native middleware read the body. */\n readonly rawBody: Uint8Array | undefined\n}\n\nconst NO_BODY: RequestBody = {\n body: undefined,\n files: undefined,\n rawBody: undefined,\n}\n\nconst readBody = (req: ExpressRequest, limit: number): Promise<RequestBody> => {\n // The body was already read by a native middleware (express.json(),\n // multer…): use its result.\n if (req._body === true || req.readableEnded) {\n return Promise.resolve({\n body: req.body,\n files: undefined,\n rawBody: undefined,\n })\n }\n\n if (!hasBody(req)) {\n return Promise.resolve(NO_BODY)\n }\n\n const encoding = (req.headers['content-encoding'] ?? 'identity')\n .trim()\n .toLowerCase()\n\n if (encoding !== 'identity') {\n discard(req)\n return Promise.reject(\n unsupportedMediaTypeError(`Unsupported content encoding \"${encoding}\"`)\n )\n }\n\n const declaredLength = Number(req.headers['content-length'])\n if (Number.isFinite(declaredLength) && declaredLength > limit) {\n discard(req)\n return Promise.reject(payloadTooLargeError())\n }\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onGone)\n req.off('close', onClose)\n }\n\n function onData(chunk: Buffer) {\n size += chunk.byteLength\n if (size > limit) {\n cleanup()\n discard(req)\n reject(payloadTooLargeError())\n return\n }\n chunks.push(chunk)\n }\n\n function onEnd() {\n cleanup()\n const buffer = Buffer.concat(chunks, size)\n const bytes = new Uint8Array(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength\n )\n parseRequestBody(bytes, req.headers['content-type']).then(\n ({ body, files }) => resolve({ body, files, rawBody: bytes }),\n reject\n )\n }\n\n function onGone() {\n cleanup()\n reject(CLIENT_GONE)\n }\n\n function onClose() {\n if (!req.complete) onGone()\n }\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onGone)\n req.on('close', onClose)\n })\n}\n\nconst ERROR_CODES: Record<number, string> = {\n 400: 'BAD_REQUEST',\n 401: 'UNAUTHORIZED',\n 403: 'FORBIDDEN',\n 404: 'NOT_FOUND',\n 405: 'METHOD_NOT_ALLOWED',\n 408: 'REQUEST_TIMEOUT',\n 409: 'CONFLICT',\n 413: 'PAYLOAD_TOO_LARGE',\n 415: 'UNSUPPORTED_MEDIA_TYPE',\n 422: 'UNPROCESSABLE_ENTITY',\n 429: 'TOO_MANY_REQUESTS',\n}\n\n/**\n * Converts errors raised by native middlewares (`next(error)`) into Dotpress\n * errors. Client errors (4xx) keep their status; anything else is an\n * unexpected error.\n */\nconst fromNativeError = (error: unknown): unknown => {\n if (isHttpError(error) || typeof error !== 'object' || error === null) {\n return error\n }\n\n const { type, status, statusCode, expose, message } = error as {\n type?: unknown\n status?: unknown\n statusCode?: unknown\n expose?: unknown\n message?: unknown\n }\n\n if (type === 'entity.parse.failed') {\n return malformedBodyError()\n }\n\n const code = typeof status === 'number' ? status : statusCode\n if (typeof code !== 'number' || !Number.isInteger(code)) return error\n if (code < 400 || code > 499) return error\n\n return new HttpError(\n code,\n ERROR_CODES[code] ?? 'CLIENT_ERROR',\n expose === true && typeof message === 'string' && message\n ? message\n : 'Bad Request',\n undefined,\n { cause: error }\n )\n}\n\nconst waitForDrain = (res: ServerResponse) =>\n new Promise<void>((resolve) => {\n const done = () => {\n res.off('drain', done)\n res.off('close', done)\n resolve()\n }\n res.on('drain', done)\n res.on('close', done)\n })\n\n/**\n * Creates the Express adapter (Express 4 or 5).\n *\n * @example\n * const app = await createApp({\n * adapter: expressAdapter({\n * beforeRoutes: (app) => app.use(helmet()),\n * }),\n * })\n * await app.listen(3000)\n */\nexport function expressAdapter(\n options?: ExpressAdapterOptions<Express>\n): ExpressAdapter<Express>\nexport function expressAdapter<TApp extends ExpressApplication>(\n options: ExpressAdapterOptions<TApp> & { app: TApp }\n): ExpressAdapter<TApp>\nexport function expressAdapter<TApp extends ExpressApplication>(\n options: ExpressAdapterOptions<TApp> = {}\n): ExpressAdapter<TApp> {\n const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT\n\n if (!Number.isInteger(bodyLimit) || bodyLimit < 0) {\n throw new RangeError(\n 'Invalid bodyLimit: expected a non-negative integer (bytes).'\n )\n }\n\n let app = options.app\n if (!app) {\n const created = express()\n created.disable('x-powered-by')\n created.disable('etag')\n app = created as unknown as TApp\n }\n\n const application = app\n let mounted = false\n let server: Server | undefined\n let closeConnections = () => {}\n /** Aborts the responses being streamed, so that `close()` can complete. */\n const activeStreams = new Set<() => void>()\n\n const send = async (\n req: IncomingMessage,\n res: ServerResponse,\n response: NormalizedResponse,\n controller?: AbortController\n ): Promise<void> => {\n const { body } = response\n\n if (res.headersSent || res.destroyed) {\n if (body instanceof ReadableStream) await body.cancel().catch(() => {})\n return\n }\n\n res.statusCode = response.status\n for (const [name, value] of Object.entries(response.headers)) {\n res.setHeader(name, value)\n }\n\n if (body === null) {\n res.end()\n return\n }\n\n if (typeof body === 'string' || body instanceof Uint8Array) {\n if (!res.hasHeader('content-length')) {\n res.setHeader(\n 'content-length',\n typeof body === 'string' ? Buffer.byteLength(body) : body.byteLength\n )\n }\n res.end(body)\n return\n }\n\n if (req.method === 'HEAD') {\n await body.cancel().catch(() => {})\n res.end()\n return\n }\n\n await pipe(body, res, controller)\n }\n\n const pipe = async (\n body: ReadableStream<Uint8Array>,\n res: ServerResponse,\n controller: AbortController | undefined\n ) => {\n const reader = body.getReader()\n const cancel = () => {\n reader.cancel().catch(() => {})\n }\n const stop = () => {\n controller?.abort()\n cancel()\n }\n\n res.on('close', cancel)\n activeStreams.add(stop)\n // Sends the headers right away (Server-Sent Events).\n res.flushHeaders()\n\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done || res.destroyed) break\n\n const writable = res.write(value)\n // Flushes compression middlewares (`compression` adds `res.flush`).\n ;(res as { flush?: () => void }).flush?.()\n if (!writable) await waitForDrain(res)\n }\n\n if (res.destroyed) cancel()\n else res.end()\n } catch {\n // Ending the response would hide the failure: abort the connection.\n res.destroy()\n } finally {\n res.off('close', cancel)\n activeStreams.delete(stop)\n }\n }\n\n const normalize = (\n req: ExpressRequest,\n signal?: AbortSignal\n ): NormalizedRequest => {\n const url = req.url ?? '/'\n return {\n method: req.method ?? 'GET',\n path: pathnameOf(url),\n url: fullUrl(req),\n params: {},\n query: parseQueryString(searchOf(url)),\n headers: req.headers,\n body: undefined,\n signal,\n ip: req.ip ?? req.socket.remoteAddress,\n raw: req,\n }\n }\n\n const fail =\n (res: ServerResponse, runtime: DotpressRuntime) => (error: unknown) => {\n runtime.logger.error(\n error instanceof Error ? error : { error },\n 'Express adapter failed to send the response'\n )\n if (!res.headersSent) {\n res.statusCode = 500\n res.end()\n } else {\n res.destroy()\n }\n }\n\n const handleRoute = async (\n req: ExpressRequest,\n res: ServerResponse,\n match: RouteMatch,\n runtime: DotpressRuntime\n ) => {\n const controller = new AbortController()\n res.on('close', () => {\n if (!res.writableFinished) controller.abort()\n })\n\n const request = normalize(req, controller.signal)\n\n try {\n const { body, files, rawBody } = await readBody(\n req,\n match.route.definition.bodyLimit ?? bodyLimit\n )\n request.body = body\n request.files = files\n request.rawBody = rawBody\n } catch (error) {\n if (error === CLIENT_GONE) return\n await send(req, res, await runtime.handleError(error, request))\n return\n }\n\n request.params = match.params\n const response = await match.route.execute(request)\n await send(req, res, response, controller)\n }\n\n return {\n name: 'express',\n\n mount: async (\n routes: readonly CompiledRoute[],\n runtime: DotpressRuntime\n ) => {\n if (mounted) {\n throw new Error('The Express adapter is already mounted.')\n }\n mounted = true\n\n const matcher = createRouteMatcher(routes)\n\n await options.beforeRoutes?.(application)\n\n application.use(\n (req: ExpressRequest, res: ServerResponse, next: NextFunction) => {\n const match = matcher.match(\n req.method ?? 'GET',\n pathnameOf(req.url ?? '/')\n )\n if (!match) {\n next()\n return\n }\n handleRoute(req, res, match, runtime).catch(fail(res, runtime))\n }\n )\n\n await options.afterRoutes?.(application)\n\n application.use((req: ExpressRequest, res: ServerResponse) => {\n runtime\n .handleNotFound(normalize(req))\n .then((response) => send(req, res, response))\n .catch(fail(res, runtime))\n })\n\n application.use(\n (\n error: unknown,\n req: ExpressRequest,\n res: ServerResponse,\n next: NextFunction\n ) => {\n if (res.headersSent) {\n next(error)\n return\n }\n runtime\n .handleError(fromNativeError(error), normalize(req))\n .then((response) => send(req, res, response))\n .catch(fail(res, runtime))\n }\n )\n },\n\n listen: async (port: number, host?: string) => {\n if (server) {\n throw new Error('The Express adapter is already listening.')\n }\n\n const created = createServer(application as RequestListener)\n await new Promise<void>((resolve, reject) => {\n created.once('error', reject)\n created.listen(port, host, () => {\n created.off('error', reject)\n resolve()\n })\n })\n server = created\n closeConnections = trackConnections(created)\n },\n\n close: async () => {\n const current = server\n if (!current) return\n server = undefined\n\n await new Promise<void>((resolve, reject) => {\n current.close((error) => (error ? reject(error) : resolve()))\n closeConnections()\n // Streams never end by themselves: abort them.\n for (const stop of activeStreams) stop()\n })\n },\n\n native: () => application,\n server: () => server,\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/options.ts"],"sourcesContent":["import {\n createServer,\n type IncomingMessage,\n type RequestListener,\n type Server,\n type ServerResponse,\n} from 'node:http'\nimport type { AddressInfo, Socket } from 'node:net'\nimport {\n HttpError,\n isHttpError,\n type HttpAdapter,\n type NormalizedRequest,\n type NormalizedResponse,\n type UploadedFiles,\n type ListenAddress,\n} from '@dotpress/core'\nimport {\n DEFAULT_BODY_LIMIT,\n createRouteMatcher,\n malformedBodyError,\n parseQueryString,\n parseRequestBody,\n payloadTooLargeError,\n unsupportedMediaTypeError,\n type CompiledRoute,\n type DotpressRuntime,\n type RouteMatch,\n} from '@dotpress/core/adapter'\nimport express, { type Express } from 'express'\nimport { assertKnownOptions } from './options.js'\n\n/** Address of a listening Node.js server. */\nconst addressOf = (server: Server): ListenAddress => {\n const address = server.address() as AddressInfo\n return { port: address.port, host: address.address }\n}\n\n/**\n * Lets `close()` complete as soon as the pending requests are served, and\n * returns the function to call on close. Before Node.js 26, `server.close()`\n * waits for the connections that have sent nothing yet (a browser\n * preconnect…) until `headersTimeout`; and, whatever the version, the\n * connections of the pending requests stay open once served, until\n * `keepAliveTimeout`.\n */\nconst trackConnections = (server: Server) => {\n const sockets = new Set<Socket>()\n let closing = false\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.once('close', () => sockets.delete(socket))\n })\n server.on(\n 'request',\n (_request: IncomingMessage, response: ServerResponse) => {\n response.once('finish', () => {\n if (closing) server.closeIdleConnections()\n })\n }\n )\n return () => {\n closing = true\n for (const socket of sockets) {\n if (socket.bytesRead === 0) socket.destroy()\n }\n }\n}\n\n/**\n * Minimal shape of an Express application, compatible with Express 4 and 5.\n */\nexport interface ExpressApplication {\n (req: IncomingMessage, res: ServerResponse): void\n use(...handlers: unknown[]): unknown\n}\n\nexport interface ExpressAdapterOptions<\n TApp extends ExpressApplication = Express,\n> {\n /**\n * Express application (Express 4 or 5) to mount the routes on. Defaults to\n * a new application, created with `express()`.\n */\n app?: TApp\n /** Registers native middlewares before the Dotpress routes (helmet…). */\n beforeRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Registers native middlewares and routes after the Dotpress routes,\n * before the not-found handler.\n */\n afterRoutes?: (app: TApp) => void | Promise<void>\n /**\n * Maximum request body size, in bytes. Defaults to 1 MiB. Routes can\n * override it with their own `bodyLimit`.\n */\n bodyLimit?: number\n}\n\nexport interface ExpressAdapter<\n TApp extends ExpressApplication = Express,\n> extends HttpAdapter<TApp> {\n /** Starts an HTTP server, and returns its address. */\n listen(port: number, host?: string): Promise<ListenAddress>\n /**\n * Stops the HTTP server started by `listen()`: waits for pending\n * requests. Closes the responses being streamed (their `req.signal` is\n * aborted), also when the app serves the Express application with its own\n * server.\n */\n close(): Promise<void>\n /** HTTP server started by `listen()`, if any. */\n server(): Server | undefined\n}\n\n/** Express request fields used by the adapter (Express 4 and 5). */\ninterface ExpressRequest extends IncomingMessage {\n originalUrl?: string\n protocol?: string\n /** Client address, honoring the `trust proxy` setting. */\n ip?: string\n body?: unknown\n /** Set by body-parser and similar middlewares once the body is read. */\n _body?: boolean\n}\n\ntype NextFunction = (error?: unknown) => void\n\n/** Rejection reason when the client goes away while the body is read. */\nconst CLIENT_GONE = Symbol('client gone')\n\nconst pathnameOf = (url: string) => {\n const index = url.indexOf('?')\n return index === -1 ? url : url.slice(0, index)\n}\n\nconst searchOf = (url: string) => {\n const index = url.indexOf('?')\n return index === -1 ? '' : url.slice(index)\n}\n\nconst fullUrl = (req: ExpressRequest): string | undefined => {\n const host = req.headers.host\n if (!host) return undefined\n const protocol = req.protocol ?? 'http'\n return `${protocol}://${host}${req.originalUrl ?? req.url ?? '/'}`\n}\n\nconst hasBody = (req: IncomingMessage) =>\n req.headers['transfer-encoding'] !== undefined ||\n (req.headers['content-length'] !== undefined &&\n req.headers['content-length'] !== '0')\n\n/** Discards the rest of the request body. */\nconst discard = (req: IncomingMessage) => {\n req.resume()\n}\n\ninterface RequestBody {\n readonly body: unknown\n readonly files: UploadedFiles | undefined\n /** Undefined when a native middleware read the body. */\n readonly rawBody: Uint8Array | undefined\n}\n\nconst NO_BODY: RequestBody = {\n body: undefined,\n files: undefined,\n rawBody: undefined,\n}\n\nconst readBody = (req: ExpressRequest, limit: number): Promise<RequestBody> => {\n // The body was already read by a native middleware (express.json(),\n // multer…): use its result.\n if (req._body === true || req.readableEnded) {\n return Promise.resolve({\n body: req.body,\n files: undefined,\n rawBody: undefined,\n })\n }\n\n if (!hasBody(req)) {\n return Promise.resolve(NO_BODY)\n }\n\n const encoding = (req.headers['content-encoding'] ?? 'identity')\n .trim()\n .toLowerCase()\n\n if (encoding !== 'identity') {\n discard(req)\n return Promise.reject(\n unsupportedMediaTypeError(`Unsupported content encoding \"${encoding}\"`)\n )\n }\n\n const declaredLength = Number(req.headers['content-length'])\n if (Number.isFinite(declaredLength) && declaredLength > limit) {\n discard(req)\n return Promise.reject(payloadTooLargeError())\n }\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onGone)\n req.off('close', onClose)\n }\n\n function onData(chunk: Buffer) {\n size += chunk.byteLength\n if (size > limit) {\n cleanup()\n discard(req)\n reject(payloadTooLargeError())\n return\n }\n chunks.push(chunk)\n }\n\n function onEnd() {\n cleanup()\n const buffer = Buffer.concat(chunks, size)\n const bytes = new Uint8Array(\n buffer.buffer,\n buffer.byteOffset,\n buffer.byteLength\n )\n parseRequestBody(bytes, req.headers['content-type']).then(\n ({ body, files }) => resolve({ body, files, rawBody: bytes }),\n reject\n )\n }\n\n function onGone() {\n cleanup()\n reject(CLIENT_GONE)\n }\n\n function onClose() {\n if (!req.complete) onGone()\n }\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onGone)\n req.on('close', onClose)\n })\n}\n\nconst ERROR_CODES: Record<number, string> = {\n 400: 'BAD_REQUEST',\n 401: 'UNAUTHORIZED',\n 403: 'FORBIDDEN',\n 404: 'NOT_FOUND',\n 405: 'METHOD_NOT_ALLOWED',\n 408: 'REQUEST_TIMEOUT',\n 409: 'CONFLICT',\n 413: 'PAYLOAD_TOO_LARGE',\n 415: 'UNSUPPORTED_MEDIA_TYPE',\n 422: 'UNPROCESSABLE_ENTITY',\n 429: 'TOO_MANY_REQUESTS',\n}\n\n/**\n * Converts errors raised by native middlewares (`next(error)`) into Dotpress\n * errors. Client errors (4xx) keep their status; anything else is an\n * unexpected error.\n */\nconst fromNativeError = (error: unknown): unknown => {\n if (isHttpError(error) || typeof error !== 'object' || error === null) {\n return error\n }\n\n const { type, status, statusCode, expose, message } = error as {\n type?: unknown\n status?: unknown\n statusCode?: unknown\n expose?: unknown\n message?: unknown\n }\n\n if (type === 'entity.parse.failed') {\n return malformedBodyError()\n }\n\n const code = typeof status === 'number' ? status : statusCode\n if (typeof code !== 'number' || !Number.isInteger(code)) return error\n if (code < 400 || code > 499) return error\n\n return new HttpError(\n code,\n ERROR_CODES[code] ?? 'CLIENT_ERROR',\n expose === true && typeof message === 'string' && message\n ? message\n : 'Bad Request',\n { cause: error }\n )\n}\n\nconst waitForDrain = (res: ServerResponse) =>\n new Promise<void>((resolve) => {\n const done = () => {\n res.off('drain', done)\n res.off('close', done)\n resolve()\n }\n res.on('drain', done)\n res.on('close', done)\n })\n\n/**\n * Creates the Express adapter (Express 4 or 5).\n *\n * @example\n * const app = await createApp({\n * adapter: expressAdapter({\n * beforeRoutes: (app) => app.use(helmet()),\n * }),\n * })\n * await app.listen(3000)\n */\nexport function expressAdapter(\n options?: ExpressAdapterOptions<Express>\n): ExpressAdapter<Express>\nexport function expressAdapter<TApp extends ExpressApplication>(\n options: ExpressAdapterOptions<TApp> & { app: TApp }\n): ExpressAdapter<TApp>\nexport function expressAdapter<TApp extends ExpressApplication>(\n options: ExpressAdapterOptions<TApp> = {}\n): ExpressAdapter<TApp> {\n assertKnownOptions('expressAdapter()', options, [\n 'app',\n 'beforeRoutes',\n 'afterRoutes',\n 'bodyLimit',\n ] satisfies (keyof ExpressAdapterOptions<TApp>)[])\n const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT\n\n if (!Number.isInteger(bodyLimit) || bodyLimit < 0) {\n throw new RangeError(\n 'expressAdapter(): bodyLimit must be a non-negative integer (bytes).'\n )\n }\n\n let app = options.app\n if (!app) {\n const created = express()\n created.disable('x-powered-by')\n created.disable('etag')\n app = created as unknown as TApp\n }\n\n const application = app\n let mounted = false\n let server: Server | undefined\n let closeConnections = () => {}\n /** Aborts the responses being streamed, so that `close()` can complete. */\n const activeStreams = new Set<() => void>()\n\n const send = async (\n req: IncomingMessage,\n res: ServerResponse,\n response: NormalizedResponse,\n controller?: AbortController\n ): Promise<void> => {\n const { body } = response\n\n if (res.headersSent || res.destroyed) {\n if (body instanceof ReadableStream) await body.cancel().catch(() => {})\n return\n }\n\n res.statusCode = response.status\n for (const [name, value] of Object.entries(response.headers)) {\n res.setHeader(name, value)\n }\n\n if (body === null) {\n res.end()\n return\n }\n\n if (typeof body === 'string' || body instanceof Uint8Array) {\n if (!res.hasHeader('content-length')) {\n res.setHeader(\n 'content-length',\n typeof body === 'string' ? Buffer.byteLength(body) : body.byteLength\n )\n }\n res.end(body)\n return\n }\n\n if (req.method === 'HEAD') {\n await body.cancel().catch(() => {})\n res.end()\n return\n }\n\n await pipe(body, res, controller)\n }\n\n const pipe = async (\n body: ReadableStream<Uint8Array>,\n res: ServerResponse,\n controller: AbortController | undefined\n ) => {\n const reader = body.getReader()\n const cancel = () => {\n reader.cancel().catch(() => {})\n }\n const stop = () => {\n controller?.abort()\n cancel()\n }\n\n res.on('close', cancel)\n activeStreams.add(stop)\n // Sends the headers right away (Server-Sent Events).\n res.flushHeaders()\n\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done || res.destroyed) break\n\n const writable = res.write(value)\n // Flushes compression middlewares (`compression` adds `res.flush`).\n ;(res as { flush?: () => void }).flush?.()\n if (!writable) await waitForDrain(res)\n }\n\n if (res.destroyed) cancel()\n else res.end()\n } catch {\n // Ending the response would hide the failure: abort the connection.\n res.destroy()\n } finally {\n res.off('close', cancel)\n activeStreams.delete(stop)\n }\n }\n\n const normalize = (\n req: ExpressRequest,\n signal?: AbortSignal\n ): NormalizedRequest => {\n const url = req.url ?? '/'\n return {\n method: req.method ?? 'GET',\n path: pathnameOf(url),\n url: fullUrl(req),\n params: {},\n query: parseQueryString(searchOf(url)),\n headers: req.headers,\n body: undefined,\n signal,\n ip: req.ip ?? req.socket.remoteAddress,\n raw: req,\n }\n }\n\n const fail =\n (res: ServerResponse, runtime: DotpressRuntime) => (error: unknown) => {\n runtime.logger.error(\n error instanceof Error ? error : { error },\n 'Express adapter failed to send the response'\n )\n if (!res.headersSent) {\n res.statusCode = 500\n res.end()\n } else {\n res.destroy()\n }\n }\n\n const handleRoute = async (\n req: ExpressRequest,\n res: ServerResponse,\n match: RouteMatch,\n runtime: DotpressRuntime\n ) => {\n const controller = new AbortController()\n res.on('close', () => {\n if (!res.writableFinished) controller.abort()\n })\n\n const request = normalize(req, controller.signal)\n\n try {\n const { body, files, rawBody } = await readBody(\n req,\n match.route.definition.bodyLimit ?? bodyLimit\n )\n request.body = body\n request.files = files\n request.rawBody = rawBody\n } catch (error) {\n if (error === CLIENT_GONE) return\n await send(req, res, await runtime.handleError(error, request))\n return\n }\n\n request.params = match.params\n const response = await match.route.execute(request)\n await send(req, res, response, controller)\n }\n\n return {\n name: 'express',\n\n mount: async (\n routes: readonly CompiledRoute[],\n runtime: DotpressRuntime\n ) => {\n if (mounted) {\n throw new Error('The Express adapter is already mounted.')\n }\n mounted = true\n\n const matcher = createRouteMatcher(routes)\n\n await options.beforeRoutes?.(application)\n\n application.use(\n (req: ExpressRequest, res: ServerResponse, next: NextFunction) => {\n const match = matcher.match(\n req.method ?? 'GET',\n pathnameOf(req.url ?? '/')\n )\n if (!match) {\n next()\n return\n }\n handleRoute(req, res, match, runtime).catch(fail(res, runtime))\n }\n )\n\n await options.afterRoutes?.(application)\n\n application.use((req: ExpressRequest, res: ServerResponse) => {\n runtime\n .handleNotFound(normalize(req))\n .then((response) => send(req, res, response))\n .catch(fail(res, runtime))\n })\n\n application.use(\n (\n error: unknown,\n req: ExpressRequest,\n res: ServerResponse,\n next: NextFunction\n ) => {\n if (res.headersSent) {\n next(error)\n return\n }\n runtime\n .handleError(fromNativeError(error), normalize(req))\n .then((response) => send(req, res, response))\n .catch(fail(res, runtime))\n }\n )\n },\n\n listen: async (port: number, host?: string) => {\n if (server) {\n throw new Error('The Express adapter is already listening.')\n }\n\n const created = createServer(application as RequestListener)\n await new Promise<void>((resolve, reject) => {\n created.once('error', reject)\n created.listen(port, host, () => {\n created.off('error', reject)\n resolve()\n })\n })\n server = created\n closeConnections = trackConnections(created)\n return addressOf(created)\n },\n\n close: async () => {\n const current = server\n server = undefined\n // Streams never end by themselves: abort them, also when the app\n // serves the Express application with its own server (HTTPS…).\n const stopStreams = () => {\n for (const stop of activeStreams) stop()\n }\n if (!current) {\n stopStreams()\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n current.close((error) => (error ? reject(error) : resolve()))\n closeConnections()\n stopStreams()\n })\n },\n\n native: () => application,\n server: () => server,\n }\n}\n","/**\n * Throws a `TypeError` for a key of `options` that is not in `known`: a\n * misspelled option would otherwise be ignored without a word.\n */\nexport const assertKnownOptions = (\n label: string,\n options: object,\n known: readonly string[],\n prefix = ''\n): void => {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: options must be an object.`)\n }\n for (const key of Object.keys(options)) {\n if (!known.includes(key)) {\n throw new TypeError(`${label}: unknown option \"${prefix}${key}\".`)\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotpress/adapter-express",
3
- "version": "2.0.0-alpha.3",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "Express 4 and 5 adapter for Dotpress",
5
5
  "author": "Renaud Guyon",
6
6
  "license": "MIT",
@@ -30,12 +30,18 @@
30
30
  "node": ">=22.12"
31
31
  },
32
32
  "peerDependencies": {
33
- "@dotpress/core": "^2.0.0-alpha.3",
33
+ "@dotpress/core": "^2.0.0-alpha.5",
34
+ "@types/express": "^4.17.0 || ^5.0.0",
34
35
  "express": "^4.21.0 || ^5.0.0"
35
36
  },
37
+ "peerDependenciesMeta": {
38
+ "@types/express": {
39
+ "optional": true
40
+ }
41
+ },
36
42
  "devDependencies": {
37
- "@dotpress/core": "2.0.0-alpha.3",
38
- "@dotpress/testing": "2.0.0-alpha.3",
43
+ "@dotpress/core": "2.0.0-alpha.5",
44
+ "@dotpress/testing": "2.0.0-alpha.5",
39
45
  "@types/express": "^5.0.6",
40
46
  "express": "^5.3.0",
41
47
  "express-4": "npm:express@^4.22.3",