better-call 1.3.2 → 1.3.4

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.
@@ -3,6 +3,50 @@ let set_cookie_parser = require("set-cookie-parser");
3
3
  set_cookie_parser = require_runtime.__toESM(set_cookie_parser);
4
4
 
5
5
  //#region src/adapters/node/request.ts
6
+ const getFirstHeaderValue = (header) => {
7
+ if (Array.isArray(header)) return header[0];
8
+ return header;
9
+ };
10
+ const hasFormUrlEncodedContentType = (headers) => {
11
+ const contentType = getFirstHeaderValue(headers["content-type"]);
12
+ if (!contentType) return false;
13
+ return contentType.toLowerCase().startsWith("application/x-www-form-urlencoded");
14
+ };
15
+ const isPlainObject = (value) => {
16
+ if (typeof value !== "object" || value === null) return false;
17
+ const prototype = Object.getPrototypeOf(value);
18
+ return prototype === Object.prototype || prototype === null;
19
+ };
20
+ const appendFormValue = (params, key, value) => {
21
+ if (value === void 0) return;
22
+ if (Array.isArray(value)) {
23
+ for (const item of value) appendFormValue(params, key, item);
24
+ return;
25
+ }
26
+ if (value === null) {
27
+ params.append(key, "");
28
+ return;
29
+ }
30
+ if (isPlainObject(value)) {
31
+ params.append(key, JSON.stringify(value));
32
+ return;
33
+ }
34
+ params.append(key, `${value}`);
35
+ };
36
+ const toFormUrlEncodedBody = (body) => {
37
+ const params = new URLSearchParams();
38
+ for (const [key, value] of Object.entries(body)) appendFormValue(params, key, value);
39
+ return params.toString();
40
+ };
41
+ const canReadRawBody = (request) => {
42
+ return !request.destroyed && request.readableEnded !== true && request.readable;
43
+ };
44
+ const serializeParsedBody = (parsedBody, isFormUrlEncoded) => {
45
+ if (typeof parsedBody === "string") return parsedBody;
46
+ if (parsedBody instanceof URLSearchParams) return parsedBody.toString();
47
+ if (isFormUrlEncoded && isPlainObject(parsedBody)) return toFormUrlEncodedBody(parsedBody);
48
+ return JSON.stringify(parsedBody);
49
+ };
6
50
  function get_raw_body(req, body_size_limit) {
7
51
  const h = req.headers;
8
52
  if (!h["content-type"]) return null;
@@ -60,15 +104,20 @@ function constructRelativeUrl(req) {
60
104
  }
61
105
  function getRequest({ request, base, bodySizeLimit }) {
62
106
  const maybeConsumedReq = request;
107
+ const isFormUrlEncoded = hasFormUrlEncodedContentType(request.headers);
63
108
  let body = void 0;
64
109
  const method = request.method;
65
- if (method !== "GET" && method !== "HEAD") if (maybeConsumedReq.body !== void 0) {
66
- const bodyContent = typeof maybeConsumedReq.body === "string" ? maybeConsumedReq.body : JSON.stringify(maybeConsumedReq.body);
67
- body = new ReadableStream({ start(controller) {
68
- controller.enqueue(new TextEncoder().encode(bodyContent));
69
- controller.close();
70
- } });
71
- } else body = get_raw_body(request, bodySizeLimit);
110
+ if (method !== "GET" && method !== "HEAD") {
111
+ if (canReadRawBody(request)) body = get_raw_body(request, bodySizeLimit);
112
+ else if (maybeConsumedReq.body !== void 0) {
113
+ const parsedBody = maybeConsumedReq.body;
114
+ const bodyContent = serializeParsedBody(parsedBody, isFormUrlEncoded);
115
+ body = new ReadableStream({ start(controller) {
116
+ controller.enqueue(new TextEncoder().encode(bodyContent));
117
+ controller.close();
118
+ } });
119
+ }
120
+ }
72
121
  return new Request(base + constructRelativeUrl(request), {
73
122
  duplex: "half",
74
123
  method: request.method,
@@ -1 +1 @@
1
- {"version":3,"file":"request.cjs","names":[],"sources":["../../../src/adapters/node/request.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport * as set_cookie_parser from \"set-cookie-parser\";\n\nfunction get_raw_body(req: IncomingMessage, body_size_limit?: number) {\n\tconst h = req.headers;\n\n\tif (!h[\"content-type\"]) return null;\n\n\tconst content_length = Number(h[\"content-length\"]);\n\n\t// check if no request body\n\tif (\n\t\t(req.httpVersionMajor === 1 &&\n\t\t\tisNaN(content_length) &&\n\t\t\th[\"transfer-encoding\"] == null) ||\n\t\tcontent_length === 0\n\t) {\n\t\treturn null;\n\t}\n\n\tlet length = content_length;\n\n\tif (body_size_limit) {\n\t\tif (!length) {\n\t\t\tlength = body_size_limit;\n\t\t} else if (length > body_size_limit) {\n\t\t\tthrow Error(\n\t\t\t\t`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (req.destroyed) {\n\t\tconst readable = new ReadableStream();\n\t\treadable.cancel();\n\t\treturn readable;\n\t}\n\n\tlet size = 0;\n\tlet cancelled = false;\n\n\treturn new ReadableStream({\n\t\tstart(controller) {\n\t\t\treq.on(\"error\", (error) => {\n\t\t\t\tcancelled = true;\n\t\t\t\tcontroller.error(error);\n\t\t\t});\n\n\t\t\treq.on(\"end\", () => {\n\t\t\t\tif (cancelled) return;\n\t\t\t\tcontroller.close();\n\t\t\t});\n\n\t\t\treq.on(\"data\", (chunk) => {\n\t\t\t\tif (cancelled) return;\n\n\t\t\t\tsize += chunk.length;\n\n\t\t\t\tif (size > length) {\n\t\t\t\t\tcancelled = true;\n\n\t\t\t\t\tcontroller.error(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`request body size exceeded ${\n\t\t\t\t\t\t\t\tcontent_length ? \"'content-length'\" : \"BODY_SIZE_LIMIT\"\n\t\t\t\t\t\t\t} of ${length}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontroller.enqueue(chunk);\n\n\t\t\t\tif (controller.desiredSize === null || controller.desiredSize <= 0) {\n\t\t\t\t\treq.pause();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tpull() {\n\t\t\treq.resume();\n\t\t},\n\n\t\tcancel(reason) {\n\t\t\tcancelled = true;\n\t\t\treq.destroy(reason);\n\t\t},\n\t});\n}\n\nfunction constructRelativeUrl(\n\treq: IncomingMessage & { baseUrl?: string; originalUrl?: string },\n) {\n\tconst baseUrl = req.baseUrl;\n\tconst originalUrl = req.originalUrl;\n\n\tif (!baseUrl || !originalUrl) {\n\t\t// In express.js sub-routers `req.url` is relative to the mount\n\t\t// path (e.g., '/auth/xxx'), and `req.baseUrl` will hold the mount\n\t\t// path (e.g., '/api'). Build the full path as baseUrl + url when\n\t\t// available to preserve the full route. For application level routes\n\t\t// baseUrl will be an empty string\n\t\treturn baseUrl ? baseUrl + req.url : req.url;\n\t}\n\n\tif (baseUrl + req.url === originalUrl) {\n\t\treturn baseUrl + req.url;\n\t}\n\n\t// For certain subroutes or when mounting wildcard middlewares in express\n\t// it is possible `baseUrl + req.url` will result in a url constructed\n\t// which has a trailing forward slash the original url did not have.\n\t// Checking the `req.originalUrl` path ending can prevent this issue.\n\n\tconst originalPathEnding = originalUrl.split(\"?\")[0]!.at(-1);\n\treturn originalPathEnding === \"/\" ? baseUrl + req.url : baseUrl;\n}\n\nexport function getRequest({\n\trequest,\n\tbase,\n\tbodySizeLimit,\n}: {\n\tbase: string;\n\tbodySizeLimit?: number;\n\trequest: IncomingMessage;\n}) {\n\t// Check if body has already been parsed by Express middleware\n\tconst maybeConsumedReq = request as any;\n\tlet body = undefined;\n\n\tconst method = request.method;\n\t// Request with GET/HEAD method cannot have body.\n\tif (method !== \"GET\" && method !== \"HEAD\") {\n\t\t// If body was already parsed by Express body-parser middleware\n\t\tif (maybeConsumedReq.body !== undefined) {\n\t\t\t// Convert parsed body back to a ReadableStream\n\t\t\tconst bodyContent =\n\t\t\t\ttypeof maybeConsumedReq.body === \"string\"\n\t\t\t\t\t? maybeConsumedReq.body\n\t\t\t\t\t: JSON.stringify(maybeConsumedReq.body);\n\n\t\t\tbody = new ReadableStream({\n\t\t\t\tstart(controller) {\n\t\t\t\t\tcontroller.enqueue(new TextEncoder().encode(bodyContent));\n\t\t\t\t\tcontroller.close();\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\t// Otherwise, get the raw body stream\n\t\t\tbody = get_raw_body(request, bodySizeLimit);\n\t\t}\n\t}\n\n\treturn new Request(base + constructRelativeUrl(request), {\n\t\t// @ts-expect-error\n\t\tduplex: \"half\",\n\t\tmethod: request.method,\n\t\tbody,\n\t\theaders: request.headers as Record<string, string>,\n\t});\n}\n\nexport async function setResponse(res: ServerResponse, response: Response) {\n\tfor (const [key, value] of response.headers as any) {\n\t\ttry {\n\t\t\tres.setHeader(\n\t\t\t\tkey,\n\t\t\t\tkey === \"set-cookie\"\n\t\t\t\t\t? set_cookie_parser.splitCookiesString(\n\t\t\t\t\t\t\tresponse.headers.get(key) as string,\n\t\t\t\t\t\t)\n\t\t\t\t\t: value,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tres.getHeaderNames().forEach((name) => res.removeHeader(name));\n\t\t\tres.writeHead(500).end(String(error));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tres.statusCode = response.status;\n\tres.writeHead(response.status);\n\n\tif (!response.body) {\n\t\tres.end();\n\t\treturn;\n\t}\n\n\tif (response.body.locked) {\n\t\tres.end(\n\t\t\t\"Fatal error: Response body is locked. \" +\n\t\t\t\t\"This can happen when the response was already read (for example through 'response.json()' or 'response.text()').\",\n\t\t);\n\t\treturn;\n\t}\n\n\tconst reader = response.body.getReader();\n\n\tif (res.destroyed) {\n\t\treader.cancel();\n\t\treturn;\n\t}\n\n\tconst cancel = (error?: Error) => {\n\t\tres.off(\"close\", cancel);\n\t\tres.off(\"error\", cancel);\n\n\t\t// If the reader has already been interrupted with an error earlier,\n\t\t// then it will appear here, it is useless, but it needs to be catch.\n\t\treader.cancel(error).catch(() => {});\n\t\tif (error) res.destroy(error);\n\t};\n\n\tres.on(\"close\", cancel);\n\tres.on(\"error\", cancel);\n\n\tnext();\n\tasync function next() {\n\t\ttry {\n\t\t\tfor (;;) {\n\t\t\t\tconst { done, value } = await reader.read();\n\n\t\t\t\tif (done) break;\n\n\t\t\t\tconst writeResult = res.write(value);\n\t\t\t\tif (!writeResult) {\n\t\t\t\t\t// In AWS Lambda/serverless environments, drain events may not work properly\n\t\t\t\t\t// Check if we're in a Lambda-like environment and handle differently\n\t\t\t\t\tif (\n\t\t\t\t\t\tprocess.env.AWS_LAMBDA_FUNCTION_NAME ||\n\t\t\t\t\t\tprocess.env.LAMBDA_TASK_ROOT\n\t\t\t\t\t) {\n\t\t\t\t\t\t// In Lambda, continue without waiting for drain\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Standard Node.js behavior\n\t\t\t\t\t\tres.once(\"drain\", next);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tres.end();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tcancel(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"request.cjs","names":[],"sources":["../../../src/adapters/node/request.ts"],"sourcesContent":["import type {\n\tIncomingHttpHeaders,\n\tIncomingMessage,\n\tServerResponse,\n} from \"node:http\";\nimport * as set_cookie_parser from \"set-cookie-parser\";\n\ntype NodeRequestWithBody = IncomingMessage & {\n\tbody?: unknown;\n};\n\nconst getFirstHeaderValue = (\n\theader: IncomingHttpHeaders[string],\n): string | undefined => {\n\tif (Array.isArray(header)) {\n\t\treturn header[0];\n\t}\n\treturn header;\n};\n\nconst hasFormUrlEncodedContentType = (\n\theaders: IncomingHttpHeaders,\n): boolean => {\n\tconst contentType = getFirstHeaderValue(headers[\"content-type\"]);\n\tif (!contentType) {\n\t\treturn false;\n\t}\n\treturn contentType\n\t\t.toLowerCase()\n\t\t.startsWith(\"application/x-www-form-urlencoded\");\n};\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n};\n\nconst appendFormValue = (\n\tparams: URLSearchParams,\n\tkey: string,\n\tvalue: unknown,\n) => {\n\tif (value === undefined) {\n\t\treturn;\n\t}\n\tif (Array.isArray(value)) {\n\t\tfor (const item of value) {\n\t\t\tappendFormValue(params, key, item);\n\t\t}\n\t\treturn;\n\t}\n\tif (value === null) {\n\t\tparams.append(key, \"\");\n\t\treturn;\n\t}\n\tif (isPlainObject(value)) {\n\t\tparams.append(key, JSON.stringify(value));\n\t\treturn;\n\t}\n\tparams.append(key, `${value}`);\n};\n\nconst toFormUrlEncodedBody = (\n\tbody: Readonly<Record<string, unknown>>,\n): string => {\n\tconst params = new URLSearchParams();\n\tfor (const [key, value] of Object.entries(body)) {\n\t\tappendFormValue(params, key, value);\n\t}\n\treturn params.toString();\n};\n\nconst canReadRawBody = (request: IncomingMessage): boolean => {\n\treturn (\n\t\t!request.destroyed && request.readableEnded !== true && request.readable\n\t);\n};\n\nconst serializeParsedBody = (\n\tparsedBody: unknown,\n\tisFormUrlEncoded: boolean,\n): string => {\n\tif (typeof parsedBody === \"string\") {\n\t\treturn parsedBody;\n\t}\n\tif (parsedBody instanceof URLSearchParams) {\n\t\treturn parsedBody.toString();\n\t}\n\tif (isFormUrlEncoded && isPlainObject(parsedBody)) {\n\t\treturn toFormUrlEncodedBody(parsedBody);\n\t}\n\treturn JSON.stringify(parsedBody);\n};\n\nfunction get_raw_body(req: IncomingMessage, body_size_limit?: number) {\n\tconst h = req.headers;\n\n\tif (!h[\"content-type\"]) return null;\n\n\tconst content_length = Number(h[\"content-length\"]);\n\n\t// check if no request body\n\tif (\n\t\t(req.httpVersionMajor === 1 &&\n\t\t\tisNaN(content_length) &&\n\t\t\th[\"transfer-encoding\"] == null) ||\n\t\tcontent_length === 0\n\t) {\n\t\treturn null;\n\t}\n\n\tlet length = content_length;\n\n\tif (body_size_limit) {\n\t\tif (!length) {\n\t\t\tlength = body_size_limit;\n\t\t} else if (length > body_size_limit) {\n\t\t\tthrow Error(\n\t\t\t\t`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (req.destroyed) {\n\t\tconst readable = new ReadableStream();\n\t\treadable.cancel();\n\t\treturn readable;\n\t}\n\n\tlet size = 0;\n\tlet cancelled = false;\n\n\treturn new ReadableStream({\n\t\tstart(controller) {\n\t\t\treq.on(\"error\", (error) => {\n\t\t\t\tcancelled = true;\n\t\t\t\tcontroller.error(error);\n\t\t\t});\n\n\t\t\treq.on(\"end\", () => {\n\t\t\t\tif (cancelled) return;\n\t\t\t\tcontroller.close();\n\t\t\t});\n\n\t\t\treq.on(\"data\", (chunk) => {\n\t\t\t\tif (cancelled) return;\n\n\t\t\t\tsize += chunk.length;\n\n\t\t\t\tif (size > length) {\n\t\t\t\t\tcancelled = true;\n\n\t\t\t\t\tcontroller.error(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`request body size exceeded ${\n\t\t\t\t\t\t\t\tcontent_length ? \"'content-length'\" : \"BODY_SIZE_LIMIT\"\n\t\t\t\t\t\t\t} of ${length}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontroller.enqueue(chunk);\n\n\t\t\t\tif (controller.desiredSize === null || controller.desiredSize <= 0) {\n\t\t\t\t\treq.pause();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tpull() {\n\t\t\treq.resume();\n\t\t},\n\n\t\tcancel(reason) {\n\t\t\tcancelled = true;\n\t\t\treq.destroy(reason);\n\t\t},\n\t});\n}\n\nfunction constructRelativeUrl(\n\treq: IncomingMessage & { baseUrl?: string; originalUrl?: string },\n) {\n\tconst baseUrl = req.baseUrl;\n\tconst originalUrl = req.originalUrl;\n\n\tif (!baseUrl || !originalUrl) {\n\t\t// In express.js sub-routers `req.url` is relative to the mount\n\t\t// path (e.g., '/auth/xxx'), and `req.baseUrl` will hold the mount\n\t\t// path (e.g., '/api'). Build the full path as baseUrl + url when\n\t\t// available to preserve the full route. For application level routes\n\t\t// baseUrl will be an empty string\n\t\treturn baseUrl ? baseUrl + req.url : req.url;\n\t}\n\n\tif (baseUrl + req.url === originalUrl) {\n\t\treturn baseUrl + req.url;\n\t}\n\n\t// For certain subroutes or when mounting wildcard middlewares in express\n\t// it is possible `baseUrl + req.url` will result in a url constructed\n\t// which has a trailing forward slash the original url did not have.\n\t// Checking the `req.originalUrl` path ending can prevent this issue.\n\n\tconst originalPathEnding = originalUrl.split(\"?\")[0]!.at(-1);\n\treturn originalPathEnding === \"/\" ? baseUrl + req.url : baseUrl;\n}\n\nexport function getRequest({\n\trequest,\n\tbase,\n\tbodySizeLimit,\n}: {\n\tbase: string;\n\tbodySizeLimit?: number;\n\trequest: IncomingMessage;\n}) {\n\t// Check if body has already been parsed by Express middleware\n\tconst maybeConsumedReq = request as NodeRequestWithBody;\n\tconst isFormUrlEncoded = hasFormUrlEncodedContentType(request.headers);\n\tlet body = undefined;\n\n\tconst method = request.method;\n\t// Request with GET/HEAD method cannot have body.\n\tif (method !== \"GET\" && method !== \"HEAD\") {\n\t\t// Raw-first strategy: prefer consuming the original request stream whenever it is still readable.\n\t\tif (canReadRawBody(request)) {\n\t\t\tbody = get_raw_body(request, bodySizeLimit);\n\t\t} else if (maybeConsumedReq.body !== undefined) {\n\t\t\tconst parsedBody = maybeConsumedReq.body;\n\n\t\t\tconst bodyContent = serializeParsedBody(parsedBody, isFormUrlEncoded);\n\t\t\tbody = new ReadableStream({\n\t\t\t\tstart(controller) {\n\t\t\t\t\tcontroller.enqueue(new TextEncoder().encode(bodyContent));\n\t\t\t\t\tcontroller.close();\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\treturn new Request(base + constructRelativeUrl(request), {\n\t\t// @ts-expect-error\n\t\tduplex: \"half\",\n\t\tmethod: request.method,\n\t\tbody,\n\t\theaders: request.headers as Record<string, string>,\n\t});\n}\n\nexport async function setResponse(res: ServerResponse, response: Response) {\n\tfor (const [key, value] of response.headers as any) {\n\t\ttry {\n\t\t\tres.setHeader(\n\t\t\t\tkey,\n\t\t\t\tkey === \"set-cookie\"\n\t\t\t\t\t? set_cookie_parser.splitCookiesString(\n\t\t\t\t\t\t\tresponse.headers.get(key) as string,\n\t\t\t\t\t\t)\n\t\t\t\t\t: value,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tres.getHeaderNames().forEach((name) => res.removeHeader(name));\n\t\t\tres.writeHead(500).end(String(error));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tres.statusCode = response.status;\n\tres.writeHead(response.status);\n\n\tif (!response.body) {\n\t\tres.end();\n\t\treturn;\n\t}\n\n\tif (response.body.locked) {\n\t\tres.end(\n\t\t\t\"Fatal error: Response body is locked. \" +\n\t\t\t\t\"This can happen when the response was already read (for example through 'response.json()' or 'response.text()').\",\n\t\t);\n\t\treturn;\n\t}\n\n\tconst reader = response.body.getReader();\n\n\tif (res.destroyed) {\n\t\treader.cancel();\n\t\treturn;\n\t}\n\n\tconst cancel = (error?: Error) => {\n\t\tres.off(\"close\", cancel);\n\t\tres.off(\"error\", cancel);\n\n\t\t// If the reader has already been interrupted with an error earlier,\n\t\t// then it will appear here, it is useless, but it needs to be catch.\n\t\treader.cancel(error).catch(() => {});\n\t\tif (error) res.destroy(error);\n\t};\n\n\tres.on(\"close\", cancel);\n\tres.on(\"error\", cancel);\n\n\tnext();\n\tasync function next() {\n\t\ttry {\n\t\t\tfor (;;) {\n\t\t\t\tconst { done, value } = await reader.read();\n\n\t\t\t\tif (done) break;\n\n\t\t\t\tconst writeResult = res.write(value);\n\t\t\t\tif (!writeResult) {\n\t\t\t\t\t// In AWS Lambda/serverless environments, drain events may not work properly\n\t\t\t\t\t// Check if we're in a Lambda-like environment and handle differently\n\t\t\t\t\tif (\n\t\t\t\t\t\tprocess.env.AWS_LAMBDA_FUNCTION_NAME ||\n\t\t\t\t\t\tprocess.env.LAMBDA_TASK_ROOT\n\t\t\t\t\t) {\n\t\t\t\t\t\t// In Lambda, continue without waiting for drain\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Standard Node.js behavior\n\t\t\t\t\t\tres.once(\"drain\", next);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tres.end();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tcancel(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -1,6 +1,50 @@
1
1
  import * as set_cookie_parser from "set-cookie-parser";
2
2
 
3
3
  //#region src/adapters/node/request.ts
4
+ const getFirstHeaderValue = (header) => {
5
+ if (Array.isArray(header)) return header[0];
6
+ return header;
7
+ };
8
+ const hasFormUrlEncodedContentType = (headers) => {
9
+ const contentType = getFirstHeaderValue(headers["content-type"]);
10
+ if (!contentType) return false;
11
+ return contentType.toLowerCase().startsWith("application/x-www-form-urlencoded");
12
+ };
13
+ const isPlainObject = (value) => {
14
+ if (typeof value !== "object" || value === null) return false;
15
+ const prototype = Object.getPrototypeOf(value);
16
+ return prototype === Object.prototype || prototype === null;
17
+ };
18
+ const appendFormValue = (params, key, value) => {
19
+ if (value === void 0) return;
20
+ if (Array.isArray(value)) {
21
+ for (const item of value) appendFormValue(params, key, item);
22
+ return;
23
+ }
24
+ if (value === null) {
25
+ params.append(key, "");
26
+ return;
27
+ }
28
+ if (isPlainObject(value)) {
29
+ params.append(key, JSON.stringify(value));
30
+ return;
31
+ }
32
+ params.append(key, `${value}`);
33
+ };
34
+ const toFormUrlEncodedBody = (body) => {
35
+ const params = new URLSearchParams();
36
+ for (const [key, value] of Object.entries(body)) appendFormValue(params, key, value);
37
+ return params.toString();
38
+ };
39
+ const canReadRawBody = (request) => {
40
+ return !request.destroyed && request.readableEnded !== true && request.readable;
41
+ };
42
+ const serializeParsedBody = (parsedBody, isFormUrlEncoded) => {
43
+ if (typeof parsedBody === "string") return parsedBody;
44
+ if (parsedBody instanceof URLSearchParams) return parsedBody.toString();
45
+ if (isFormUrlEncoded && isPlainObject(parsedBody)) return toFormUrlEncodedBody(parsedBody);
46
+ return JSON.stringify(parsedBody);
47
+ };
4
48
  function get_raw_body(req, body_size_limit) {
5
49
  const h = req.headers;
6
50
  if (!h["content-type"]) return null;
@@ -58,15 +102,20 @@ function constructRelativeUrl(req) {
58
102
  }
59
103
  function getRequest({ request, base, bodySizeLimit }) {
60
104
  const maybeConsumedReq = request;
105
+ const isFormUrlEncoded = hasFormUrlEncodedContentType(request.headers);
61
106
  let body = void 0;
62
107
  const method = request.method;
63
- if (method !== "GET" && method !== "HEAD") if (maybeConsumedReq.body !== void 0) {
64
- const bodyContent = typeof maybeConsumedReq.body === "string" ? maybeConsumedReq.body : JSON.stringify(maybeConsumedReq.body);
65
- body = new ReadableStream({ start(controller) {
66
- controller.enqueue(new TextEncoder().encode(bodyContent));
67
- controller.close();
68
- } });
69
- } else body = get_raw_body(request, bodySizeLimit);
108
+ if (method !== "GET" && method !== "HEAD") {
109
+ if (canReadRawBody(request)) body = get_raw_body(request, bodySizeLimit);
110
+ else if (maybeConsumedReq.body !== void 0) {
111
+ const parsedBody = maybeConsumedReq.body;
112
+ const bodyContent = serializeParsedBody(parsedBody, isFormUrlEncoded);
113
+ body = new ReadableStream({ start(controller) {
114
+ controller.enqueue(new TextEncoder().encode(bodyContent));
115
+ controller.close();
116
+ } });
117
+ }
118
+ }
70
119
  return new Request(base + constructRelativeUrl(request), {
71
120
  duplex: "half",
72
121
  method: request.method,
@@ -1 +1 @@
1
- {"version":3,"file":"request.mjs","names":[],"sources":["../../../src/adapters/node/request.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport * as set_cookie_parser from \"set-cookie-parser\";\n\nfunction get_raw_body(req: IncomingMessage, body_size_limit?: number) {\n\tconst h = req.headers;\n\n\tif (!h[\"content-type\"]) return null;\n\n\tconst content_length = Number(h[\"content-length\"]);\n\n\t// check if no request body\n\tif (\n\t\t(req.httpVersionMajor === 1 &&\n\t\t\tisNaN(content_length) &&\n\t\t\th[\"transfer-encoding\"] == null) ||\n\t\tcontent_length === 0\n\t) {\n\t\treturn null;\n\t}\n\n\tlet length = content_length;\n\n\tif (body_size_limit) {\n\t\tif (!length) {\n\t\t\tlength = body_size_limit;\n\t\t} else if (length > body_size_limit) {\n\t\t\tthrow Error(\n\t\t\t\t`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (req.destroyed) {\n\t\tconst readable = new ReadableStream();\n\t\treadable.cancel();\n\t\treturn readable;\n\t}\n\n\tlet size = 0;\n\tlet cancelled = false;\n\n\treturn new ReadableStream({\n\t\tstart(controller) {\n\t\t\treq.on(\"error\", (error) => {\n\t\t\t\tcancelled = true;\n\t\t\t\tcontroller.error(error);\n\t\t\t});\n\n\t\t\treq.on(\"end\", () => {\n\t\t\t\tif (cancelled) return;\n\t\t\t\tcontroller.close();\n\t\t\t});\n\n\t\t\treq.on(\"data\", (chunk) => {\n\t\t\t\tif (cancelled) return;\n\n\t\t\t\tsize += chunk.length;\n\n\t\t\t\tif (size > length) {\n\t\t\t\t\tcancelled = true;\n\n\t\t\t\t\tcontroller.error(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`request body size exceeded ${\n\t\t\t\t\t\t\t\tcontent_length ? \"'content-length'\" : \"BODY_SIZE_LIMIT\"\n\t\t\t\t\t\t\t} of ${length}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontroller.enqueue(chunk);\n\n\t\t\t\tif (controller.desiredSize === null || controller.desiredSize <= 0) {\n\t\t\t\t\treq.pause();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tpull() {\n\t\t\treq.resume();\n\t\t},\n\n\t\tcancel(reason) {\n\t\t\tcancelled = true;\n\t\t\treq.destroy(reason);\n\t\t},\n\t});\n}\n\nfunction constructRelativeUrl(\n\treq: IncomingMessage & { baseUrl?: string; originalUrl?: string },\n) {\n\tconst baseUrl = req.baseUrl;\n\tconst originalUrl = req.originalUrl;\n\n\tif (!baseUrl || !originalUrl) {\n\t\t// In express.js sub-routers `req.url` is relative to the mount\n\t\t// path (e.g., '/auth/xxx'), and `req.baseUrl` will hold the mount\n\t\t// path (e.g., '/api'). Build the full path as baseUrl + url when\n\t\t// available to preserve the full route. For application level routes\n\t\t// baseUrl will be an empty string\n\t\treturn baseUrl ? baseUrl + req.url : req.url;\n\t}\n\n\tif (baseUrl + req.url === originalUrl) {\n\t\treturn baseUrl + req.url;\n\t}\n\n\t// For certain subroutes or when mounting wildcard middlewares in express\n\t// it is possible `baseUrl + req.url` will result in a url constructed\n\t// which has a trailing forward slash the original url did not have.\n\t// Checking the `req.originalUrl` path ending can prevent this issue.\n\n\tconst originalPathEnding = originalUrl.split(\"?\")[0]!.at(-1);\n\treturn originalPathEnding === \"/\" ? baseUrl + req.url : baseUrl;\n}\n\nexport function getRequest({\n\trequest,\n\tbase,\n\tbodySizeLimit,\n}: {\n\tbase: string;\n\tbodySizeLimit?: number;\n\trequest: IncomingMessage;\n}) {\n\t// Check if body has already been parsed by Express middleware\n\tconst maybeConsumedReq = request as any;\n\tlet body = undefined;\n\n\tconst method = request.method;\n\t// Request with GET/HEAD method cannot have body.\n\tif (method !== \"GET\" && method !== \"HEAD\") {\n\t\t// If body was already parsed by Express body-parser middleware\n\t\tif (maybeConsumedReq.body !== undefined) {\n\t\t\t// Convert parsed body back to a ReadableStream\n\t\t\tconst bodyContent =\n\t\t\t\ttypeof maybeConsumedReq.body === \"string\"\n\t\t\t\t\t? maybeConsumedReq.body\n\t\t\t\t\t: JSON.stringify(maybeConsumedReq.body);\n\n\t\t\tbody = new ReadableStream({\n\t\t\t\tstart(controller) {\n\t\t\t\t\tcontroller.enqueue(new TextEncoder().encode(bodyContent));\n\t\t\t\t\tcontroller.close();\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\t// Otherwise, get the raw body stream\n\t\t\tbody = get_raw_body(request, bodySizeLimit);\n\t\t}\n\t}\n\n\treturn new Request(base + constructRelativeUrl(request), {\n\t\t// @ts-expect-error\n\t\tduplex: \"half\",\n\t\tmethod: request.method,\n\t\tbody,\n\t\theaders: request.headers as Record<string, string>,\n\t});\n}\n\nexport async function setResponse(res: ServerResponse, response: Response) {\n\tfor (const [key, value] of response.headers as any) {\n\t\ttry {\n\t\t\tres.setHeader(\n\t\t\t\tkey,\n\t\t\t\tkey === \"set-cookie\"\n\t\t\t\t\t? set_cookie_parser.splitCookiesString(\n\t\t\t\t\t\t\tresponse.headers.get(key) as string,\n\t\t\t\t\t\t)\n\t\t\t\t\t: value,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tres.getHeaderNames().forEach((name) => res.removeHeader(name));\n\t\t\tres.writeHead(500).end(String(error));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tres.statusCode = response.status;\n\tres.writeHead(response.status);\n\n\tif (!response.body) {\n\t\tres.end();\n\t\treturn;\n\t}\n\n\tif (response.body.locked) {\n\t\tres.end(\n\t\t\t\"Fatal error: Response body is locked. \" +\n\t\t\t\t\"This can happen when the response was already read (for example through 'response.json()' or 'response.text()').\",\n\t\t);\n\t\treturn;\n\t}\n\n\tconst reader = response.body.getReader();\n\n\tif (res.destroyed) {\n\t\treader.cancel();\n\t\treturn;\n\t}\n\n\tconst cancel = (error?: Error) => {\n\t\tres.off(\"close\", cancel);\n\t\tres.off(\"error\", cancel);\n\n\t\t// If the reader has already been interrupted with an error earlier,\n\t\t// then it will appear here, it is useless, but it needs to be catch.\n\t\treader.cancel(error).catch(() => {});\n\t\tif (error) res.destroy(error);\n\t};\n\n\tres.on(\"close\", cancel);\n\tres.on(\"error\", cancel);\n\n\tnext();\n\tasync function next() {\n\t\ttry {\n\t\t\tfor (;;) {\n\t\t\t\tconst { done, value } = await reader.read();\n\n\t\t\t\tif (done) break;\n\n\t\t\t\tconst writeResult = res.write(value);\n\t\t\t\tif (!writeResult) {\n\t\t\t\t\t// In AWS Lambda/serverless environments, drain events may not work properly\n\t\t\t\t\t// Check if we're in a Lambda-like environment and handle differently\n\t\t\t\t\tif (\n\t\t\t\t\t\tprocess.env.AWS_LAMBDA_FUNCTION_NAME ||\n\t\t\t\t\t\tprocess.env.LAMBDA_TASK_ROOT\n\t\t\t\t\t) {\n\t\t\t\t\t\t// In Lambda, continue without waiting for drain\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Standard Node.js behavior\n\t\t\t\t\t\tres.once(\"drain\", next);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tres.end();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tcancel(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"request.mjs","names":[],"sources":["../../../src/adapters/node/request.ts"],"sourcesContent":["import type {\n\tIncomingHttpHeaders,\n\tIncomingMessage,\n\tServerResponse,\n} from \"node:http\";\nimport * as set_cookie_parser from \"set-cookie-parser\";\n\ntype NodeRequestWithBody = IncomingMessage & {\n\tbody?: unknown;\n};\n\nconst getFirstHeaderValue = (\n\theader: IncomingHttpHeaders[string],\n): string | undefined => {\n\tif (Array.isArray(header)) {\n\t\treturn header[0];\n\t}\n\treturn header;\n};\n\nconst hasFormUrlEncodedContentType = (\n\theaders: IncomingHttpHeaders,\n): boolean => {\n\tconst contentType = getFirstHeaderValue(headers[\"content-type\"]);\n\tif (!contentType) {\n\t\treturn false;\n\t}\n\treturn contentType\n\t\t.toLowerCase()\n\t\t.startsWith(\"application/x-www-form-urlencoded\");\n};\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n};\n\nconst appendFormValue = (\n\tparams: URLSearchParams,\n\tkey: string,\n\tvalue: unknown,\n) => {\n\tif (value === undefined) {\n\t\treturn;\n\t}\n\tif (Array.isArray(value)) {\n\t\tfor (const item of value) {\n\t\t\tappendFormValue(params, key, item);\n\t\t}\n\t\treturn;\n\t}\n\tif (value === null) {\n\t\tparams.append(key, \"\");\n\t\treturn;\n\t}\n\tif (isPlainObject(value)) {\n\t\tparams.append(key, JSON.stringify(value));\n\t\treturn;\n\t}\n\tparams.append(key, `${value}`);\n};\n\nconst toFormUrlEncodedBody = (\n\tbody: Readonly<Record<string, unknown>>,\n): string => {\n\tconst params = new URLSearchParams();\n\tfor (const [key, value] of Object.entries(body)) {\n\t\tappendFormValue(params, key, value);\n\t}\n\treturn params.toString();\n};\n\nconst canReadRawBody = (request: IncomingMessage): boolean => {\n\treturn (\n\t\t!request.destroyed && request.readableEnded !== true && request.readable\n\t);\n};\n\nconst serializeParsedBody = (\n\tparsedBody: unknown,\n\tisFormUrlEncoded: boolean,\n): string => {\n\tif (typeof parsedBody === \"string\") {\n\t\treturn parsedBody;\n\t}\n\tif (parsedBody instanceof URLSearchParams) {\n\t\treturn parsedBody.toString();\n\t}\n\tif (isFormUrlEncoded && isPlainObject(parsedBody)) {\n\t\treturn toFormUrlEncodedBody(parsedBody);\n\t}\n\treturn JSON.stringify(parsedBody);\n};\n\nfunction get_raw_body(req: IncomingMessage, body_size_limit?: number) {\n\tconst h = req.headers;\n\n\tif (!h[\"content-type\"]) return null;\n\n\tconst content_length = Number(h[\"content-length\"]);\n\n\t// check if no request body\n\tif (\n\t\t(req.httpVersionMajor === 1 &&\n\t\t\tisNaN(content_length) &&\n\t\t\th[\"transfer-encoding\"] == null) ||\n\t\tcontent_length === 0\n\t) {\n\t\treturn null;\n\t}\n\n\tlet length = content_length;\n\n\tif (body_size_limit) {\n\t\tif (!length) {\n\t\t\tlength = body_size_limit;\n\t\t} else if (length > body_size_limit) {\n\t\t\tthrow Error(\n\t\t\t\t`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (req.destroyed) {\n\t\tconst readable = new ReadableStream();\n\t\treadable.cancel();\n\t\treturn readable;\n\t}\n\n\tlet size = 0;\n\tlet cancelled = false;\n\n\treturn new ReadableStream({\n\t\tstart(controller) {\n\t\t\treq.on(\"error\", (error) => {\n\t\t\t\tcancelled = true;\n\t\t\t\tcontroller.error(error);\n\t\t\t});\n\n\t\t\treq.on(\"end\", () => {\n\t\t\t\tif (cancelled) return;\n\t\t\t\tcontroller.close();\n\t\t\t});\n\n\t\t\treq.on(\"data\", (chunk) => {\n\t\t\t\tif (cancelled) return;\n\n\t\t\t\tsize += chunk.length;\n\n\t\t\t\tif (size > length) {\n\t\t\t\t\tcancelled = true;\n\n\t\t\t\t\tcontroller.error(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`request body size exceeded ${\n\t\t\t\t\t\t\t\tcontent_length ? \"'content-length'\" : \"BODY_SIZE_LIMIT\"\n\t\t\t\t\t\t\t} of ${length}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontroller.enqueue(chunk);\n\n\t\t\t\tif (controller.desiredSize === null || controller.desiredSize <= 0) {\n\t\t\t\t\treq.pause();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tpull() {\n\t\t\treq.resume();\n\t\t},\n\n\t\tcancel(reason) {\n\t\t\tcancelled = true;\n\t\t\treq.destroy(reason);\n\t\t},\n\t});\n}\n\nfunction constructRelativeUrl(\n\treq: IncomingMessage & { baseUrl?: string; originalUrl?: string },\n) {\n\tconst baseUrl = req.baseUrl;\n\tconst originalUrl = req.originalUrl;\n\n\tif (!baseUrl || !originalUrl) {\n\t\t// In express.js sub-routers `req.url` is relative to the mount\n\t\t// path (e.g., '/auth/xxx'), and `req.baseUrl` will hold the mount\n\t\t// path (e.g., '/api'). Build the full path as baseUrl + url when\n\t\t// available to preserve the full route. For application level routes\n\t\t// baseUrl will be an empty string\n\t\treturn baseUrl ? baseUrl + req.url : req.url;\n\t}\n\n\tif (baseUrl + req.url === originalUrl) {\n\t\treturn baseUrl + req.url;\n\t}\n\n\t// For certain subroutes or when mounting wildcard middlewares in express\n\t// it is possible `baseUrl + req.url` will result in a url constructed\n\t// which has a trailing forward slash the original url did not have.\n\t// Checking the `req.originalUrl` path ending can prevent this issue.\n\n\tconst originalPathEnding = originalUrl.split(\"?\")[0]!.at(-1);\n\treturn originalPathEnding === \"/\" ? baseUrl + req.url : baseUrl;\n}\n\nexport function getRequest({\n\trequest,\n\tbase,\n\tbodySizeLimit,\n}: {\n\tbase: string;\n\tbodySizeLimit?: number;\n\trequest: IncomingMessage;\n}) {\n\t// Check if body has already been parsed by Express middleware\n\tconst maybeConsumedReq = request as NodeRequestWithBody;\n\tconst isFormUrlEncoded = hasFormUrlEncodedContentType(request.headers);\n\tlet body = undefined;\n\n\tconst method = request.method;\n\t// Request with GET/HEAD method cannot have body.\n\tif (method !== \"GET\" && method !== \"HEAD\") {\n\t\t// Raw-first strategy: prefer consuming the original request stream whenever it is still readable.\n\t\tif (canReadRawBody(request)) {\n\t\t\tbody = get_raw_body(request, bodySizeLimit);\n\t\t} else if (maybeConsumedReq.body !== undefined) {\n\t\t\tconst parsedBody = maybeConsumedReq.body;\n\n\t\t\tconst bodyContent = serializeParsedBody(parsedBody, isFormUrlEncoded);\n\t\t\tbody = new ReadableStream({\n\t\t\t\tstart(controller) {\n\t\t\t\t\tcontroller.enqueue(new TextEncoder().encode(bodyContent));\n\t\t\t\t\tcontroller.close();\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\treturn new Request(base + constructRelativeUrl(request), {\n\t\t// @ts-expect-error\n\t\tduplex: \"half\",\n\t\tmethod: request.method,\n\t\tbody,\n\t\theaders: request.headers as Record<string, string>,\n\t});\n}\n\nexport async function setResponse(res: ServerResponse, response: Response) {\n\tfor (const [key, value] of response.headers as any) {\n\t\ttry {\n\t\t\tres.setHeader(\n\t\t\t\tkey,\n\t\t\t\tkey === \"set-cookie\"\n\t\t\t\t\t? set_cookie_parser.splitCookiesString(\n\t\t\t\t\t\t\tresponse.headers.get(key) as string,\n\t\t\t\t\t\t)\n\t\t\t\t\t: value,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tres.getHeaderNames().forEach((name) => res.removeHeader(name));\n\t\t\tres.writeHead(500).end(String(error));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tres.statusCode = response.status;\n\tres.writeHead(response.status);\n\n\tif (!response.body) {\n\t\tres.end();\n\t\treturn;\n\t}\n\n\tif (response.body.locked) {\n\t\tres.end(\n\t\t\t\"Fatal error: Response body is locked. \" +\n\t\t\t\t\"This can happen when the response was already read (for example through 'response.json()' or 'response.text()').\",\n\t\t);\n\t\treturn;\n\t}\n\n\tconst reader = response.body.getReader();\n\n\tif (res.destroyed) {\n\t\treader.cancel();\n\t\treturn;\n\t}\n\n\tconst cancel = (error?: Error) => {\n\t\tres.off(\"close\", cancel);\n\t\tres.off(\"error\", cancel);\n\n\t\t// If the reader has already been interrupted with an error earlier,\n\t\t// then it will appear here, it is useless, but it needs to be catch.\n\t\treader.cancel(error).catch(() => {});\n\t\tif (error) res.destroy(error);\n\t};\n\n\tres.on(\"close\", cancel);\n\tres.on(\"error\", cancel);\n\n\tnext();\n\tasync function next() {\n\t\ttry {\n\t\t\tfor (;;) {\n\t\t\t\tconst { done, value } = await reader.read();\n\n\t\t\t\tif (done) break;\n\n\t\t\t\tconst writeResult = res.write(value);\n\t\t\t\tif (!writeResult) {\n\t\t\t\t\t// In AWS Lambda/serverless environments, drain events may not work properly\n\t\t\t\t\t// Check if we're in a Lambda-like environment and handle differently\n\t\t\t\t\tif (\n\t\t\t\t\t\tprocess.env.AWS_LAMBDA_FUNCTION_NAME ||\n\t\t\t\t\t\tprocess.env.LAMBDA_TASK_ROOT\n\t\t\t\t\t) {\n\t\t\t\t\t\t// In Lambda, continue without waiting for drain\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Standard Node.js behavior\n\t\t\t\t\t\tres.once(\"drain\", next);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tres.end();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tcancel(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -23,7 +23,7 @@ function createMiddleware(optionsOrHandler, handler) {
23
23
  } catch (e) {
24
24
  if (require_utils.isAPIError(e)) Object.defineProperty(e, require_error.kAPIErrorHeaderSymbol, {
25
25
  enumerable: false,
26
- configurable: false,
26
+ configurable: true,
27
27
  get() {
28
28
  return internalContext.responseHeaders;
29
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":["createInternalContext","isAPIError","kAPIErrorHeaderSymbol"],"sources":["../src/middleware.ts"],"sourcesContent":["import {\n\tcreateEndpoint,\n\ttype Endpoint,\n\ttype EndpointContext,\n\ttype EndpointOptions,\n} from \"./endpoint\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferBodyInput,\n\ttype InferHeaders,\n\ttype InferHeadersInput,\n\ttype InferMiddlewareBody,\n\ttype InferMiddlewareQuery,\n\ttype InferQuery,\n\ttype InferQueryInput,\n\ttype InferRequest,\n\ttype InferRequestInput,\n\ttype InferUse,\n\ttype InputContext,\n} from \"./context\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the\n\t * params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*`\n\t * then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the\n\t * wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: string;\n\t/**\n\t * Request object\n\t *\n\t * If `requireRequest` is set to true in the endpoint options this will be\n\t * required\n\t */\n\trequest: InferRequest<Options>;\n\t/**\n\t * Headers\n\t *\n\t * If `requireHeaders` is set to true in the endpoint options this will be\n\t * required\n\t */\n\theaders: InferHeaders<Options>;\n\t/**\n\t * Set header\n\t *\n\t * If it's called outside of a request it will just be ignored.\n\t */\n\tsetHeader: (key: string, value: string) => void;\n\t/**\n\t * Get header\n\t *\n\t * If it's called outside of a request it will just return null\n\t *\n\t * @param key - The key of the header\n\t * @returns\n\t */\n\tgetHeader: (key: string) => string | null;\n\t/**\n\t * JSON\n\t *\n\t * a helper function to create a JSON response with\n\t * the correct headers\n\t * and status code. If `asResponse` is set to true in\n\t * the context then\n\t * it will return a Response object instead of the\n\t * JSON object.\n\t *\n\t * @param json - The JSON object to return\n\t * @param routerResponse - The response object to\n\t * return if `asResponse` is\n\t * true in the context this will take precedence\n\t */\n\tjson: <R extends Record<string, any> | null>(\n\t\tjson: R,\n\t\trouterResponse?:\n\t\t\t| {\n\t\t\t\t\tstatus?: number;\n\t\t\t\t\theaders?: Record<string, string>;\n\t\t\t\t\tresponse?: Response;\n\t\t\t }\n\t\t\t| Response,\n\t) => Promise<R>;\n\t/**\n\t * Middleware context\n\t */\n\tcontext: Prettify<Context>;\n};\n\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\toptions: Options,\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n\tconst internalHandler = async (inputCtx: InputContext<any, any>) => {\n\t\tconst context = inputCtx as InputContext<any, any>;\n\t\tconst _handler =\n\t\t\ttypeof optionsOrHandler === \"function\" ? optionsOrHandler : handler;\n\t\tconst options =\n\t\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\t\tconst internalContext = await createInternalContext(context, {\n\t\t\toptions,\n\t\t\tpath: \"/\",\n\t\t});\n\n\t\tif (!_handler) {\n\t\t\tthrow new Error(\"handler must be defined\");\n\t\t}\n\t\ttry {\n\t\t\tconst response = await _handler(internalContext as any);\n\t\t\tconst headers = internalContext.responseHeaders;\n\t\t\treturn context.returnHeaders\n\t\t\t\t? {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t}\n\t\t\t\t: response;\n\t\t} catch (e) {\n\t\t\t// fixme(alex): this is workaround that set-cookie headers are not accessible when error is thrown from middleware\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tObject.defineProperty(e, kAPIErrorHeaderSymbol, {\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\tconfigurable: false,\n\t\t\t\t\tget() {\n\t\t\t\t\t\treturn internalContext.responseHeaders;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t};\n\tinternalHandler.options =\n\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\treturn internalHandler;\n}\n\nexport type MiddlewareInputContext<Options extends MiddlewareOptions> =\n\tInferBodyInput<Options> &\n\t\tInferQueryInput<Options> &\n\t\tInferRequestInput<Options> &\n\t\tInferHeadersInput<Options> & {\n\t\t\tasResponse?: boolean;\n\t\t\treturnHeaders?: boolean;\n\t\t\tuse?: Middleware[];\n\t\t};\n\nexport type Middleware<\n\tOptions extends MiddlewareOptions = MiddlewareOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = any,\n> = Handler & {\n\toptions: Options;\n};\n\ncreateMiddleware.create = <\n\tE extends {\n\t\tuse?: Middleware[];\n\t},\n>(\n\topts?: E,\n) => {\n\ttype InferredContext = InferUse<E[\"use\"]>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\toptions: Options,\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn(optionsOrHandler: any, handler?: any) {\n\t\tif (typeof optionsOrHandler === \"function\") {\n\t\t\treturn createMiddleware(\n\t\t\t\t{\n\t\t\t\t\tuse: opts?.use,\n\t\t\t\t},\n\t\t\t\toptionsOrHandler,\n\t\t\t);\n\t\t}\n\t\tif (!handler) {\n\t\t\tthrow new Error(\"Middleware handler is required\");\n\t\t}\n\t\tconst middleware = createMiddleware(\n\t\t\t{\n\t\t\t\t...optionsOrHandler,\n\t\t\t\tmethod: \"*\",\n\t\t\t\tuse: [...(opts?.use || []), ...(optionsOrHandler.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t\treturn middleware as any;\n\t}\n\treturn fn;\n};\n"],"mappings":";;;;;;AAmJA,SAAgB,iBAAiB,kBAAuB,SAAe;CACtE,MAAM,kBAAkB,OAAO,aAAqC;EACnE,MAAM,UAAU;EAChB,MAAM,WACL,OAAO,qBAAqB,aAAa,mBAAmB;EAG7D,MAAM,kBAAkB,MAAMA,sCAAsB,SAAS;GAC5D,SAFA,OAAO,qBAAqB,aAAa,EAAE,GAAG;GAG9C,MAAM;GACN,CAAC;AAEF,MAAI,CAAC,SACJ,OAAM,IAAI,MAAM,0BAA0B;AAE3C,MAAI;GACH,MAAM,WAAW,MAAM,SAAS,gBAAuB;GACvD,MAAM,UAAU,gBAAgB;AAChC,UAAO,QAAQ,gBACZ;IACA;IACA;IACA,GACA;WACK,GAAG;AAEX,OAAIC,yBAAW,EAAE,CAChB,QAAO,eAAe,GAAGC,qCAAuB;IAC/C,YAAY;IACZ,cAAc;IACd,MAAM;AACL,YAAO,gBAAgB;;IAExB,CAAC;AAEH,SAAM;;;AAGR,iBAAgB,UACf,OAAO,qBAAqB,aAAa,EAAE,GAAG;AAC/C,QAAO;;AAoBR,iBAAiB,UAKhB,SACI;CASJ,SAAS,GAAG,kBAAuB,SAAe;AACjD,MAAI,OAAO,qBAAqB,WAC/B,QAAO,iBACN,EACC,KAAK,MAAM,KACX,EACD,iBACA;AAEF,MAAI,CAAC,QACJ,OAAM,IAAI,MAAM,iCAAiC;AAUlD,SARmB,iBAClB;GACC,GAAG;GACH,QAAQ;GACR,KAAK,CAAC,GAAI,MAAM,OAAO,EAAE,EAAG,GAAI,iBAAiB,OAAO,EAAE,CAAE;GAC5D,EACD,QACA;;AAGF,QAAO"}
1
+ {"version":3,"file":"middleware.cjs","names":["createInternalContext","isAPIError","kAPIErrorHeaderSymbol"],"sources":["../src/middleware.ts"],"sourcesContent":["import {\n\tcreateEndpoint,\n\ttype Endpoint,\n\ttype EndpointContext,\n\ttype EndpointOptions,\n} from \"./endpoint\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferBodyInput,\n\ttype InferHeaders,\n\ttype InferHeadersInput,\n\ttype InferMiddlewareBody,\n\ttype InferMiddlewareQuery,\n\ttype InferQuery,\n\ttype InferQueryInput,\n\ttype InferRequest,\n\ttype InferRequestInput,\n\ttype InferUse,\n\ttype InputContext,\n} from \"./context\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the\n\t * params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*`\n\t * then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the\n\t * wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: string;\n\t/**\n\t * Request object\n\t *\n\t * If `requireRequest` is set to true in the endpoint options this will be\n\t * required\n\t */\n\trequest: InferRequest<Options>;\n\t/**\n\t * Headers\n\t *\n\t * If `requireHeaders` is set to true in the endpoint options this will be\n\t * required\n\t */\n\theaders: InferHeaders<Options>;\n\t/**\n\t * Set header\n\t *\n\t * If it's called outside of a request it will just be ignored.\n\t */\n\tsetHeader: (key: string, value: string) => void;\n\t/**\n\t * Get header\n\t *\n\t * If it's called outside of a request it will just return null\n\t *\n\t * @param key - The key of the header\n\t * @returns\n\t */\n\tgetHeader: (key: string) => string | null;\n\t/**\n\t * JSON\n\t *\n\t * a helper function to create a JSON response with\n\t * the correct headers\n\t * and status code. If `asResponse` is set to true in\n\t * the context then\n\t * it will return a Response object instead of the\n\t * JSON object.\n\t *\n\t * @param json - The JSON object to return\n\t * @param routerResponse - The response object to\n\t * return if `asResponse` is\n\t * true in the context this will take precedence\n\t */\n\tjson: <R extends Record<string, any> | null>(\n\t\tjson: R,\n\t\trouterResponse?:\n\t\t\t| {\n\t\t\t\t\tstatus?: number;\n\t\t\t\t\theaders?: Record<string, string>;\n\t\t\t\t\tresponse?: Response;\n\t\t\t }\n\t\t\t| Response,\n\t) => Promise<R>;\n\t/**\n\t * Middleware context\n\t */\n\tcontext: Prettify<Context>;\n};\n\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\toptions: Options,\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n\tconst internalHandler = async (inputCtx: InputContext<any, any>) => {\n\t\tconst context = inputCtx as InputContext<any, any>;\n\t\tconst _handler =\n\t\t\ttypeof optionsOrHandler === \"function\" ? optionsOrHandler : handler;\n\t\tconst options =\n\t\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\t\tconst internalContext = await createInternalContext(context, {\n\t\t\toptions,\n\t\t\tpath: \"/\",\n\t\t});\n\n\t\tif (!_handler) {\n\t\t\tthrow new Error(\"handler must be defined\");\n\t\t}\n\t\ttry {\n\t\t\tconst response = await _handler(internalContext as any);\n\t\t\tconst headers = internalContext.responseHeaders;\n\t\t\treturn context.returnHeaders\n\t\t\t\t? {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t}\n\t\t\t\t: response;\n\t\t} catch (e) {\n\t\t\t// fixme(alex): this is workaround that set-cookie headers are not accessible when error is thrown from middleware\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tObject.defineProperty(e, kAPIErrorHeaderSymbol, {\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\tget() {\n\t\t\t\t\t\treturn internalContext.responseHeaders;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t};\n\tinternalHandler.options =\n\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\treturn internalHandler;\n}\n\nexport type MiddlewareInputContext<Options extends MiddlewareOptions> =\n\tInferBodyInput<Options> &\n\t\tInferQueryInput<Options> &\n\t\tInferRequestInput<Options> &\n\t\tInferHeadersInput<Options> & {\n\t\t\tasResponse?: boolean;\n\t\t\treturnHeaders?: boolean;\n\t\t\tuse?: Middleware[];\n\t\t};\n\nexport type Middleware<\n\tOptions extends MiddlewareOptions = MiddlewareOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = any,\n> = Handler & {\n\toptions: Options;\n};\n\ncreateMiddleware.create = <\n\tE extends {\n\t\tuse?: Middleware[];\n\t},\n>(\n\topts?: E,\n) => {\n\ttype InferredContext = InferUse<E[\"use\"]>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\toptions: Options,\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn(optionsOrHandler: any, handler?: any) {\n\t\tif (typeof optionsOrHandler === \"function\") {\n\t\t\treturn createMiddleware(\n\t\t\t\t{\n\t\t\t\t\tuse: opts?.use,\n\t\t\t\t},\n\t\t\t\toptionsOrHandler,\n\t\t\t);\n\t\t}\n\t\tif (!handler) {\n\t\t\tthrow new Error(\"Middleware handler is required\");\n\t\t}\n\t\tconst middleware = createMiddleware(\n\t\t\t{\n\t\t\t\t...optionsOrHandler,\n\t\t\t\tmethod: \"*\",\n\t\t\t\tuse: [...(opts?.use || []), ...(optionsOrHandler.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t\treturn middleware as any;\n\t}\n\treturn fn;\n};\n"],"mappings":";;;;;;AAmJA,SAAgB,iBAAiB,kBAAuB,SAAe;CACtE,MAAM,kBAAkB,OAAO,aAAqC;EACnE,MAAM,UAAU;EAChB,MAAM,WACL,OAAO,qBAAqB,aAAa,mBAAmB;EAG7D,MAAM,kBAAkB,MAAMA,sCAAsB,SAAS;GAC5D,SAFA,OAAO,qBAAqB,aAAa,EAAE,GAAG;GAG9C,MAAM;GACN,CAAC;AAEF,MAAI,CAAC,SACJ,OAAM,IAAI,MAAM,0BAA0B;AAE3C,MAAI;GACH,MAAM,WAAW,MAAM,SAAS,gBAAuB;GACvD,MAAM,UAAU,gBAAgB;AAChC,UAAO,QAAQ,gBACZ;IACA;IACA;IACA,GACA;WACK,GAAG;AAEX,OAAIC,yBAAW,EAAE,CAChB,QAAO,eAAe,GAAGC,qCAAuB;IAC/C,YAAY;IACZ,cAAc;IACd,MAAM;AACL,YAAO,gBAAgB;;IAExB,CAAC;AAEH,SAAM;;;AAGR,iBAAgB,UACf,OAAO,qBAAqB,aAAa,EAAE,GAAG;AAC/C,QAAO;;AAoBR,iBAAiB,UAKhB,SACI;CASJ,SAAS,GAAG,kBAAuB,SAAe;AACjD,MAAI,OAAO,qBAAqB,WAC/B,QAAO,iBACN,EACC,KAAK,MAAM,KACX,EACD,iBACA;AAEF,MAAI,CAAC,QACJ,OAAM,IAAI,MAAM,iCAAiC;AAUlD,SARmB,iBAClB;GACC,GAAG;GACH,QAAQ;GACR,KAAK,CAAC,GAAI,MAAM,OAAO,EAAE,EAAG,GAAI,iBAAiB,OAAO,EAAE,CAAE;GAC5D,EACD,QACA;;AAGF,QAAO"}
@@ -23,7 +23,7 @@ function createMiddleware(optionsOrHandler, handler) {
23
23
  } catch (e) {
24
24
  if (isAPIError(e)) Object.defineProperty(e, kAPIErrorHeaderSymbol, {
25
25
  enumerable: false,
26
- configurable: false,
26
+ configurable: true,
27
27
  get() {
28
28
  return internalContext.responseHeaders;
29
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import {\n\tcreateEndpoint,\n\ttype Endpoint,\n\ttype EndpointContext,\n\ttype EndpointOptions,\n} from \"./endpoint\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferBodyInput,\n\ttype InferHeaders,\n\ttype InferHeadersInput,\n\ttype InferMiddlewareBody,\n\ttype InferMiddlewareQuery,\n\ttype InferQuery,\n\ttype InferQueryInput,\n\ttype InferRequest,\n\ttype InferRequestInput,\n\ttype InferUse,\n\ttype InputContext,\n} from \"./context\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the\n\t * params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*`\n\t * then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the\n\t * wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: string;\n\t/**\n\t * Request object\n\t *\n\t * If `requireRequest` is set to true in the endpoint options this will be\n\t * required\n\t */\n\trequest: InferRequest<Options>;\n\t/**\n\t * Headers\n\t *\n\t * If `requireHeaders` is set to true in the endpoint options this will be\n\t * required\n\t */\n\theaders: InferHeaders<Options>;\n\t/**\n\t * Set header\n\t *\n\t * If it's called outside of a request it will just be ignored.\n\t */\n\tsetHeader: (key: string, value: string) => void;\n\t/**\n\t * Get header\n\t *\n\t * If it's called outside of a request it will just return null\n\t *\n\t * @param key - The key of the header\n\t * @returns\n\t */\n\tgetHeader: (key: string) => string | null;\n\t/**\n\t * JSON\n\t *\n\t * a helper function to create a JSON response with\n\t * the correct headers\n\t * and status code. If `asResponse` is set to true in\n\t * the context then\n\t * it will return a Response object instead of the\n\t * JSON object.\n\t *\n\t * @param json - The JSON object to return\n\t * @param routerResponse - The response object to\n\t * return if `asResponse` is\n\t * true in the context this will take precedence\n\t */\n\tjson: <R extends Record<string, any> | null>(\n\t\tjson: R,\n\t\trouterResponse?:\n\t\t\t| {\n\t\t\t\t\tstatus?: number;\n\t\t\t\t\theaders?: Record<string, string>;\n\t\t\t\t\tresponse?: Response;\n\t\t\t }\n\t\t\t| Response,\n\t) => Promise<R>;\n\t/**\n\t * Middleware context\n\t */\n\tcontext: Prettify<Context>;\n};\n\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\toptions: Options,\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n\tconst internalHandler = async (inputCtx: InputContext<any, any>) => {\n\t\tconst context = inputCtx as InputContext<any, any>;\n\t\tconst _handler =\n\t\t\ttypeof optionsOrHandler === \"function\" ? optionsOrHandler : handler;\n\t\tconst options =\n\t\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\t\tconst internalContext = await createInternalContext(context, {\n\t\t\toptions,\n\t\t\tpath: \"/\",\n\t\t});\n\n\t\tif (!_handler) {\n\t\t\tthrow new Error(\"handler must be defined\");\n\t\t}\n\t\ttry {\n\t\t\tconst response = await _handler(internalContext as any);\n\t\t\tconst headers = internalContext.responseHeaders;\n\t\t\treturn context.returnHeaders\n\t\t\t\t? {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t}\n\t\t\t\t: response;\n\t\t} catch (e) {\n\t\t\t// fixme(alex): this is workaround that set-cookie headers are not accessible when error is thrown from middleware\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tObject.defineProperty(e, kAPIErrorHeaderSymbol, {\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\tconfigurable: false,\n\t\t\t\t\tget() {\n\t\t\t\t\t\treturn internalContext.responseHeaders;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t};\n\tinternalHandler.options =\n\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\treturn internalHandler;\n}\n\nexport type MiddlewareInputContext<Options extends MiddlewareOptions> =\n\tInferBodyInput<Options> &\n\t\tInferQueryInput<Options> &\n\t\tInferRequestInput<Options> &\n\t\tInferHeadersInput<Options> & {\n\t\t\tasResponse?: boolean;\n\t\t\treturnHeaders?: boolean;\n\t\t\tuse?: Middleware[];\n\t\t};\n\nexport type Middleware<\n\tOptions extends MiddlewareOptions = MiddlewareOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = any,\n> = Handler & {\n\toptions: Options;\n};\n\ncreateMiddleware.create = <\n\tE extends {\n\t\tuse?: Middleware[];\n\t},\n>(\n\topts?: E,\n) => {\n\ttype InferredContext = InferUse<E[\"use\"]>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\toptions: Options,\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn(optionsOrHandler: any, handler?: any) {\n\t\tif (typeof optionsOrHandler === \"function\") {\n\t\t\treturn createMiddleware(\n\t\t\t\t{\n\t\t\t\t\tuse: opts?.use,\n\t\t\t\t},\n\t\t\t\toptionsOrHandler,\n\t\t\t);\n\t\t}\n\t\tif (!handler) {\n\t\t\tthrow new Error(\"Middleware handler is required\");\n\t\t}\n\t\tconst middleware = createMiddleware(\n\t\t\t{\n\t\t\t\t...optionsOrHandler,\n\t\t\t\tmethod: \"*\",\n\t\t\t\tuse: [...(opts?.use || []), ...(optionsOrHandler.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t\treturn middleware as any;\n\t}\n\treturn fn;\n};\n"],"mappings":";;;;;;AAmJA,SAAgB,iBAAiB,kBAAuB,SAAe;CACtE,MAAM,kBAAkB,OAAO,aAAqC;EACnE,MAAM,UAAU;EAChB,MAAM,WACL,OAAO,qBAAqB,aAAa,mBAAmB;EAG7D,MAAM,kBAAkB,MAAM,sBAAsB,SAAS;GAC5D,SAFA,OAAO,qBAAqB,aAAa,EAAE,GAAG;GAG9C,MAAM;GACN,CAAC;AAEF,MAAI,CAAC,SACJ,OAAM,IAAI,MAAM,0BAA0B;AAE3C,MAAI;GACH,MAAM,WAAW,MAAM,SAAS,gBAAuB;GACvD,MAAM,UAAU,gBAAgB;AAChC,UAAO,QAAQ,gBACZ;IACA;IACA;IACA,GACA;WACK,GAAG;AAEX,OAAI,WAAW,EAAE,CAChB,QAAO,eAAe,GAAG,uBAAuB;IAC/C,YAAY;IACZ,cAAc;IACd,MAAM;AACL,YAAO,gBAAgB;;IAExB,CAAC;AAEH,SAAM;;;AAGR,iBAAgB,UACf,OAAO,qBAAqB,aAAa,EAAE,GAAG;AAC/C,QAAO;;AAoBR,iBAAiB,UAKhB,SACI;CASJ,SAAS,GAAG,kBAAuB,SAAe;AACjD,MAAI,OAAO,qBAAqB,WAC/B,QAAO,iBACN,EACC,KAAK,MAAM,KACX,EACD,iBACA;AAEF,MAAI,CAAC,QACJ,OAAM,IAAI,MAAM,iCAAiC;AAUlD,SARmB,iBAClB;GACC,GAAG;GACH,QAAQ;GACR,KAAK,CAAC,GAAI,MAAM,OAAO,EAAE,EAAG,GAAI,iBAAiB,OAAO,EAAE,CAAE;GAC5D,EACD,QACA;;AAGF,QAAO"}
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import {\n\tcreateEndpoint,\n\ttype Endpoint,\n\ttype EndpointContext,\n\ttype EndpointOptions,\n} from \"./endpoint\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferBodyInput,\n\ttype InferHeaders,\n\ttype InferHeadersInput,\n\ttype InferMiddlewareBody,\n\ttype InferMiddlewareQuery,\n\ttype InferQuery,\n\ttype InferQueryInput,\n\ttype InferRequest,\n\ttype InferRequestInput,\n\ttype InferUse,\n\ttype InputContext,\n} from \"./context\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the\n\t * params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*`\n\t * then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the\n\t * wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: string;\n\t/**\n\t * Request object\n\t *\n\t * If `requireRequest` is set to true in the endpoint options this will be\n\t * required\n\t */\n\trequest: InferRequest<Options>;\n\t/**\n\t * Headers\n\t *\n\t * If `requireHeaders` is set to true in the endpoint options this will be\n\t * required\n\t */\n\theaders: InferHeaders<Options>;\n\t/**\n\t * Set header\n\t *\n\t * If it's called outside of a request it will just be ignored.\n\t */\n\tsetHeader: (key: string, value: string) => void;\n\t/**\n\t * Get header\n\t *\n\t * If it's called outside of a request it will just return null\n\t *\n\t * @param key - The key of the header\n\t * @returns\n\t */\n\tgetHeader: (key: string) => string | null;\n\t/**\n\t * JSON\n\t *\n\t * a helper function to create a JSON response with\n\t * the correct headers\n\t * and status code. If `asResponse` is set to true in\n\t * the context then\n\t * it will return a Response object instead of the\n\t * JSON object.\n\t *\n\t * @param json - The JSON object to return\n\t * @param routerResponse - The response object to\n\t * return if `asResponse` is\n\t * true in the context this will take precedence\n\t */\n\tjson: <R extends Record<string, any> | null>(\n\t\tjson: R,\n\t\trouterResponse?:\n\t\t\t| {\n\t\t\t\t\tstatus?: number;\n\t\t\t\t\theaders?: Record<string, string>;\n\t\t\t\t\tresponse?: Response;\n\t\t\t }\n\t\t\t| Response,\n\t) => Promise<R>;\n\t/**\n\t * Middleware context\n\t */\n\tcontext: Prettify<Context>;\n};\n\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\toptions: Options,\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): <InputCtx extends MiddlewareInputContext<Options>>(\n\tinputContext: InputCtx,\n) => Promise<R>;\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n\tconst internalHandler = async (inputCtx: InputContext<any, any>) => {\n\t\tconst context = inputCtx as InputContext<any, any>;\n\t\tconst _handler =\n\t\t\ttypeof optionsOrHandler === \"function\" ? optionsOrHandler : handler;\n\t\tconst options =\n\t\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\t\tconst internalContext = await createInternalContext(context, {\n\t\t\toptions,\n\t\t\tpath: \"/\",\n\t\t});\n\n\t\tif (!_handler) {\n\t\t\tthrow new Error(\"handler must be defined\");\n\t\t}\n\t\ttry {\n\t\t\tconst response = await _handler(internalContext as any);\n\t\t\tconst headers = internalContext.responseHeaders;\n\t\t\treturn context.returnHeaders\n\t\t\t\t? {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t}\n\t\t\t\t: response;\n\t\t} catch (e) {\n\t\t\t// fixme(alex): this is workaround that set-cookie headers are not accessible when error is thrown from middleware\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tObject.defineProperty(e, kAPIErrorHeaderSymbol, {\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\tget() {\n\t\t\t\t\t\treturn internalContext.responseHeaders;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t};\n\tinternalHandler.options =\n\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\treturn internalHandler;\n}\n\nexport type MiddlewareInputContext<Options extends MiddlewareOptions> =\n\tInferBodyInput<Options> &\n\t\tInferQueryInput<Options> &\n\t\tInferRequestInput<Options> &\n\t\tInferHeadersInput<Options> & {\n\t\t\tasResponse?: boolean;\n\t\t\treturnHeaders?: boolean;\n\t\t\tuse?: Middleware[];\n\t\t};\n\nexport type Middleware<\n\tOptions extends MiddlewareOptions = MiddlewareOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = any,\n> = Handler & {\n\toptions: Options;\n};\n\ncreateMiddleware.create = <\n\tE extends {\n\t\tuse?: Middleware[];\n\t},\n>(\n\topts?: E,\n) => {\n\ttype InferredContext = InferUse<E[\"use\"]>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\toptions: Options,\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): (inputContext: MiddlewareInputContext<Options>) => Promise<R>;\n\tfunction fn(optionsOrHandler: any, handler?: any) {\n\t\tif (typeof optionsOrHandler === \"function\") {\n\t\t\treturn createMiddleware(\n\t\t\t\t{\n\t\t\t\t\tuse: opts?.use,\n\t\t\t\t},\n\t\t\t\toptionsOrHandler,\n\t\t\t);\n\t\t}\n\t\tif (!handler) {\n\t\t\tthrow new Error(\"Middleware handler is required\");\n\t\t}\n\t\tconst middleware = createMiddleware(\n\t\t\t{\n\t\t\t\t...optionsOrHandler,\n\t\t\t\tmethod: \"*\",\n\t\t\t\tuse: [...(opts?.use || []), ...(optionsOrHandler.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t\treturn middleware as any;\n\t}\n\treturn fn;\n};\n"],"mappings":";;;;;;AAmJA,SAAgB,iBAAiB,kBAAuB,SAAe;CACtE,MAAM,kBAAkB,OAAO,aAAqC;EACnE,MAAM,UAAU;EAChB,MAAM,WACL,OAAO,qBAAqB,aAAa,mBAAmB;EAG7D,MAAM,kBAAkB,MAAM,sBAAsB,SAAS;GAC5D,SAFA,OAAO,qBAAqB,aAAa,EAAE,GAAG;GAG9C,MAAM;GACN,CAAC;AAEF,MAAI,CAAC,SACJ,OAAM,IAAI,MAAM,0BAA0B;AAE3C,MAAI;GACH,MAAM,WAAW,MAAM,SAAS,gBAAuB;GACvD,MAAM,UAAU,gBAAgB;AAChC,UAAO,QAAQ,gBACZ;IACA;IACA;IACA,GACA;WACK,GAAG;AAEX,OAAI,WAAW,EAAE,CAChB,QAAO,eAAe,GAAG,uBAAuB;IAC/C,YAAY;IACZ,cAAc;IACd,MAAM;AACL,YAAO,gBAAgB;;IAExB,CAAC;AAEH,SAAM;;;AAGR,iBAAgB,UACf,OAAO,qBAAqB,aAAa,EAAE,GAAG;AAC/C,QAAO;;AAoBR,iBAAiB,UAKhB,SACI;CASJ,SAAS,GAAG,kBAAuB,SAAe;AACjD,MAAI,OAAO,qBAAqB,WAC/B,QAAO,iBACN,EACC,KAAK,MAAM,KACX,EACD,iBACA;AAEF,MAAI,CAAC,QACJ,OAAM,IAAI,MAAM,iCAAiC;AAUlD,SARmB,iBAClB;GACC,GAAG;GACH,QAAQ;GACR,KAAK,CAAC,GAAI,MAAM,OAAO,EAAE,EAAG,GAAI,iBAAiB,OAAO,EAAE,CAAE;GAC5D,EACD,QACA;;AAGF,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-call",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",