better-call 1.3.5 → 1.3.6

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.
@@ -167,8 +167,8 @@ async function setResponse(res, response) {
167
167
  res.once("drain", next);
168
168
  return;
169
169
  }
170
- res.end();
171
170
  }
171
+ res.end();
172
172
  } catch (error) {
173
173
  cancel(error instanceof Error ? error : new Error(String(error)));
174
174
  }
@@ -1 +1 @@
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
+ {"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}\n\t\t\tres.end();\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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"}
@@ -165,8 +165,8 @@ async function setResponse(res, response) {
165
165
  res.once("drain", next);
166
166
  return;
167
167
  }
168
- res.end();
169
168
  }
169
+ res.end();
170
170
  } catch (error) {
171
171
  cancel(error instanceof Error ? error : new Error(String(error)));
172
172
  }
@@ -1 +1 @@
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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"}
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}\n\t\t\tres.end();\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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"}
package/dist/client.cjs CHANGED
@@ -6,8 +6,8 @@ let _better_fetch_fetch = require("@better-fetch/fetch");
6
6
  //#region src/client.ts
7
7
  const createClient = (options) => {
8
8
  const fetch = (0, _better_fetch_fetch.createFetch)(options ?? {});
9
- return async (path, ...options) => {
10
- return await fetch(path, { ...options[0] });
9
+ return async (path, ...callOptions) => {
10
+ return await fetch(path, { ...callOptions[0] });
11
11
  };
12
12
  };
13
13
 
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype BetterFetchOption,\n\ttype BetterFetchResponse,\n\tcreateFetch,\n} from \"@better-fetch/fetch\";\nimport type { Router } from \"./router\";\nimport type { HasRequiredKeys, Prettify, UnionToIntersection } from \"./helper\";\nimport type { Endpoint } from \"./endpoint\";\n\nexport type HasRequired<T extends object> = T extends {}\n\t? false\n\t: T extends {\n\t\t\t\tbody?: any;\n\t\t\t\tquery?: any;\n\t\t\t\tparams?: any;\n\t\t\t}\n\t\t? T[\"body\"] extends object\n\t\t\t? HasRequiredKeys<T[\"body\"]> extends true\n\t\t\t\t? true\n\t\t\t\t: T[\"query\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t\t: false\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t: T[\"query\"] extends object\n\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t? true\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t: false\n\t\t: false;\n\ntype InferContext<T> = T extends (ctx: infer Ctx) => any\n\t? Ctx extends object\n\t\t? Ctx\n\t\t: never\n\t: never;\n\nexport interface ClientOptions extends BetterFetchOption {\n\tbaseURL?: string;\n}\n\ntype WithRequired<T, K> = T & {\n\t[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];\n};\n\ntype InferClientRoutes<T extends Record<string, Endpoint>> = {\n\t[K in keyof T]: T[K] extends Endpoint<any, infer O>\n\t\t? O extends\n\t\t\t\t| { metadata: { scope: \"http\" } }\n\t\t\t\t| { metadata: { scope: \"server\" } }\n\t\t\t\t| { metadata: { SERVER_ONLY: true } }\n\t\t\t\t| { metadata: { isAction: false } }\n\t\t\t? never\n\t\t\t: T[K]\n\t\t: T[K];\n};\n\nexport type RequiredOptionKeys<\n\tC extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = (undefined extends C[\"body\"]\n\t? {}\n\t: {\n\t\t\tbody: true;\n\t\t}) &\n\t(undefined extends C[\"query\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tquery: true;\n\t\t\t}) &\n\t(undefined extends C[\"params\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tparams: true;\n\t\t\t});\n\nexport const createClient = <R extends Router | Router[\"endpoints\"]>(\n\toptions?: ClientOptions,\n) => {\n\tconst fetch = createFetch(options ?? {});\n\ttype API = InferClientRoutes<\n\t\tR extends { endpoints: Record<string, Endpoint> } ? R[\"endpoints\"] : R\n\t>;\n\ttype Options = API extends {\n\t\t[key: string]: infer T;\n\t}\n\t\t? T extends Endpoint\n\t\t\t? {\n\t\t\t\t\t[key in T[\"options\"][\"method\"] extends \"GET\"\n\t\t\t\t\t\t? T[\"path\"]\n\t\t\t\t\t\t: `@${T[\"options\"][\"method\"] extends string ? Lowercase<T[\"options\"][\"method\"]> : never}${T[\"path\"]}`]: T;\n\t\t\t\t}\n\t\t\t: {}\n\t\t: {};\n\n\ttype O = Prettify<UnionToIntersection<Options>>;\n\treturn async <\n\t\tOPT extends O,\n\t\tK extends keyof OPT,\n\t\tC extends InferContext<OPT[K]>,\n\t>(\n\t\tpath: K,\n\t\t...options: HasRequired<C> extends true\n\t\t\t? [\n\t\t\t\t\tWithRequired<\n\t\t\t\t\t\tBetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t\t\t\t\t\tkeyof RequiredOptionKeys<C>\n\t\t\t\t\t>,\n\t\t\t\t]\n\t\t\t: [BetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>?]\n\t): Promise<\n\t\tBetterFetchResponse<\n\t\t\tAwaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>\n\t\t>\n\t> => {\n\t\treturn (await fetch(path as string, {\n\t\t\t...options[0],\n\t\t})) as any;\n\t};\n};\n\nexport * from \"./error\";\n"],"mappings":";;;;;;AAuFA,MAAa,gBACZ,YACI;CACJ,MAAM,6CAAoB,WAAW,EAAE,CAAC;AAiBxC,QAAO,OAKN,MACA,GAAG,YAYC;AACJ,SAAQ,MAAM,MAAM,MAAgB,EACnC,GAAG,QAAQ,IACX,CAAC"}
1
+ {"version":3,"file":"client.cjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype BetterFetchOption,\n\ttype BetterFetchResponse,\n\tcreateFetch,\n} from \"@better-fetch/fetch\";\nimport type { Router } from \"./router\";\nimport type { HasRequiredKeys, Prettify, UnionToIntersection } from \"./helper\";\nimport type { Endpoint } from \"./endpoint\";\n\nexport type HasRequired<T extends object> = T extends {}\n\t? false\n\t: T extends {\n\t\t\t\tbody?: any;\n\t\t\t\tquery?: any;\n\t\t\t\tparams?: any;\n\t\t\t}\n\t\t? T[\"body\"] extends object\n\t\t\t? HasRequiredKeys<T[\"body\"]> extends true\n\t\t\t\t? true\n\t\t\t\t: T[\"query\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t\t: false\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t: T[\"query\"] extends object\n\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t? true\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t: false\n\t\t: false;\n\ntype InferContext<T> = T extends (ctx: infer Ctx) => any\n\t? Ctx extends object\n\t\t? Ctx\n\t\t: never\n\t: never;\n\nexport interface ClientOptions extends BetterFetchOption {\n\tbaseURL?: string;\n}\n\ntype WithRequired<T, K> = T & {\n\t[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];\n};\n\ntype InferClientRoutes<T extends Record<string, Endpoint>> = {\n\t[K in keyof T]: T[K] extends Endpoint<any, infer O>\n\t\t? O extends\n\t\t\t\t| { metadata: { scope: \"http\" } }\n\t\t\t\t| { metadata: { scope: \"server\" } }\n\t\t\t\t| { metadata: { SERVER_ONLY: true } }\n\t\t\t\t| { metadata: { isAction: false } }\n\t\t\t? never\n\t\t\t: T[K]\n\t\t: T[K];\n};\n\nexport type RequiredOptionKeys<\n\tC extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = (undefined extends C[\"body\"]\n\t? {}\n\t: {\n\t\t\tbody: true;\n\t\t}) &\n\t(undefined extends C[\"query\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tquery: true;\n\t\t\t}) &\n\t(undefined extends C[\"params\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tparams: true;\n\t\t\t});\n\nexport const createClient = <\n\tR extends Router | Router[\"endpoints\"],\n\tGlobalOpts extends ClientOptions = ClientOptions,\n>(\n\toptions?: GlobalOpts,\n) => {\n\tconst fetch = createFetch(options ?? {});\n\ttype API = InferClientRoutes<\n\t\tR extends { endpoints: Record<string, Endpoint> } ? R[\"endpoints\"] : R\n\t>;\n\ttype Options = API extends {\n\t\t[key: string]: infer T;\n\t}\n\t\t? T extends Endpoint\n\t\t\t? {\n\t\t\t\t\t[key in T[\"options\"][\"method\"] extends \"GET\"\n\t\t\t\t\t\t? T[\"path\"]\n\t\t\t\t\t\t: `@${T[\"options\"][\"method\"] extends string ? Lowercase<T[\"options\"][\"method\"]> : never}${T[\"path\"]}`]: T;\n\t\t\t\t}\n\t\t\t: {}\n\t\t: {};\n\n\ttype O = Prettify<UnionToIntersection<Options>>;\n\treturn async <\n\t\tOPT extends O,\n\t\tK extends keyof OPT,\n\t\tC extends InferContext<OPT[K]>,\n\t\tCallOpts extends BetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t>(\n\t\tpath: K,\n\t\t...callOptions: HasRequired<C> extends true\n\t\t\t? [\n\t\t\t\t\tWithRequired<\n\t\t\t\t\t\tBetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t\t\t\t\t\tkeyof RequiredOptionKeys<C>\n\t\t\t\t\t>,\n\t\t\t\t]\n\t\t\t: [CallOpts?]\n\t): Promise<\n\t\tBetterFetchResponse<\n\t\t\tAwaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>,\n\t\t\tunknown,\n\t\t\tCallOpts[\"throw\"] extends boolean\n\t\t\t\t? CallOpts[\"throw\"]\n\t\t\t\t: GlobalOpts[\"throw\"] extends true\n\t\t\t\t\t? true\n\t\t\t\t\t: false\n\t\t>\n\t> => {\n\t\treturn (await fetch(path as string, {\n\t\t\t...callOptions[0],\n\t\t})) as any;\n\t};\n};\n\nexport * from \"./error\";\n"],"mappings":";;;;;;AAuFA,MAAa,gBAIZ,YACI;CACJ,MAAM,6CAAoB,WAAW,EAAE,CAAC;AAiBxC,QAAO,OAMN,MACA,GAAG,gBAkBC;AACJ,SAAQ,MAAM,MAAM,MAAgB,EACnC,GAAG,YAAY,IACf,CAAC"}
package/dist/client.d.cts CHANGED
@@ -43,11 +43,11 @@ type RequiredOptionKeys<C extends {
43
43
  }) & (undefined extends C["params"] ? {} : {
44
44
  params: true;
45
45
  });
46
- declare const createClient: <R extends Router | Router["endpoints"]>(options?: ClientOptions) => <OPT extends (UnionToIntersection<InferClientRoutes<R extends {
46
+ declare const createClient: <R extends Router | Router["endpoints"], GlobalOpts extends ClientOptions = ClientOptions>(options?: GlobalOpts) => <OPT extends (UnionToIntersection<InferClientRoutes<R extends {
47
47
  endpoints: Record<string, Endpoint>;
48
48
  } ? R["endpoints"] : R> extends {
49
49
  [key: string]: infer T_1;
50
- } ? T_1 extends Endpoint ? { [key in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1 } : {} : {}> extends infer T ? { [K_1 in keyof T]: T[K_1] } : never), K extends keyof OPT, C extends InferContext<OPT[K]>>(path: K, ...options: HasRequired<C> extends true ? [WithRequired<BetterFetchOption<C["body"], C["query"], C["params"]>, keyof RequiredOptionKeys<C>>] : [BetterFetchOption<C["body"], C["query"], C["params"]>?]) => Promise<BetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>>>;
50
+ } ? T_1 extends Endpoint ? { [key in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1 } : {} : {}> extends infer T ? { [K_1 in keyof T]: T[K_1] } : never), K extends keyof OPT, C extends InferContext<OPT[K]>, CallOpts extends BetterFetchOption<C["body"], C["query"], C["params"]>>(path: K, ...callOptions: HasRequired<C> extends true ? [WithRequired<BetterFetchOption<C["body"], C["query"], C["params"]>, keyof RequiredOptionKeys<C>>] : [CallOpts?]) => Promise<BetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>, unknown, CallOpts["throw"] extends boolean ? CallOpts["throw"] : GlobalOpts["throw"] extends true ? true : false>>;
51
51
  //#endregion
52
52
  export { APIError, BetterCallError, ClientOptions, HasRequired, RequiredOptionKeys, Status, ValidationError, createClient, hideInternalStackFrames, kAPIErrorHeaderSymbol, makeErrorForHideStackFrame, statusCodes };
53
53
  //# sourceMappingURL=client.d.cts.map
package/dist/client.d.mts CHANGED
@@ -43,11 +43,11 @@ type RequiredOptionKeys<C extends {
43
43
  }) & (undefined extends C["params"] ? {} : {
44
44
  params: true;
45
45
  });
46
- declare const createClient: <R extends Router | Router["endpoints"]>(options?: ClientOptions) => <OPT extends (UnionToIntersection<InferClientRoutes<R extends {
46
+ declare const createClient: <R extends Router | Router["endpoints"], GlobalOpts extends ClientOptions = ClientOptions>(options?: GlobalOpts) => <OPT extends (UnionToIntersection<InferClientRoutes<R extends {
47
47
  endpoints: Record<string, Endpoint>;
48
48
  } ? R["endpoints"] : R> extends {
49
49
  [key: string]: infer T_1;
50
- } ? T_1 extends Endpoint ? { [key in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1 } : {} : {}> extends infer T ? { [K_1 in keyof T]: T[K_1] } : never), K extends keyof OPT, C extends InferContext<OPT[K]>>(path: K, ...options: HasRequired<C> extends true ? [WithRequired<BetterFetchOption<C["body"], C["query"], C["params"]>, keyof RequiredOptionKeys<C>>] : [BetterFetchOption<C["body"], C["query"], C["params"]>?]) => Promise<BetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>>>;
50
+ } ? T_1 extends Endpoint ? { [key in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1 } : {} : {}> extends infer T ? { [K_1 in keyof T]: T[K_1] } : never), K extends keyof OPT, C extends InferContext<OPT[K]>, CallOpts extends BetterFetchOption<C["body"], C["query"], C["params"]>>(path: K, ...callOptions: HasRequired<C> extends true ? [WithRequired<BetterFetchOption<C["body"], C["query"], C["params"]>, keyof RequiredOptionKeys<C>>] : [CallOpts?]) => Promise<BetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>, unknown, CallOpts["throw"] extends boolean ? CallOpts["throw"] : GlobalOpts["throw"] extends true ? true : false>>;
51
51
  //#endregion
52
52
  export { APIError, BetterCallError, ClientOptions, HasRequired, RequiredOptionKeys, Status, ValidationError, createClient, hideInternalStackFrames, kAPIErrorHeaderSymbol, makeErrorForHideStackFrame, statusCodes };
53
53
  //# sourceMappingURL=client.d.mts.map
package/dist/client.mjs CHANGED
@@ -4,8 +4,8 @@ import { createFetch } from "@better-fetch/fetch";
4
4
  //#region src/client.ts
5
5
  const createClient = (options) => {
6
6
  const fetch = createFetch(options ?? {});
7
- return async (path, ...options) => {
8
- return await fetch(path, { ...options[0] });
7
+ return async (path, ...callOptions) => {
8
+ return await fetch(path, { ...callOptions[0] });
9
9
  };
10
10
  };
11
11
 
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype BetterFetchOption,\n\ttype BetterFetchResponse,\n\tcreateFetch,\n} from \"@better-fetch/fetch\";\nimport type { Router } from \"./router\";\nimport type { HasRequiredKeys, Prettify, UnionToIntersection } from \"./helper\";\nimport type { Endpoint } from \"./endpoint\";\n\nexport type HasRequired<T extends object> = T extends {}\n\t? false\n\t: T extends {\n\t\t\t\tbody?: any;\n\t\t\t\tquery?: any;\n\t\t\t\tparams?: any;\n\t\t\t}\n\t\t? T[\"body\"] extends object\n\t\t\t? HasRequiredKeys<T[\"body\"]> extends true\n\t\t\t\t? true\n\t\t\t\t: T[\"query\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t\t: false\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t: T[\"query\"] extends object\n\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t? true\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t: false\n\t\t: false;\n\ntype InferContext<T> = T extends (ctx: infer Ctx) => any\n\t? Ctx extends object\n\t\t? Ctx\n\t\t: never\n\t: never;\n\nexport interface ClientOptions extends BetterFetchOption {\n\tbaseURL?: string;\n}\n\ntype WithRequired<T, K> = T & {\n\t[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];\n};\n\ntype InferClientRoutes<T extends Record<string, Endpoint>> = {\n\t[K in keyof T]: T[K] extends Endpoint<any, infer O>\n\t\t? O extends\n\t\t\t\t| { metadata: { scope: \"http\" } }\n\t\t\t\t| { metadata: { scope: \"server\" } }\n\t\t\t\t| { metadata: { SERVER_ONLY: true } }\n\t\t\t\t| { metadata: { isAction: false } }\n\t\t\t? never\n\t\t\t: T[K]\n\t\t: T[K];\n};\n\nexport type RequiredOptionKeys<\n\tC extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = (undefined extends C[\"body\"]\n\t? {}\n\t: {\n\t\t\tbody: true;\n\t\t}) &\n\t(undefined extends C[\"query\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tquery: true;\n\t\t\t}) &\n\t(undefined extends C[\"params\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tparams: true;\n\t\t\t});\n\nexport const createClient = <R extends Router | Router[\"endpoints\"]>(\n\toptions?: ClientOptions,\n) => {\n\tconst fetch = createFetch(options ?? {});\n\ttype API = InferClientRoutes<\n\t\tR extends { endpoints: Record<string, Endpoint> } ? R[\"endpoints\"] : R\n\t>;\n\ttype Options = API extends {\n\t\t[key: string]: infer T;\n\t}\n\t\t? T extends Endpoint\n\t\t\t? {\n\t\t\t\t\t[key in T[\"options\"][\"method\"] extends \"GET\"\n\t\t\t\t\t\t? T[\"path\"]\n\t\t\t\t\t\t: `@${T[\"options\"][\"method\"] extends string ? Lowercase<T[\"options\"][\"method\"]> : never}${T[\"path\"]}`]: T;\n\t\t\t\t}\n\t\t\t: {}\n\t\t: {};\n\n\ttype O = Prettify<UnionToIntersection<Options>>;\n\treturn async <\n\t\tOPT extends O,\n\t\tK extends keyof OPT,\n\t\tC extends InferContext<OPT[K]>,\n\t>(\n\t\tpath: K,\n\t\t...options: HasRequired<C> extends true\n\t\t\t? [\n\t\t\t\t\tWithRequired<\n\t\t\t\t\t\tBetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t\t\t\t\t\tkeyof RequiredOptionKeys<C>\n\t\t\t\t\t>,\n\t\t\t\t]\n\t\t\t: [BetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>?]\n\t): Promise<\n\t\tBetterFetchResponse<\n\t\t\tAwaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>\n\t\t>\n\t> => {\n\t\treturn (await fetch(path as string, {\n\t\t\t...options[0],\n\t\t})) as any;\n\t};\n};\n\nexport * from \"./error\";\n"],"mappings":";;;;AAuFA,MAAa,gBACZ,YACI;CACJ,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAiBxC,QAAO,OAKN,MACA,GAAG,YAYC;AACJ,SAAQ,MAAM,MAAM,MAAgB,EACnC,GAAG,QAAQ,IACX,CAAC"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype BetterFetchOption,\n\ttype BetterFetchResponse,\n\tcreateFetch,\n} from \"@better-fetch/fetch\";\nimport type { Router } from \"./router\";\nimport type { HasRequiredKeys, Prettify, UnionToIntersection } from \"./helper\";\nimport type { Endpoint } from \"./endpoint\";\n\nexport type HasRequired<T extends object> = T extends {}\n\t? false\n\t: T extends {\n\t\t\t\tbody?: any;\n\t\t\t\tquery?: any;\n\t\t\t\tparams?: any;\n\t\t\t}\n\t\t? T[\"body\"] extends object\n\t\t\t? HasRequiredKeys<T[\"body\"]> extends true\n\t\t\t\t? true\n\t\t\t\t: T[\"query\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t\t: false\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t: T[\"query\"] extends object\n\t\t\t\t? HasRequiredKeys<T[\"query\"]> extends true\n\t\t\t\t\t? true\n\t\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t\t: false\n\t\t\t\t: T[\"params\"] extends object\n\t\t\t\t\t? HasRequiredKeys<T[\"params\"]>\n\t\t\t\t\t: false\n\t\t: false;\n\ntype InferContext<T> = T extends (ctx: infer Ctx) => any\n\t? Ctx extends object\n\t\t? Ctx\n\t\t: never\n\t: never;\n\nexport interface ClientOptions extends BetterFetchOption {\n\tbaseURL?: string;\n}\n\ntype WithRequired<T, K> = T & {\n\t[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];\n};\n\ntype InferClientRoutes<T extends Record<string, Endpoint>> = {\n\t[K in keyof T]: T[K] extends Endpoint<any, infer O>\n\t\t? O extends\n\t\t\t\t| { metadata: { scope: \"http\" } }\n\t\t\t\t| { metadata: { scope: \"server\" } }\n\t\t\t\t| { metadata: { SERVER_ONLY: true } }\n\t\t\t\t| { metadata: { isAction: false } }\n\t\t\t? never\n\t\t\t: T[K]\n\t\t: T[K];\n};\n\nexport type RequiredOptionKeys<\n\tC extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = (undefined extends C[\"body\"]\n\t? {}\n\t: {\n\t\t\tbody: true;\n\t\t}) &\n\t(undefined extends C[\"query\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tquery: true;\n\t\t\t}) &\n\t(undefined extends C[\"params\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tparams: true;\n\t\t\t});\n\nexport const createClient = <\n\tR extends Router | Router[\"endpoints\"],\n\tGlobalOpts extends ClientOptions = ClientOptions,\n>(\n\toptions?: GlobalOpts,\n) => {\n\tconst fetch = createFetch(options ?? {});\n\ttype API = InferClientRoutes<\n\t\tR extends { endpoints: Record<string, Endpoint> } ? R[\"endpoints\"] : R\n\t>;\n\ttype Options = API extends {\n\t\t[key: string]: infer T;\n\t}\n\t\t? T extends Endpoint\n\t\t\t? {\n\t\t\t\t\t[key in T[\"options\"][\"method\"] extends \"GET\"\n\t\t\t\t\t\t? T[\"path\"]\n\t\t\t\t\t\t: `@${T[\"options\"][\"method\"] extends string ? Lowercase<T[\"options\"][\"method\"]> : never}${T[\"path\"]}`]: T;\n\t\t\t\t}\n\t\t\t: {}\n\t\t: {};\n\n\ttype O = Prettify<UnionToIntersection<Options>>;\n\treturn async <\n\t\tOPT extends O,\n\t\tK extends keyof OPT,\n\t\tC extends InferContext<OPT[K]>,\n\t\tCallOpts extends BetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t>(\n\t\tpath: K,\n\t\t...callOptions: HasRequired<C> extends true\n\t\t\t? [\n\t\t\t\t\tWithRequired<\n\t\t\t\t\t\tBetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t\t\t\t\t\tkeyof RequiredOptionKeys<C>\n\t\t\t\t\t>,\n\t\t\t\t]\n\t\t\t: [CallOpts?]\n\t): Promise<\n\t\tBetterFetchResponse<\n\t\t\tAwaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>,\n\t\t\tunknown,\n\t\t\tCallOpts[\"throw\"] extends boolean\n\t\t\t\t? CallOpts[\"throw\"]\n\t\t\t\t: GlobalOpts[\"throw\"] extends true\n\t\t\t\t\t? true\n\t\t\t\t\t: false\n\t\t>\n\t> => {\n\t\treturn (await fetch(path as string, {\n\t\t\t...callOptions[0],\n\t\t})) as any;\n\t};\n};\n\nexport * from \"./error\";\n"],"mappings":";;;;AAuFA,MAAa,gBAIZ,YACI;CACJ,MAAM,QAAQ,YAAY,WAAW,EAAE,CAAC;AAiBxC,QAAO,OAMN,MACA,GAAG,gBAkBC;AACJ,SAAQ,MAAM,MAAM,MAAgB,EACnC,GAAG,YAAY,IACf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"endpoint.cjs","names":["BetterCallError","tryCatch","createInternalContext","ValidationError","APIError","isAPIError","toResponse"],"sources":["../src/endpoint.ts"],"sourcesContent":["import type { HasRequiredKeys, Prettify } from \"./helper\";\nimport { toResponse } from \"./to-response\";\nimport type { Middleware } from \"./middleware\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferHeaders,\n\ttype InferMethod,\n\ttype InferParam,\n\ttype InferQuery,\n\ttype InferRequest,\n\ttype InferUse,\n\ttype InputContext,\n\ttype Method,\n} from \"./context\";\nimport type { CookieOptions, CookiePrefixOptions } from \"./cookies\";\nimport {\n\tAPIError,\n\tValidationError,\n\ttype statusCodes,\n\ttype Status,\n\tBetterCallError,\n} from \"./error\";\nimport type { OpenAPIParameter, OpenAPISchemaType } from \"./openapi\";\nimport type { StandardSchemaV1 } from \"./standard-schema\";\nimport { isAPIError, tryCatch } from \"./utils\";\n\nexport interface EndpointBaseOptions {\n\t/**\n\t * Query Schema\n\t */\n\tquery?: StandardSchemaV1;\n\t/**\n\t * Error Schema\n\t */\n\terror?: StandardSchemaV1;\n\t/**\n\t * If true headers will be required to be passed in the context\n\t */\n\trequireHeaders?: boolean;\n\t/**\n\t * If true request object will be required\n\t */\n\trequireRequest?: boolean;\n\t/**\n\t * Clone the request object from the router\n\t */\n\tcloneRequest?: boolean;\n\t/**\n\t * If true the body will be undefined\n\t */\n\tdisableBody?: boolean;\n\t/**\n\t * Endpoint metadata\n\t */\n\tmetadata?: {\n\t\t/**\n\t\t * Open API definition\n\t\t */\n\t\topenapi?: {\n\t\t\tsummary?: string;\n\t\t\tdescription?: string;\n\t\t\ttags?: string[];\n\t\t\toperationId?: string;\n\t\t\tparameters?: OpenAPIParameter[];\n\t\t\trequestBody?: {\n\t\t\t\tcontent: {\n\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tresponses?: {\n\t\t\t\t[status: string]: {\n\t\t\t\t\tdescription: string;\n\t\t\t\t\tcontent?: {\n\t\t\t\t\t\t\"application/json\"?: {\n\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/plain\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/html\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t\t/**\n\t\t * Infer body and query type from ts interface\n\t\t *\n\t\t * useful for generic and dynamic types\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tbody: z.record(z.string()),\n\t\t * \t\t$Infer: {\n\t\t * \t\t\tbody: {} as {\n\t\t * \t\t\t\ttype: InferTypeFromOptions<Option> // custom type inference\n\t\t * \t\t\t}\n\t\t * \t\t}\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\t$Infer?: {\n\t\t\t/**\n\t\t\t * Body\n\t\t\t */\n\t\t\tbody?: any;\n\t\t\t/**\n\t\t\t * Query\n\t\t\t */\n\t\t\tquery?: Record<string, any>;\n\t\t};\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed over a router\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tSERVER_ONLY?: boolean;\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed as an action to the client\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tisAction?: boolean;\n\t\t/**\n\t\t * Defines the places where the endpoint will be available\n\t\t *\n\t\t * Possible options:\n\t\t * - `rpc` - the endpoint is exposed to the router, can be invoked directly and is available to the client\n\t\t * - `server` - the endpoint is exposed to the router, can be invoked directly, but is not available to the client\n\t\t * - `http` - the endpoint is only exposed to the router\n\t\t * @default \"rpc\"\n\t\t */\n\t\tscope?: \"rpc\" | \"server\" | \"http\";\n\t\t/**\n\t\t * List of allowed media types (MIME types) for the endpoint\n\t\t *\n\t\t * if provided, only the media types in the list will be allowed to be passed in the body\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\tallowedMediaTypes?: string[];\n\t\t/**\n\t\t * Extra metadata\n\t\t */\n\t\t[key: string]: any;\n\t};\n\t/**\n\t * List of middlewares to use\n\t */\n\tuse?: Middleware[];\n\t/**\n\t * A callback to run before any API error is throw or returned\n\t *\n\t * @param e - The API error\n\t * @returns - The response to return\n\t */\n\tonAPIError?: (e: APIError) => void | Promise<void>;\n\t/**\n\t * A callback to run before a validation error is thrown\n\t * You can customize the validation error message by throwing your own APIError\n\t */\n\tonValidationError?: ({\n\t\tissues,\n\t\tmessage,\n\t}: {\n\t\tmessage: string;\n\t\tissues: readonly StandardSchemaV1.Issue[];\n\t}) => void | Promise<void>;\n}\n\nexport type EndpointBodyMethodOptions =\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod:\n\t\t\t\t| \"POST\"\n\t\t\t\t| \"PUT\"\n\t\t\t\t| \"DELETE\"\n\t\t\t\t| \"PATCH\"\n\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"*\";\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t };\n\nexport type EndpointOptions = EndpointBaseOptions & EndpointBodyMethodOptions;\n\nexport type EndpointContext<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tContext = {},\n> = {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: InferMethod<Options>;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: Path;\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: InferBody<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: InferQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*` then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: InferParam<Path>;\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 * Set the response status code\n\t */\n\tsetStatus: (status: Status) => 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 * Get a cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie\n\t */\n\tgetCookie: (key: string, prefix?: CookiePrefixOptions) => string | null;\n\t/**\n\t * Get a signed cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param secret - The secret of the signed cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie or null if the cookie is not found or false if the signature is invalid\n\t */\n\tgetSignedCookie: (\n\t\tkey: string,\n\t\tsecret: string,\n\t\tprefix?: CookiePrefixOptions,\n\t) => Promise<string | null | false>;\n\t/**\n\t * Set a cookie value in the response\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetCookie: (key: string, value: string, options?: CookieOptions) => string;\n\t/**\n\t * Set signed cookie\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param secret - The secret to sign the cookie with\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetSignedCookie: (\n\t\tkey: string,\n\t\tvalue: string,\n\t\tsecret: string,\n\t\toptions?: CookieOptions,\n\t) => Promise<string>;\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\t\tbody?: Record<string, string>;\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 & InferUse<Options[\"use\"]>>;\n\t/**\n\t * Redirect to a new URL\n\t */\n\tredirect: (url: string) => APIError;\n\t/**\n\t * Return error\n\t */\n\terror: (\n\t\tstatus: keyof typeof statusCodes | Status,\n\t\tbody?: {\n\t\t\tmessage?: string;\n\t\t\tcode?: string;\n\t\t} & Record<string, any>,\n\t\theaders?: HeadersInit,\n\t) => APIError;\n};\n\ntype ExtractBody<E extends EndpointBodyMethodOptions> = E extends {\n\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\tbody?: StandardSchemaV1<infer B>;\n}\n\t? E extends {\n\t\t\tmethod: infer M;\n\t\t\tbody?: StandardSchemaV1<B>;\n\t\t}\n\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t: never\n\t: E extends {\n\t\t\t\tmethod:\n\t\t\t\t\t| \"POST\"\n\t\t\t\t\t| \"PUT\"\n\t\t\t\t\t| \"DELETE\"\n\t\t\t\t\t| \"PATCH\"\n\t\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t}\n\t\t? E extends {\n\t\t\t\tmethod: infer M;\n\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t}\n\t\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t\t: never\n\t\t: E extends {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t\t}\n\t\t\t? {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t\t}\n\t\t\t: E extends {\n\t\t\t\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t\t\t\tbody?: never;\n\t\t\t\t\t}\n\t\t\t\t? E extends { method: infer M }\n\t\t\t\t\t? { method: M }\n\t\t\t\t\t: never\n\t\t\t\t: never;\ntype ExtractError<E extends EndpointOptions> = E extends {\n\terror?: StandardSchemaV1<infer Err>;\n}\n\t? {\n\t\t\terror: StandardSchemaV1<Err>;\n\t\t}\n\t: {};\ntype ExtractQuery<E extends EndpointOptions> = E extends {\n\tquery?: StandardSchemaV1<infer Q>;\n}\n\t? {\n\t\t\tquery: StandardSchemaV1<Q>;\n\t\t}\n\t: {};\n\ntype ExtractOthers<E extends EndpointOptions> = Pick<\n\tE,\n\tExclude<keyof E, \"method\" | \"body\" | \"query\" | \"error\">\n>;\n\n/**\n * DO NOT EXPORT THIS TYPE\n */\ntype ExtractStandSchema<E extends EndpointOptions> = ExtractOthers<E> &\n\tExtractBody<E> &\n\tExtractQuery<E> &\n\tExtractError<E>;\n\nexport type EndpointHandler<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n> = (context: EndpointContext<Path, Options>) => Promise<R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpath: Path,\n\toptions: Options,\n\thandler: EndpointHandler<Path, Options, R>,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<Options extends EndpointOptions, R>(\n\toptions: Options,\n\thandler: EndpointHandler<never, Options, R>,\n): StrictEndpoint<never, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpathOrOptions: Path | Options,\n\thandlerOrOptions: EndpointHandler<Path, Options, R> | Options,\n\thandlerOrNever?: any,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R> {\n\tconst path: string | undefined =\n\t\ttypeof pathOrOptions === \"string\" ? pathOrOptions : undefined;\n\tconst options: Options =\n\t\ttypeof handlerOrOptions === \"object\"\n\t\t\t? handlerOrOptions\n\t\t\t: (pathOrOptions as Options);\n\tconst handler: EndpointHandler<Path, Options, R> =\n\t\ttypeof handlerOrOptions === \"function\" ? handlerOrOptions : handlerOrNever;\n\n\tif ((options.method === \"GET\" || options.method === \"HEAD\") && options.body) {\n\t\tthrow new BetterCallError(\"Body is not allowed with GET or HEAD methods\");\n\t}\n\n\tif (path && /\\/{2,}/.test(path)) {\n\t\tthrow new BetterCallError(\"Path cannot contain consecutive slashes\");\n\t}\n\ttype Context = InputContext<Path, Options>;\n\n\ttype ResultType<\n\t\tAsResponse extends boolean,\n\t\tReturnHeaders extends boolean,\n\t\tReturnStatus extends boolean,\n\t> = AsResponse extends true\n\t\t? Response\n\t\t: ReturnHeaders extends true\n\t\t\t? ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t: ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: Awaited<R>;\n\n\tconst internalHandler = async <\n\t\tAsResponse extends boolean = false,\n\t\tReturnHeaders extends boolean = false,\n\t\tReturnStatus extends boolean = false,\n\t>(\n\t\t...inputCtx: HasRequiredKeys<Context> extends true\n\t\t\t? [\n\t\t\t\t\tContext & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t(Context & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t})?,\n\t\t\t\t]\n\t): Promise<ResultType<AsResponse, ReturnHeaders, ReturnStatus>> => {\n\t\tconst context = (inputCtx[0] || {}) as InputContext<any, any>;\n\t\tconst { data: internalContext, error: validationError } = await tryCatch(\n\t\t\tcreateInternalContext(context, {\n\t\t\t\toptions,\n\t\t\t\tpath,\n\t\t\t}),\n\t\t);\n\n\t\tif (validationError) {\n\t\t\t// If it's not a validation error, we throw it\n\t\t\tif (!(validationError instanceof ValidationError)) throw validationError;\n\n\t\t\t// Check if the endpoint has a custom onValidationError callback\n\t\t\tif (options.onValidationError) {\n\t\t\t\t// This can possibly throw an APIError in order to customize the validation error message\n\t\t\t\tawait options.onValidationError({\n\t\t\t\t\tmessage: validationError.message,\n\t\t\t\t\tissues: validationError.issues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new APIError(400, {\n\t\t\t\tmessage: validationError.message,\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t});\n\t\t}\n\t\tconst response = await handler(internalContext as any).catch(async (e) => {\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tconst onAPIError = options.onAPIError;\n\t\t\t\tif (onAPIError) {\n\t\t\t\t\tawait onAPIError(e);\n\t\t\t\t}\n\t\t\t\tif (context.asResponse) {\n\t\t\t\t\treturn e;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow e;\n\t\t});\n\t\tconst headers = internalContext.responseHeaders;\n\t\tconst status = internalContext.responseStatus;\n\n\t\treturn (\n\t\t\tcontext.asResponse\n\t\t\t\t? toResponse(response, {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t})\n\t\t\t\t: context.returnHeaders\n\t\t\t\t\t? context.returnStatus\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t: context.returnStatus\n\t\t\t\t\t\t? { response, status }\n\t\t\t\t\t\t: response\n\t\t) as ResultType<AsResponse, ReturnHeaders, ReturnStatus>;\n\t};\n\tinternalHandler.options = options;\n\tinternalHandler.path = path;\n\treturn internalHandler as unknown as StrictEndpoint<\n\t\tPath,\n\t\tExtractStandSchema<Options>,\n\t\tR\n\t>;\n}\n\ncreateEndpoint.create = <E extends { use?: Middleware[] }>(opts?: E) => {\n\treturn <\n\t\tPath extends string,\n\t\tOpts extends EndpointOptions,\n\t\tR extends Promise<any>,\n\t>(\n\t\tpath: Path,\n\t\toptions: Opts,\n\t\thandler: (ctx: EndpointContext<Path, Opts, InferUse<E[\"use\"]>>) => R,\n\t) => {\n\t\treturn createEndpoint(\n\t\t\tpath,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tuse: [...(options?.use || []), ...(opts?.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t};\n};\n\nexport type StrictEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR = any,\n> = {\n\t// asResponse cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { asResponse: true },\n\t): Promise<Response>;\n\n\t// returnHeaders & returnStatus cases\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ headers: Headers; status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<R>;\n\n\t// individual flag cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnHeaders: true },\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnStatus: true },\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\n\t// default case\n\t(context?: InputContext<Path, Options>): Promise<R>;\n\n\toptions: Options;\n\tpath: Path;\n};\n\nexport type Endpoint<\n\tPath extends string = string,\n\tOptions extends EndpointOptions = EndpointOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = (\n\t\tinputCtx: any,\n\t) => Promise<any>,\n> = Handler & {\n\toptions: Options;\n\tpath: Path;\n};\n"],"mappings":";;;;;;AA4fA,SAAgB,eAKf,eACA,kBACA,gBACuD;CACvD,MAAM,OACL,OAAO,kBAAkB,WAAW,gBAAgB;CACrD,MAAM,UACL,OAAO,qBAAqB,WACzB,mBACC;CACL,MAAM,UACL,OAAO,qBAAqB,aAAa,mBAAmB;AAE7D,MAAK,QAAQ,WAAW,SAAS,QAAQ,WAAW,WAAW,QAAQ,KACtE,OAAM,IAAIA,8BAAgB,+CAA+C;AAG1E,KAAI,QAAQ,SAAS,KAAK,KAAK,CAC9B,OAAM,IAAIA,8BAAgB,0CAA0C;CA4BrE,MAAM,kBAAkB,OAKvB,GAAG,aAe+D;EAClE,MAAM,UAAW,SAAS,MAAM,EAAE;EAClC,MAAM,EAAE,MAAM,iBAAiB,OAAO,oBAAoB,MAAMC,uBAC/DC,sCAAsB,SAAS;GAC9B;GACA;GACA,CAAC,CACF;AAED,MAAI,iBAAiB;AAEpB,OAAI,EAAE,2BAA2BC,+BAAkB,OAAM;AAGzD,OAAI,QAAQ,kBAEX,OAAM,QAAQ,kBAAkB;IAC/B,SAAS,gBAAgB;IACzB,QAAQ,gBAAgB;IACxB,CAAC;AAGH,SAAM,IAAIC,uBAAS,KAAK;IACvB,SAAS,gBAAgB;IACzB,MAAM;IACN,CAAC;;EAEH,MAAM,WAAW,MAAM,QAAQ,gBAAuB,CAAC,MAAM,OAAO,MAAM;AACzE,OAAIC,yBAAW,EAAE,EAAE;IAClB,MAAM,aAAa,QAAQ;AAC3B,QAAI,WACH,OAAM,WAAW,EAAE;AAEpB,QAAI,QAAQ,WACX,QAAO;;AAGT,SAAM;IACL;EACF,MAAM,UAAU,gBAAgB;EAChC,MAAM,SAAS,gBAAgB;AAE/B,SACC,QAAQ,aACLC,+BAAW,UAAU;GACrB;GACA;GACA,CAAC,GACD,QAAQ,gBACP,QAAQ,eACP;GACA;GACA;GACA;GACA,GACA;GACA;GACA;GACA,GACD,QAAQ,eACP;GAAE;GAAU;GAAQ,GACpB;;AAGP,iBAAgB,UAAU;AAC1B,iBAAgB,OAAO;AACvB,QAAO;;AAOR,eAAe,UAA4C,SAAa;AACvE,SAKC,MACA,SACA,YACI;AACJ,SAAO,eACN,MACA;GACC,GAAG;GACH,KAAK,CAAC,GAAI,SAAS,OAAO,EAAE,EAAG,GAAI,MAAM,OAAO,EAAE,CAAE;GACpD,EACD,QACA"}
1
+ {"version":3,"file":"endpoint.cjs","names":["BetterCallError","tryCatch","createInternalContext","ValidationError","APIError","isAPIError","toResponse"],"sources":["../src/endpoint.ts"],"sourcesContent":["import type { HasRequiredKeys, Prettify } from \"./helper\";\nimport { toResponse } from \"./to-response\";\nimport type { Middleware } from \"./middleware\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferHeaders,\n\ttype InferMethod,\n\ttype InferParam,\n\ttype InferQuery,\n\ttype InferRequest,\n\ttype InferUse,\n\ttype InputContext,\n\ttype Method,\n} from \"./context\";\nimport type { CookieOptions, CookiePrefixOptions } from \"./cookies\";\nimport {\n\tAPIError,\n\tValidationError,\n\ttype statusCodes,\n\ttype Status,\n\tBetterCallError,\n} from \"./error\";\nimport type { OpenAPIParameter, OpenAPISchemaType } from \"./openapi\";\nimport type { StandardSchemaV1 } from \"./standard-schema\";\nimport { isAPIError, tryCatch } from \"./utils\";\n\nexport interface EndpointBaseOptions {\n\t/**\n\t * Query Schema\n\t */\n\tquery?: StandardSchemaV1;\n\t/**\n\t * Error Schema\n\t */\n\terror?: StandardSchemaV1;\n\t/**\n\t * If true headers will be required to be passed in the context\n\t */\n\trequireHeaders?: boolean;\n\t/**\n\t * If true request object will be required\n\t */\n\trequireRequest?: boolean;\n\t/**\n\t * Clone the request object from the router\n\t */\n\tcloneRequest?: boolean;\n\t/**\n\t * If true the body will be undefined\n\t */\n\tdisableBody?: boolean;\n\t/**\n\t * Endpoint metadata\n\t */\n\tmetadata?: {\n\t\t/**\n\t\t * Open API definition\n\t\t */\n\t\topenapi?: {\n\t\t\tsummary?: string;\n\t\t\tdescription?: string;\n\t\t\ttags?: string[];\n\t\t\toperationId?: string;\n\t\t\tparameters?: OpenAPIParameter[];\n\t\t\trequestBody?: {\n\t\t\t\tcontent: {\n\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tresponses?: {\n\t\t\t\t[status: string]: {\n\t\t\t\t\tdescription: string;\n\t\t\t\t\tcontent?: {\n\t\t\t\t\t\t\"application/json\"?: {\n\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/plain\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/html\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t\t/**\n\t\t * Infer body and query type from ts interface\n\t\t *\n\t\t * useful for generic and dynamic types\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tbody: z.record(z.string()),\n\t\t * \t\t$Infer: {\n\t\t * \t\t\tbody: {} as {\n\t\t * \t\t\t\ttype: InferTypeFromOptions<Option> // custom type inference\n\t\t * \t\t\t}\n\t\t * \t\t}\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\t$Infer?: {\n\t\t\t/**\n\t\t\t * Body\n\t\t\t */\n\t\t\tbody?: any;\n\t\t\t/**\n\t\t\t * Query\n\t\t\t */\n\t\t\tquery?: Record<string, any>;\n\t\t};\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed over a router\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tSERVER_ONLY?: boolean;\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed as an action to the client\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tisAction?: boolean;\n\t\t/**\n\t\t * Defines the places where the endpoint will be available\n\t\t *\n\t\t * Possible options:\n\t\t * - `rpc` - the endpoint is exposed to the router, can be invoked directly and is available to the client\n\t\t * - `server` - the endpoint is exposed to the router, can be invoked directly, but is not available to the client\n\t\t * - `http` - the endpoint is only exposed to the router\n\t\t * @default \"rpc\"\n\t\t */\n\t\tscope?: \"rpc\" | \"server\" | \"http\";\n\t\t/**\n\t\t * List of allowed media types (MIME types) for the endpoint\n\t\t *\n\t\t * if provided, only the media types in the list will be allowed to be passed in the body\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\tallowedMediaTypes?: string[];\n\t\t/**\n\t\t * Extra metadata\n\t\t */\n\t\t[key: string]: any;\n\t};\n\t/**\n\t * List of middlewares to use\n\t */\n\tuse?: Middleware[];\n\t/**\n\t * A callback to run before any API error is throw or returned\n\t *\n\t * @param e - The API error\n\t * @returns - The response to return\n\t */\n\tonAPIError?: (e: APIError) => void | Promise<void>;\n\t/**\n\t * A callback to run before a validation error is thrown\n\t * You can customize the validation error message by throwing your own APIError\n\t */\n\tonValidationError?: ({\n\t\tissues,\n\t\tmessage,\n\t}: {\n\t\tmessage: string;\n\t\tissues: readonly StandardSchemaV1.Issue[];\n\t}) => void | Promise<void>;\n}\n\nexport type EndpointBodyMethodOptions =\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod:\n\t\t\t\t| \"POST\"\n\t\t\t\t| \"PUT\"\n\t\t\t\t| \"DELETE\"\n\t\t\t\t| \"PATCH\"\n\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"*\";\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t };\n\nexport type EndpointOptions = EndpointBaseOptions & EndpointBodyMethodOptions;\n\nexport type EndpointContext<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tContext = {},\n> = {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: InferMethod<Options>;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: Path;\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: InferBody<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: InferQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*` then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: InferParam<Path>;\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 * Set the response status code\n\t */\n\tsetStatus: (status: Status) => 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 * Get a cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie\n\t */\n\tgetCookie: (key: string, prefix?: CookiePrefixOptions) => string | null;\n\t/**\n\t * Get a signed cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param secret - The secret of the signed cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie or null if the cookie is not found or false if the signature is invalid\n\t */\n\tgetSignedCookie: (\n\t\tkey: string,\n\t\tsecret: string,\n\t\tprefix?: CookiePrefixOptions,\n\t) => Promise<string | null | false>;\n\t/**\n\t * Set a cookie value in the response\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetCookie: (key: string, value: string, options?: CookieOptions) => string;\n\t/**\n\t * Set signed cookie\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param secret - The secret to sign the cookie with\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetSignedCookie: (\n\t\tkey: string,\n\t\tvalue: string,\n\t\tsecret: string,\n\t\toptions?: CookieOptions,\n\t) => Promise<string>;\n\t/**\n\t * Response headers\n\t *\n\t * The live `Headers` for the response being built in the current\n\t * request. Read it to inspect what has already been queued, e.g. to\n\t * avoid emitting a `Set-Cookie` twice or to check headers set by an\n\t * earlier handler in the chain.\n\t *\n\t * @example\n\t * ```ts\n\t * const alreadySet = ctx.responseHeaders\n\t * .getSetCookie()\n\t * .some((c) => c.startsWith(\"session=\"));\n\t * ```\n\t */\n\tresponseHeaders: Headers;\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\t\tbody?: Record<string, string>;\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 & InferUse<Options[\"use\"]>>;\n\t/**\n\t * Redirect to a new URL\n\t */\n\tredirect: (url: string) => APIError;\n\t/**\n\t * Return error\n\t */\n\terror: (\n\t\tstatus: keyof typeof statusCodes | Status,\n\t\tbody?: {\n\t\t\tmessage?: string;\n\t\t\tcode?: string;\n\t\t} & Record<string, any>,\n\t\theaders?: HeadersInit,\n\t) => APIError;\n};\n\ntype ExtractBody<E extends EndpointBodyMethodOptions> = E extends {\n\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\tbody?: StandardSchemaV1<infer B>;\n}\n\t? E extends {\n\t\t\tmethod: infer M;\n\t\t\tbody?: StandardSchemaV1<B>;\n\t\t}\n\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t: never\n\t: E extends {\n\t\t\t\tmethod:\n\t\t\t\t\t| \"POST\"\n\t\t\t\t\t| \"PUT\"\n\t\t\t\t\t| \"DELETE\"\n\t\t\t\t\t| \"PATCH\"\n\t\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t}\n\t\t? E extends {\n\t\t\t\tmethod: infer M;\n\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t}\n\t\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t\t: never\n\t\t: E extends {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t\t}\n\t\t\t? {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t\t}\n\t\t\t: E extends {\n\t\t\t\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t\t\t\tbody?: never;\n\t\t\t\t\t}\n\t\t\t\t? E extends { method: infer M }\n\t\t\t\t\t? { method: M }\n\t\t\t\t\t: never\n\t\t\t\t: never;\ntype ExtractError<E extends EndpointOptions> = E extends {\n\terror?: StandardSchemaV1<infer Err>;\n}\n\t? {\n\t\t\terror: StandardSchemaV1<Err>;\n\t\t}\n\t: {};\ntype ExtractQuery<E extends EndpointOptions> = E extends {\n\tquery?: StandardSchemaV1<infer Q>;\n}\n\t? {\n\t\t\tquery: StandardSchemaV1<Q>;\n\t\t}\n\t: {};\n\ntype ExtractOthers<E extends EndpointOptions> = Pick<\n\tE,\n\tExclude<keyof E, \"method\" | \"body\" | \"query\" | \"error\">\n>;\n\n/**\n * DO NOT EXPORT THIS TYPE\n */\ntype ExtractStandSchema<E extends EndpointOptions> = ExtractOthers<E> &\n\tExtractBody<E> &\n\tExtractQuery<E> &\n\tExtractError<E>;\n\nexport type EndpointHandler<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n> = (context: EndpointContext<Path, Options>) => Promise<R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpath: Path,\n\toptions: Options,\n\thandler: EndpointHandler<Path, Options, R>,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<Options extends EndpointOptions, R>(\n\toptions: Options,\n\thandler: EndpointHandler<never, Options, R>,\n): StrictEndpoint<never, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpathOrOptions: Path | Options,\n\thandlerOrOptions: EndpointHandler<Path, Options, R> | Options,\n\thandlerOrNever?: any,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R> {\n\tconst path: string | undefined =\n\t\ttypeof pathOrOptions === \"string\" ? pathOrOptions : undefined;\n\tconst options: Options =\n\t\ttypeof handlerOrOptions === \"object\"\n\t\t\t? handlerOrOptions\n\t\t\t: (pathOrOptions as Options);\n\tconst handler: EndpointHandler<Path, Options, R> =\n\t\ttypeof handlerOrOptions === \"function\" ? handlerOrOptions : handlerOrNever;\n\n\tif ((options.method === \"GET\" || options.method === \"HEAD\") && options.body) {\n\t\tthrow new BetterCallError(\"Body is not allowed with GET or HEAD methods\");\n\t}\n\n\tif (path && /\\/{2,}/.test(path)) {\n\t\tthrow new BetterCallError(\"Path cannot contain consecutive slashes\");\n\t}\n\ttype Context = InputContext<Path, Options>;\n\n\ttype ResultType<\n\t\tAsResponse extends boolean,\n\t\tReturnHeaders extends boolean,\n\t\tReturnStatus extends boolean,\n\t> = AsResponse extends true\n\t\t? Response\n\t\t: ReturnHeaders extends true\n\t\t\t? ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t: ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: Awaited<R>;\n\n\tconst internalHandler = async <\n\t\tAsResponse extends boolean = false,\n\t\tReturnHeaders extends boolean = false,\n\t\tReturnStatus extends boolean = false,\n\t>(\n\t\t...inputCtx: HasRequiredKeys<Context> extends true\n\t\t\t? [\n\t\t\t\t\tContext & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t(Context & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t})?,\n\t\t\t\t]\n\t): Promise<ResultType<AsResponse, ReturnHeaders, ReturnStatus>> => {\n\t\tconst context = (inputCtx[0] || {}) as InputContext<any, any>;\n\t\tconst { data: internalContext, error: validationError } = await tryCatch(\n\t\t\tcreateInternalContext(context, {\n\t\t\t\toptions,\n\t\t\t\tpath,\n\t\t\t}),\n\t\t);\n\n\t\tif (validationError) {\n\t\t\t// If it's not a validation error, we throw it\n\t\t\tif (!(validationError instanceof ValidationError)) throw validationError;\n\n\t\t\t// Check if the endpoint has a custom onValidationError callback\n\t\t\tif (options.onValidationError) {\n\t\t\t\t// This can possibly throw an APIError in order to customize the validation error message\n\t\t\t\tawait options.onValidationError({\n\t\t\t\t\tmessage: validationError.message,\n\t\t\t\t\tissues: validationError.issues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new APIError(400, {\n\t\t\t\tmessage: validationError.message,\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t});\n\t\t}\n\t\tconst response = await handler(internalContext as any).catch(async (e) => {\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tconst onAPIError = options.onAPIError;\n\t\t\t\tif (onAPIError) {\n\t\t\t\t\tawait onAPIError(e);\n\t\t\t\t}\n\t\t\t\tif (context.asResponse) {\n\t\t\t\t\treturn e;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow e;\n\t\t});\n\t\tconst headers = internalContext.responseHeaders;\n\t\tconst status = internalContext.responseStatus;\n\n\t\treturn (\n\t\t\tcontext.asResponse\n\t\t\t\t? toResponse(response, {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t})\n\t\t\t\t: context.returnHeaders\n\t\t\t\t\t? context.returnStatus\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t: context.returnStatus\n\t\t\t\t\t\t? { response, status }\n\t\t\t\t\t\t: response\n\t\t) as ResultType<AsResponse, ReturnHeaders, ReturnStatus>;\n\t};\n\tinternalHandler.options = options;\n\tinternalHandler.path = path;\n\treturn internalHandler as unknown as StrictEndpoint<\n\t\tPath,\n\t\tExtractStandSchema<Options>,\n\t\tR\n\t>;\n}\n\ncreateEndpoint.create = <E extends { use?: Middleware[] }>(opts?: E) => {\n\treturn <\n\t\tPath extends string,\n\t\tOpts extends EndpointOptions,\n\t\tR extends Promise<any>,\n\t>(\n\t\tpath: Path,\n\t\toptions: Opts,\n\t\thandler: (ctx: EndpointContext<Path, Opts, InferUse<E[\"use\"]>>) => R,\n\t) => {\n\t\treturn createEndpoint(\n\t\t\tpath,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tuse: [...(options?.use || []), ...(opts?.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t};\n};\n\nexport type StrictEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR = any,\n> = {\n\t// asResponse cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { asResponse: true },\n\t): Promise<Response>;\n\n\t// returnHeaders & returnStatus cases\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ headers: Headers; status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<R>;\n\n\t// individual flag cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnHeaders: true },\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnStatus: true },\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\n\t// default case\n\t(context?: InputContext<Path, Options>): Promise<R>;\n\n\toptions: Options;\n\tpath: Path;\n};\n\nexport type Endpoint<\n\tPath extends string = string,\n\tOptions extends EndpointOptions = EndpointOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = (\n\t\tinputCtx: any,\n\t) => Promise<any>,\n> = Handler & {\n\toptions: Options;\n\tpath: Path;\n};\n"],"mappings":";;;;;;AA4gBA,SAAgB,eAKf,eACA,kBACA,gBACuD;CACvD,MAAM,OACL,OAAO,kBAAkB,WAAW,gBAAgB;CACrD,MAAM,UACL,OAAO,qBAAqB,WACzB,mBACC;CACL,MAAM,UACL,OAAO,qBAAqB,aAAa,mBAAmB;AAE7D,MAAK,QAAQ,WAAW,SAAS,QAAQ,WAAW,WAAW,QAAQ,KACtE,OAAM,IAAIA,8BAAgB,+CAA+C;AAG1E,KAAI,QAAQ,SAAS,KAAK,KAAK,CAC9B,OAAM,IAAIA,8BAAgB,0CAA0C;CA4BrE,MAAM,kBAAkB,OAKvB,GAAG,aAe+D;EAClE,MAAM,UAAW,SAAS,MAAM,EAAE;EAClC,MAAM,EAAE,MAAM,iBAAiB,OAAO,oBAAoB,MAAMC,uBAC/DC,sCAAsB,SAAS;GAC9B;GACA;GACA,CAAC,CACF;AAED,MAAI,iBAAiB;AAEpB,OAAI,EAAE,2BAA2BC,+BAAkB,OAAM;AAGzD,OAAI,QAAQ,kBAEX,OAAM,QAAQ,kBAAkB;IAC/B,SAAS,gBAAgB;IACzB,QAAQ,gBAAgB;IACxB,CAAC;AAGH,SAAM,IAAIC,uBAAS,KAAK;IACvB,SAAS,gBAAgB;IACzB,MAAM;IACN,CAAC;;EAEH,MAAM,WAAW,MAAM,QAAQ,gBAAuB,CAAC,MAAM,OAAO,MAAM;AACzE,OAAIC,yBAAW,EAAE,EAAE;IAClB,MAAM,aAAa,QAAQ;AAC3B,QAAI,WACH,OAAM,WAAW,EAAE;AAEpB,QAAI,QAAQ,WACX,QAAO;;AAGT,SAAM;IACL;EACF,MAAM,UAAU,gBAAgB;EAChC,MAAM,SAAS,gBAAgB;AAE/B,SACC,QAAQ,aACLC,+BAAW,UAAU;GACrB;GACA;GACA,CAAC,GACD,QAAQ,gBACP,QAAQ,eACP;GACA;GACA;GACA;GACA,GACA;GACA;GACA;GACA,GACD,QAAQ,eACP;GAAE;GAAU;GAAQ,GACpB;;AAGP,iBAAgB,UAAU;AAC1B,iBAAgB,OAAO;AACvB,QAAO;;AAOR,eAAe,UAA4C,SAAa;AACvE,SAKC,MACA,SACA,YACI;AACJ,SAAO,eACN,MACA;GACC,GAAG;GACH,KAAK,CAAC,GAAI,SAAS,OAAO,EAAE,EAAG,GAAI,MAAM,OAAO,EAAE,CAAE;GACpD,EACD,QACA"}
@@ -326,6 +326,22 @@ type EndpointContext<Path extends string, Options extends EndpointOptions, Conte
326
326
  * @returns - The cookie string
327
327
  */
328
328
  setSignedCookie: (key: string, value: string, secret: string, options?: CookieOptions) => Promise<string>;
329
+ /**
330
+ * Response headers
331
+ *
332
+ * The live `Headers` for the response being built in the current
333
+ * request. Read it to inspect what has already been queued, e.g. to
334
+ * avoid emitting a `Set-Cookie` twice or to check headers set by an
335
+ * earlier handler in the chain.
336
+ *
337
+ * @example
338
+ * ```ts
339
+ * const alreadySet = ctx.responseHeaders
340
+ * .getSetCookie()
341
+ * .some((c) => c.startsWith("session="));
342
+ * ```
343
+ */
344
+ responseHeaders: Headers;
329
345
  /**
330
346
  * JSON
331
347
  *
@@ -326,6 +326,22 @@ type EndpointContext<Path extends string, Options extends EndpointOptions, Conte
326
326
  * @returns - The cookie string
327
327
  */
328
328
  setSignedCookie: (key: string, value: string, secret: string, options?: CookieOptions) => Promise<string>;
329
+ /**
330
+ * Response headers
331
+ *
332
+ * The live `Headers` for the response being built in the current
333
+ * request. Read it to inspect what has already been queued, e.g. to
334
+ * avoid emitting a `Set-Cookie` twice or to check headers set by an
335
+ * earlier handler in the chain.
336
+ *
337
+ * @example
338
+ * ```ts
339
+ * const alreadySet = ctx.responseHeaders
340
+ * .getSetCookie()
341
+ * .some((c) => c.startsWith("session="));
342
+ * ```
343
+ */
344
+ responseHeaders: Headers;
329
345
  /**
330
346
  * JSON
331
347
  *
@@ -1 +1 @@
1
- {"version":3,"file":"endpoint.mjs","names":[],"sources":["../src/endpoint.ts"],"sourcesContent":["import type { HasRequiredKeys, Prettify } from \"./helper\";\nimport { toResponse } from \"./to-response\";\nimport type { Middleware } from \"./middleware\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferHeaders,\n\ttype InferMethod,\n\ttype InferParam,\n\ttype InferQuery,\n\ttype InferRequest,\n\ttype InferUse,\n\ttype InputContext,\n\ttype Method,\n} from \"./context\";\nimport type { CookieOptions, CookiePrefixOptions } from \"./cookies\";\nimport {\n\tAPIError,\n\tValidationError,\n\ttype statusCodes,\n\ttype Status,\n\tBetterCallError,\n} from \"./error\";\nimport type { OpenAPIParameter, OpenAPISchemaType } from \"./openapi\";\nimport type { StandardSchemaV1 } from \"./standard-schema\";\nimport { isAPIError, tryCatch } from \"./utils\";\n\nexport interface EndpointBaseOptions {\n\t/**\n\t * Query Schema\n\t */\n\tquery?: StandardSchemaV1;\n\t/**\n\t * Error Schema\n\t */\n\terror?: StandardSchemaV1;\n\t/**\n\t * If true headers will be required to be passed in the context\n\t */\n\trequireHeaders?: boolean;\n\t/**\n\t * If true request object will be required\n\t */\n\trequireRequest?: boolean;\n\t/**\n\t * Clone the request object from the router\n\t */\n\tcloneRequest?: boolean;\n\t/**\n\t * If true the body will be undefined\n\t */\n\tdisableBody?: boolean;\n\t/**\n\t * Endpoint metadata\n\t */\n\tmetadata?: {\n\t\t/**\n\t\t * Open API definition\n\t\t */\n\t\topenapi?: {\n\t\t\tsummary?: string;\n\t\t\tdescription?: string;\n\t\t\ttags?: string[];\n\t\t\toperationId?: string;\n\t\t\tparameters?: OpenAPIParameter[];\n\t\t\trequestBody?: {\n\t\t\t\tcontent: {\n\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tresponses?: {\n\t\t\t\t[status: string]: {\n\t\t\t\t\tdescription: string;\n\t\t\t\t\tcontent?: {\n\t\t\t\t\t\t\"application/json\"?: {\n\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/plain\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/html\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t\t/**\n\t\t * Infer body and query type from ts interface\n\t\t *\n\t\t * useful for generic and dynamic types\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tbody: z.record(z.string()),\n\t\t * \t\t$Infer: {\n\t\t * \t\t\tbody: {} as {\n\t\t * \t\t\t\ttype: InferTypeFromOptions<Option> // custom type inference\n\t\t * \t\t\t}\n\t\t * \t\t}\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\t$Infer?: {\n\t\t\t/**\n\t\t\t * Body\n\t\t\t */\n\t\t\tbody?: any;\n\t\t\t/**\n\t\t\t * Query\n\t\t\t */\n\t\t\tquery?: Record<string, any>;\n\t\t};\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed over a router\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tSERVER_ONLY?: boolean;\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed as an action to the client\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tisAction?: boolean;\n\t\t/**\n\t\t * Defines the places where the endpoint will be available\n\t\t *\n\t\t * Possible options:\n\t\t * - `rpc` - the endpoint is exposed to the router, can be invoked directly and is available to the client\n\t\t * - `server` - the endpoint is exposed to the router, can be invoked directly, but is not available to the client\n\t\t * - `http` - the endpoint is only exposed to the router\n\t\t * @default \"rpc\"\n\t\t */\n\t\tscope?: \"rpc\" | \"server\" | \"http\";\n\t\t/**\n\t\t * List of allowed media types (MIME types) for the endpoint\n\t\t *\n\t\t * if provided, only the media types in the list will be allowed to be passed in the body\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\tallowedMediaTypes?: string[];\n\t\t/**\n\t\t * Extra metadata\n\t\t */\n\t\t[key: string]: any;\n\t};\n\t/**\n\t * List of middlewares to use\n\t */\n\tuse?: Middleware[];\n\t/**\n\t * A callback to run before any API error is throw or returned\n\t *\n\t * @param e - The API error\n\t * @returns - The response to return\n\t */\n\tonAPIError?: (e: APIError) => void | Promise<void>;\n\t/**\n\t * A callback to run before a validation error is thrown\n\t * You can customize the validation error message by throwing your own APIError\n\t */\n\tonValidationError?: ({\n\t\tissues,\n\t\tmessage,\n\t}: {\n\t\tmessage: string;\n\t\tissues: readonly StandardSchemaV1.Issue[];\n\t}) => void | Promise<void>;\n}\n\nexport type EndpointBodyMethodOptions =\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod:\n\t\t\t\t| \"POST\"\n\t\t\t\t| \"PUT\"\n\t\t\t\t| \"DELETE\"\n\t\t\t\t| \"PATCH\"\n\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"*\";\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t };\n\nexport type EndpointOptions = EndpointBaseOptions & EndpointBodyMethodOptions;\n\nexport type EndpointContext<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tContext = {},\n> = {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: InferMethod<Options>;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: Path;\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: InferBody<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: InferQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*` then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: InferParam<Path>;\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 * Set the response status code\n\t */\n\tsetStatus: (status: Status) => 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 * Get a cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie\n\t */\n\tgetCookie: (key: string, prefix?: CookiePrefixOptions) => string | null;\n\t/**\n\t * Get a signed cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param secret - The secret of the signed cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie or null if the cookie is not found or false if the signature is invalid\n\t */\n\tgetSignedCookie: (\n\t\tkey: string,\n\t\tsecret: string,\n\t\tprefix?: CookiePrefixOptions,\n\t) => Promise<string | null | false>;\n\t/**\n\t * Set a cookie value in the response\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetCookie: (key: string, value: string, options?: CookieOptions) => string;\n\t/**\n\t * Set signed cookie\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param secret - The secret to sign the cookie with\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetSignedCookie: (\n\t\tkey: string,\n\t\tvalue: string,\n\t\tsecret: string,\n\t\toptions?: CookieOptions,\n\t) => Promise<string>;\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\t\tbody?: Record<string, string>;\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 & InferUse<Options[\"use\"]>>;\n\t/**\n\t * Redirect to a new URL\n\t */\n\tredirect: (url: string) => APIError;\n\t/**\n\t * Return error\n\t */\n\terror: (\n\t\tstatus: keyof typeof statusCodes | Status,\n\t\tbody?: {\n\t\t\tmessage?: string;\n\t\t\tcode?: string;\n\t\t} & Record<string, any>,\n\t\theaders?: HeadersInit,\n\t) => APIError;\n};\n\ntype ExtractBody<E extends EndpointBodyMethodOptions> = E extends {\n\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\tbody?: StandardSchemaV1<infer B>;\n}\n\t? E extends {\n\t\t\tmethod: infer M;\n\t\t\tbody?: StandardSchemaV1<B>;\n\t\t}\n\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t: never\n\t: E extends {\n\t\t\t\tmethod:\n\t\t\t\t\t| \"POST\"\n\t\t\t\t\t| \"PUT\"\n\t\t\t\t\t| \"DELETE\"\n\t\t\t\t\t| \"PATCH\"\n\t\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t}\n\t\t? E extends {\n\t\t\t\tmethod: infer M;\n\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t}\n\t\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t\t: never\n\t\t: E extends {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t\t}\n\t\t\t? {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t\t}\n\t\t\t: E extends {\n\t\t\t\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t\t\t\tbody?: never;\n\t\t\t\t\t}\n\t\t\t\t? E extends { method: infer M }\n\t\t\t\t\t? { method: M }\n\t\t\t\t\t: never\n\t\t\t\t: never;\ntype ExtractError<E extends EndpointOptions> = E extends {\n\terror?: StandardSchemaV1<infer Err>;\n}\n\t? {\n\t\t\terror: StandardSchemaV1<Err>;\n\t\t}\n\t: {};\ntype ExtractQuery<E extends EndpointOptions> = E extends {\n\tquery?: StandardSchemaV1<infer Q>;\n}\n\t? {\n\t\t\tquery: StandardSchemaV1<Q>;\n\t\t}\n\t: {};\n\ntype ExtractOthers<E extends EndpointOptions> = Pick<\n\tE,\n\tExclude<keyof E, \"method\" | \"body\" | \"query\" | \"error\">\n>;\n\n/**\n * DO NOT EXPORT THIS TYPE\n */\ntype ExtractStandSchema<E extends EndpointOptions> = ExtractOthers<E> &\n\tExtractBody<E> &\n\tExtractQuery<E> &\n\tExtractError<E>;\n\nexport type EndpointHandler<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n> = (context: EndpointContext<Path, Options>) => Promise<R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpath: Path,\n\toptions: Options,\n\thandler: EndpointHandler<Path, Options, R>,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<Options extends EndpointOptions, R>(\n\toptions: Options,\n\thandler: EndpointHandler<never, Options, R>,\n): StrictEndpoint<never, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpathOrOptions: Path | Options,\n\thandlerOrOptions: EndpointHandler<Path, Options, R> | Options,\n\thandlerOrNever?: any,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R> {\n\tconst path: string | undefined =\n\t\ttypeof pathOrOptions === \"string\" ? pathOrOptions : undefined;\n\tconst options: Options =\n\t\ttypeof handlerOrOptions === \"object\"\n\t\t\t? handlerOrOptions\n\t\t\t: (pathOrOptions as Options);\n\tconst handler: EndpointHandler<Path, Options, R> =\n\t\ttypeof handlerOrOptions === \"function\" ? handlerOrOptions : handlerOrNever;\n\n\tif ((options.method === \"GET\" || options.method === \"HEAD\") && options.body) {\n\t\tthrow new BetterCallError(\"Body is not allowed with GET or HEAD methods\");\n\t}\n\n\tif (path && /\\/{2,}/.test(path)) {\n\t\tthrow new BetterCallError(\"Path cannot contain consecutive slashes\");\n\t}\n\ttype Context = InputContext<Path, Options>;\n\n\ttype ResultType<\n\t\tAsResponse extends boolean,\n\t\tReturnHeaders extends boolean,\n\t\tReturnStatus extends boolean,\n\t> = AsResponse extends true\n\t\t? Response\n\t\t: ReturnHeaders extends true\n\t\t\t? ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t: ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: Awaited<R>;\n\n\tconst internalHandler = async <\n\t\tAsResponse extends boolean = false,\n\t\tReturnHeaders extends boolean = false,\n\t\tReturnStatus extends boolean = false,\n\t>(\n\t\t...inputCtx: HasRequiredKeys<Context> extends true\n\t\t\t? [\n\t\t\t\t\tContext & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t(Context & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t})?,\n\t\t\t\t]\n\t): Promise<ResultType<AsResponse, ReturnHeaders, ReturnStatus>> => {\n\t\tconst context = (inputCtx[0] || {}) as InputContext<any, any>;\n\t\tconst { data: internalContext, error: validationError } = await tryCatch(\n\t\t\tcreateInternalContext(context, {\n\t\t\t\toptions,\n\t\t\t\tpath,\n\t\t\t}),\n\t\t);\n\n\t\tif (validationError) {\n\t\t\t// If it's not a validation error, we throw it\n\t\t\tif (!(validationError instanceof ValidationError)) throw validationError;\n\n\t\t\t// Check if the endpoint has a custom onValidationError callback\n\t\t\tif (options.onValidationError) {\n\t\t\t\t// This can possibly throw an APIError in order to customize the validation error message\n\t\t\t\tawait options.onValidationError({\n\t\t\t\t\tmessage: validationError.message,\n\t\t\t\t\tissues: validationError.issues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new APIError(400, {\n\t\t\t\tmessage: validationError.message,\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t});\n\t\t}\n\t\tconst response = await handler(internalContext as any).catch(async (e) => {\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tconst onAPIError = options.onAPIError;\n\t\t\t\tif (onAPIError) {\n\t\t\t\t\tawait onAPIError(e);\n\t\t\t\t}\n\t\t\t\tif (context.asResponse) {\n\t\t\t\t\treturn e;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow e;\n\t\t});\n\t\tconst headers = internalContext.responseHeaders;\n\t\tconst status = internalContext.responseStatus;\n\n\t\treturn (\n\t\t\tcontext.asResponse\n\t\t\t\t? toResponse(response, {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t})\n\t\t\t\t: context.returnHeaders\n\t\t\t\t\t? context.returnStatus\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t: context.returnStatus\n\t\t\t\t\t\t? { response, status }\n\t\t\t\t\t\t: response\n\t\t) as ResultType<AsResponse, ReturnHeaders, ReturnStatus>;\n\t};\n\tinternalHandler.options = options;\n\tinternalHandler.path = path;\n\treturn internalHandler as unknown as StrictEndpoint<\n\t\tPath,\n\t\tExtractStandSchema<Options>,\n\t\tR\n\t>;\n}\n\ncreateEndpoint.create = <E extends { use?: Middleware[] }>(opts?: E) => {\n\treturn <\n\t\tPath extends string,\n\t\tOpts extends EndpointOptions,\n\t\tR extends Promise<any>,\n\t>(\n\t\tpath: Path,\n\t\toptions: Opts,\n\t\thandler: (ctx: EndpointContext<Path, Opts, InferUse<E[\"use\"]>>) => R,\n\t) => {\n\t\treturn createEndpoint(\n\t\t\tpath,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tuse: [...(options?.use || []), ...(opts?.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t};\n};\n\nexport type StrictEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR = any,\n> = {\n\t// asResponse cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { asResponse: true },\n\t): Promise<Response>;\n\n\t// returnHeaders & returnStatus cases\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ headers: Headers; status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<R>;\n\n\t// individual flag cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnHeaders: true },\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnStatus: true },\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\n\t// default case\n\t(context?: InputContext<Path, Options>): Promise<R>;\n\n\toptions: Options;\n\tpath: Path;\n};\n\nexport type Endpoint<\n\tPath extends string = string,\n\tOptions extends EndpointOptions = EndpointOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = (\n\t\tinputCtx: any,\n\t) => Promise<any>,\n> = Handler & {\n\toptions: Options;\n\tpath: Path;\n};\n"],"mappings":";;;;;;AA4fA,SAAgB,eAKf,eACA,kBACA,gBACuD;CACvD,MAAM,OACL,OAAO,kBAAkB,WAAW,gBAAgB;CACrD,MAAM,UACL,OAAO,qBAAqB,WACzB,mBACC;CACL,MAAM,UACL,OAAO,qBAAqB,aAAa,mBAAmB;AAE7D,MAAK,QAAQ,WAAW,SAAS,QAAQ,WAAW,WAAW,QAAQ,KACtE,OAAM,IAAI,gBAAgB,+CAA+C;AAG1E,KAAI,QAAQ,SAAS,KAAK,KAAK,CAC9B,OAAM,IAAI,gBAAgB,0CAA0C;CA4BrE,MAAM,kBAAkB,OAKvB,GAAG,aAe+D;EAClE,MAAM,UAAW,SAAS,MAAM,EAAE;EAClC,MAAM,EAAE,MAAM,iBAAiB,OAAO,oBAAoB,MAAM,SAC/D,sBAAsB,SAAS;GAC9B;GACA;GACA,CAAC,CACF;AAED,MAAI,iBAAiB;AAEpB,OAAI,EAAE,2BAA2B,iBAAkB,OAAM;AAGzD,OAAI,QAAQ,kBAEX,OAAM,QAAQ,kBAAkB;IAC/B,SAAS,gBAAgB;IACzB,QAAQ,gBAAgB;IACxB,CAAC;AAGH,SAAM,IAAI,SAAS,KAAK;IACvB,SAAS,gBAAgB;IACzB,MAAM;IACN,CAAC;;EAEH,MAAM,WAAW,MAAM,QAAQ,gBAAuB,CAAC,MAAM,OAAO,MAAM;AACzE,OAAI,WAAW,EAAE,EAAE;IAClB,MAAM,aAAa,QAAQ;AAC3B,QAAI,WACH,OAAM,WAAW,EAAE;AAEpB,QAAI,QAAQ,WACX,QAAO;;AAGT,SAAM;IACL;EACF,MAAM,UAAU,gBAAgB;EAChC,MAAM,SAAS,gBAAgB;AAE/B,SACC,QAAQ,aACL,WAAW,UAAU;GACrB;GACA;GACA,CAAC,GACD,QAAQ,gBACP,QAAQ,eACP;GACA;GACA;GACA;GACA,GACA;GACA;GACA;GACA,GACD,QAAQ,eACP;GAAE;GAAU;GAAQ,GACpB;;AAGP,iBAAgB,UAAU;AAC1B,iBAAgB,OAAO;AACvB,QAAO;;AAOR,eAAe,UAA4C,SAAa;AACvE,SAKC,MACA,SACA,YACI;AACJ,SAAO,eACN,MACA;GACC,GAAG;GACH,KAAK,CAAC,GAAI,SAAS,OAAO,EAAE,EAAG,GAAI,MAAM,OAAO,EAAE,CAAE;GACpD,EACD,QACA"}
1
+ {"version":3,"file":"endpoint.mjs","names":[],"sources":["../src/endpoint.ts"],"sourcesContent":["import type { HasRequiredKeys, Prettify } from \"./helper\";\nimport { toResponse } from \"./to-response\";\nimport type { Middleware } from \"./middleware\";\nimport {\n\tcreateInternalContext,\n\ttype InferBody,\n\ttype InferHeaders,\n\ttype InferMethod,\n\ttype InferParam,\n\ttype InferQuery,\n\ttype InferRequest,\n\ttype InferUse,\n\ttype InputContext,\n\ttype Method,\n} from \"./context\";\nimport type { CookieOptions, CookiePrefixOptions } from \"./cookies\";\nimport {\n\tAPIError,\n\tValidationError,\n\ttype statusCodes,\n\ttype Status,\n\tBetterCallError,\n} from \"./error\";\nimport type { OpenAPIParameter, OpenAPISchemaType } from \"./openapi\";\nimport type { StandardSchemaV1 } from \"./standard-schema\";\nimport { isAPIError, tryCatch } from \"./utils\";\n\nexport interface EndpointBaseOptions {\n\t/**\n\t * Query Schema\n\t */\n\tquery?: StandardSchemaV1;\n\t/**\n\t * Error Schema\n\t */\n\terror?: StandardSchemaV1;\n\t/**\n\t * If true headers will be required to be passed in the context\n\t */\n\trequireHeaders?: boolean;\n\t/**\n\t * If true request object will be required\n\t */\n\trequireRequest?: boolean;\n\t/**\n\t * Clone the request object from the router\n\t */\n\tcloneRequest?: boolean;\n\t/**\n\t * If true the body will be undefined\n\t */\n\tdisableBody?: boolean;\n\t/**\n\t * Endpoint metadata\n\t */\n\tmetadata?: {\n\t\t/**\n\t\t * Open API definition\n\t\t */\n\t\topenapi?: {\n\t\t\tsummary?: string;\n\t\t\tdescription?: string;\n\t\t\ttags?: string[];\n\t\t\toperationId?: string;\n\t\t\tparameters?: OpenAPIParameter[];\n\t\t\trequestBody?: {\n\t\t\t\tcontent: {\n\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tresponses?: {\n\t\t\t\t[status: string]: {\n\t\t\t\t\tdescription: string;\n\t\t\t\t\tcontent?: {\n\t\t\t\t\t\t\"application/json\"?: {\n\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/plain\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\"text/html\"?: {\n\t\t\t\t\t\t\tschema?: {\n\t\t\t\t\t\t\t\ttype?: OpenAPISchemaType;\n\t\t\t\t\t\t\t\tproperties?: Record<string, any>;\n\t\t\t\t\t\t\t\trequired?: string[];\n\t\t\t\t\t\t\t\t$ref?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t\t/**\n\t\t * Infer body and query type from ts interface\n\t\t *\n\t\t * useful for generic and dynamic types\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tbody: z.record(z.string()),\n\t\t * \t\t$Infer: {\n\t\t * \t\t\tbody: {} as {\n\t\t * \t\t\t\ttype: InferTypeFromOptions<Option> // custom type inference\n\t\t * \t\t\t}\n\t\t * \t\t}\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\t$Infer?: {\n\t\t\t/**\n\t\t\t * Body\n\t\t\t */\n\t\t\tbody?: any;\n\t\t\t/**\n\t\t\t * Query\n\t\t\t */\n\t\t\tquery?: Record<string, any>;\n\t\t};\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed over a router\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tSERVER_ONLY?: boolean;\n\t\t/**\n\t\t * If enabled, endpoint won't be exposed as an action to the client\n\t\t * @deprecated Use path-less endpoints instead\n\t\t */\n\t\tisAction?: boolean;\n\t\t/**\n\t\t * Defines the places where the endpoint will be available\n\t\t *\n\t\t * Possible options:\n\t\t * - `rpc` - the endpoint is exposed to the router, can be invoked directly and is available to the client\n\t\t * - `server` - the endpoint is exposed to the router, can be invoked directly, but is not available to the client\n\t\t * - `http` - the endpoint is only exposed to the router\n\t\t * @default \"rpc\"\n\t\t */\n\t\tscope?: \"rpc\" | \"server\" | \"http\";\n\t\t/**\n\t\t * List of allowed media types (MIME types) for the endpoint\n\t\t *\n\t\t * if provided, only the media types in the list will be allowed to be passed in the body\n\t\t *\n\t\t * @example\n\t\t * ```ts\n\t\t * const endpoint = createEndpoint(\"/path\", {\n\t\t * \t\tmethod: \"POST\",\n\t\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t\t * \t}, async(ctx)=>{\n\t\t * \t\tconst body = ctx.body\n\t\t * \t})\n\t\t * ```\n\t\t */\n\t\tallowedMediaTypes?: string[];\n\t\t/**\n\t\t * Extra metadata\n\t\t */\n\t\t[key: string]: any;\n\t};\n\t/**\n\t * List of middlewares to use\n\t */\n\tuse?: Middleware[];\n\t/**\n\t * A callback to run before any API error is throw or returned\n\t *\n\t * @param e - The API error\n\t * @returns - The response to return\n\t */\n\tonAPIError?: (e: APIError) => void | Promise<void>;\n\t/**\n\t * A callback to run before a validation error is thrown\n\t * You can customize the validation error message by throwing your own APIError\n\t */\n\tonValidationError?: ({\n\t\tissues,\n\t\tmessage,\n\t}: {\n\t\tmessage: string;\n\t\tissues: readonly StandardSchemaV1.Issue[];\n\t}) => void | Promise<void>;\n}\n\nexport type EndpointBodyMethodOptions =\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod:\n\t\t\t\t| \"POST\"\n\t\t\t\t| \"PUT\"\n\t\t\t\t| \"DELETE\"\n\t\t\t\t| \"PATCH\"\n\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: \"*\";\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Request Method\n\t\t\t */\n\t\t\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\t\t\t/**\n\t\t\t * Body Schema\n\t\t\t */\n\t\t\tbody?: StandardSchemaV1;\n\t };\n\nexport type EndpointOptions = EndpointBaseOptions & EndpointBodyMethodOptions;\n\nexport type EndpointContext<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tContext = {},\n> = {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: InferMethod<Options>;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: Path;\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: InferBody<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: InferQuery<Options>;\n\t/**\n\t * Params\n\t *\n\t * If the path is `/user/:id` and the request is `/user/1` then the params will\n\t * be `{ id: \"1\" }` and if the path includes a wildcard like `/user/*` then the\n\t * params will be `{ _: \"1\" }` where `_` is the wildcard key. If the wildcard\n\t * is named like `/user/**:name` then the params will be `{ name: string }`\n\t */\n\tparams: InferParam<Path>;\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 * Set the response status code\n\t */\n\tsetStatus: (status: Status) => 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 * Get a cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie\n\t */\n\tgetCookie: (key: string, prefix?: CookiePrefixOptions) => string | null;\n\t/**\n\t * Get a signed cookie value from the request\n\t *\n\t * @param key - The key of the cookie\n\t * @param secret - The secret of the signed cookie\n\t * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`\n\t * @returns - The value of the cookie or null if the cookie is not found or false if the signature is invalid\n\t */\n\tgetSignedCookie: (\n\t\tkey: string,\n\t\tsecret: string,\n\t\tprefix?: CookiePrefixOptions,\n\t) => Promise<string | null | false>;\n\t/**\n\t * Set a cookie value in the response\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetCookie: (key: string, value: string, options?: CookieOptions) => string;\n\t/**\n\t * Set signed cookie\n\t *\n\t * @param key - The key of the cookie\n\t * @param value - The value to set\n\t * @param secret - The secret to sign the cookie with\n\t * @param options - The options of the cookie\n\t * @returns - The cookie string\n\t */\n\tsetSignedCookie: (\n\t\tkey: string,\n\t\tvalue: string,\n\t\tsecret: string,\n\t\toptions?: CookieOptions,\n\t) => Promise<string>;\n\t/**\n\t * Response headers\n\t *\n\t * The live `Headers` for the response being built in the current\n\t * request. Read it to inspect what has already been queued, e.g. to\n\t * avoid emitting a `Set-Cookie` twice or to check headers set by an\n\t * earlier handler in the chain.\n\t *\n\t * @example\n\t * ```ts\n\t * const alreadySet = ctx.responseHeaders\n\t * .getSetCookie()\n\t * .some((c) => c.startsWith(\"session=\"));\n\t * ```\n\t */\n\tresponseHeaders: Headers;\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\t\tbody?: Record<string, string>;\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 & InferUse<Options[\"use\"]>>;\n\t/**\n\t * Redirect to a new URL\n\t */\n\tredirect: (url: string) => APIError;\n\t/**\n\t * Return error\n\t */\n\terror: (\n\t\tstatus: keyof typeof statusCodes | Status,\n\t\tbody?: {\n\t\t\tmessage?: string;\n\t\t\tcode?: string;\n\t\t} & Record<string, any>,\n\t\theaders?: HeadersInit,\n\t) => APIError;\n};\n\ntype ExtractBody<E extends EndpointBodyMethodOptions> = E extends {\n\tmethod: (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"GET\" | \"HEAD\")[];\n\tbody?: StandardSchemaV1<infer B>;\n}\n\t? E extends {\n\t\t\tmethod: infer M;\n\t\t\tbody?: StandardSchemaV1<B>;\n\t\t}\n\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t: never\n\t: E extends {\n\t\t\t\tmethod:\n\t\t\t\t\t| \"POST\"\n\t\t\t\t\t| \"PUT\"\n\t\t\t\t\t| \"DELETE\"\n\t\t\t\t\t| \"PATCH\"\n\t\t\t\t\t| (\"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\")[];\n\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t}\n\t\t? E extends {\n\t\t\t\tmethod: infer M;\n\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t}\n\t\t\t? { method: M; body: StandardSchemaV1<B> }\n\t\t\t: never\n\t\t: E extends {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<infer B>;\n\t\t\t\t}\n\t\t\t? {\n\t\t\t\t\tmethod: \"*\";\n\t\t\t\t\tbody?: StandardSchemaV1<B>;\n\t\t\t\t}\n\t\t\t: E extends {\n\t\t\t\t\t\tmethod: \"GET\" | \"HEAD\" | (\"GET\" | \"HEAD\")[];\n\t\t\t\t\t\tbody?: never;\n\t\t\t\t\t}\n\t\t\t\t? E extends { method: infer M }\n\t\t\t\t\t? { method: M }\n\t\t\t\t\t: never\n\t\t\t\t: never;\ntype ExtractError<E extends EndpointOptions> = E extends {\n\terror?: StandardSchemaV1<infer Err>;\n}\n\t? {\n\t\t\terror: StandardSchemaV1<Err>;\n\t\t}\n\t: {};\ntype ExtractQuery<E extends EndpointOptions> = E extends {\n\tquery?: StandardSchemaV1<infer Q>;\n}\n\t? {\n\t\t\tquery: StandardSchemaV1<Q>;\n\t\t}\n\t: {};\n\ntype ExtractOthers<E extends EndpointOptions> = Pick<\n\tE,\n\tExclude<keyof E, \"method\" | \"body\" | \"query\" | \"error\">\n>;\n\n/**\n * DO NOT EXPORT THIS TYPE\n */\ntype ExtractStandSchema<E extends EndpointOptions> = ExtractOthers<E> &\n\tExtractBody<E> &\n\tExtractQuery<E> &\n\tExtractError<E>;\n\nexport type EndpointHandler<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n> = (context: EndpointContext<Path, Options>) => Promise<R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpath: Path,\n\toptions: Options,\n\thandler: EndpointHandler<Path, Options, R>,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<Options extends EndpointOptions, R>(\n\toptions: Options,\n\thandler: EndpointHandler<never, Options, R>,\n): StrictEndpoint<never, ExtractStandSchema<Options>, R>;\n\nexport function createEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR,\n>(\n\tpathOrOptions: Path | Options,\n\thandlerOrOptions: EndpointHandler<Path, Options, R> | Options,\n\thandlerOrNever?: any,\n): StrictEndpoint<Path, ExtractStandSchema<Options>, R> {\n\tconst path: string | undefined =\n\t\ttypeof pathOrOptions === \"string\" ? pathOrOptions : undefined;\n\tconst options: Options =\n\t\ttypeof handlerOrOptions === \"object\"\n\t\t\t? handlerOrOptions\n\t\t\t: (pathOrOptions as Options);\n\tconst handler: EndpointHandler<Path, Options, R> =\n\t\ttypeof handlerOrOptions === \"function\" ? handlerOrOptions : handlerOrNever;\n\n\tif ((options.method === \"GET\" || options.method === \"HEAD\") && options.body) {\n\t\tthrow new BetterCallError(\"Body is not allowed with GET or HEAD methods\");\n\t}\n\n\tif (path && /\\/{2,}/.test(path)) {\n\t\tthrow new BetterCallError(\"Path cannot contain consecutive slashes\");\n\t}\n\ttype Context = InputContext<Path, Options>;\n\n\ttype ResultType<\n\t\tAsResponse extends boolean,\n\t\tReturnHeaders extends boolean,\n\t\tReturnStatus extends boolean,\n\t> = AsResponse extends true\n\t\t? Response\n\t\t: ReturnHeaders extends true\n\t\t\t? ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\theaders: Headers;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t: ReturnStatus extends true\n\t\t\t\t? {\n\t\t\t\t\t\tstatus: number;\n\t\t\t\t\t\tresponse: Awaited<R>;\n\t\t\t\t\t}\n\t\t\t\t: Awaited<R>;\n\n\tconst internalHandler = async <\n\t\tAsResponse extends boolean = false,\n\t\tReturnHeaders extends boolean = false,\n\t\tReturnStatus extends boolean = false,\n\t>(\n\t\t...inputCtx: HasRequiredKeys<Context> extends true\n\t\t\t? [\n\t\t\t\t\tContext & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t(Context & {\n\t\t\t\t\t\tasResponse?: AsResponse;\n\t\t\t\t\t\treturnHeaders?: ReturnHeaders;\n\t\t\t\t\t\treturnStatus?: ReturnStatus;\n\t\t\t\t\t})?,\n\t\t\t\t]\n\t): Promise<ResultType<AsResponse, ReturnHeaders, ReturnStatus>> => {\n\t\tconst context = (inputCtx[0] || {}) as InputContext<any, any>;\n\t\tconst { data: internalContext, error: validationError } = await tryCatch(\n\t\t\tcreateInternalContext(context, {\n\t\t\t\toptions,\n\t\t\t\tpath,\n\t\t\t}),\n\t\t);\n\n\t\tif (validationError) {\n\t\t\t// If it's not a validation error, we throw it\n\t\t\tif (!(validationError instanceof ValidationError)) throw validationError;\n\n\t\t\t// Check if the endpoint has a custom onValidationError callback\n\t\t\tif (options.onValidationError) {\n\t\t\t\t// This can possibly throw an APIError in order to customize the validation error message\n\t\t\t\tawait options.onValidationError({\n\t\t\t\t\tmessage: validationError.message,\n\t\t\t\t\tissues: validationError.issues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new APIError(400, {\n\t\t\t\tmessage: validationError.message,\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t});\n\t\t}\n\t\tconst response = await handler(internalContext as any).catch(async (e) => {\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tconst onAPIError = options.onAPIError;\n\t\t\t\tif (onAPIError) {\n\t\t\t\t\tawait onAPIError(e);\n\t\t\t\t}\n\t\t\t\tif (context.asResponse) {\n\t\t\t\t\treturn e;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow e;\n\t\t});\n\t\tconst headers = internalContext.responseHeaders;\n\t\tconst status = internalContext.responseStatus;\n\n\t\treturn (\n\t\t\tcontext.asResponse\n\t\t\t\t? toResponse(response, {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t})\n\t\t\t\t: context.returnHeaders\n\t\t\t\t\t? context.returnStatus\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t: context.returnStatus\n\t\t\t\t\t\t? { response, status }\n\t\t\t\t\t\t: response\n\t\t) as ResultType<AsResponse, ReturnHeaders, ReturnStatus>;\n\t};\n\tinternalHandler.options = options;\n\tinternalHandler.path = path;\n\treturn internalHandler as unknown as StrictEndpoint<\n\t\tPath,\n\t\tExtractStandSchema<Options>,\n\t\tR\n\t>;\n}\n\ncreateEndpoint.create = <E extends { use?: Middleware[] }>(opts?: E) => {\n\treturn <\n\t\tPath extends string,\n\t\tOpts extends EndpointOptions,\n\t\tR extends Promise<any>,\n\t>(\n\t\tpath: Path,\n\t\toptions: Opts,\n\t\thandler: (ctx: EndpointContext<Path, Opts, InferUse<E[\"use\"]>>) => R,\n\t) => {\n\t\treturn createEndpoint(\n\t\t\tpath,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tuse: [...(options?.use || []), ...(opts?.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t};\n};\n\nexport type StrictEndpoint<\n\tPath extends string,\n\tOptions extends EndpointOptions,\n\tR = any,\n> = {\n\t// asResponse cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { asResponse: true },\n\t): Promise<Response>;\n\n\t// returnHeaders & returnStatus cases\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ headers: Headers; status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: true;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: true;\n\t\t},\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & {\n\t\t\treturnHeaders: false;\n\t\t\treturnStatus: false;\n\t\t},\n\t): Promise<R>;\n\n\t// individual flag cases\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnHeaders: true },\n\t): Promise<{ headers: Headers; response: Awaited<R> }>;\n\t(\n\t\tcontext: InputContext<Path, Options> & { returnStatus: true },\n\t): Promise<{ status: number; response: Awaited<R> }>;\n\n\t// default case\n\t(context?: InputContext<Path, Options>): Promise<R>;\n\n\toptions: Options;\n\tpath: Path;\n};\n\nexport type Endpoint<\n\tPath extends string = string,\n\tOptions extends EndpointOptions = EndpointOptions,\n\tHandler extends (inputCtx: any) => Promise<any> = (\n\t\tinputCtx: any,\n\t) => Promise<any>,\n> = Handler & {\n\toptions: Options;\n\tpath: Path;\n};\n"],"mappings":";;;;;;AA4gBA,SAAgB,eAKf,eACA,kBACA,gBACuD;CACvD,MAAM,OACL,OAAO,kBAAkB,WAAW,gBAAgB;CACrD,MAAM,UACL,OAAO,qBAAqB,WACzB,mBACC;CACL,MAAM,UACL,OAAO,qBAAqB,aAAa,mBAAmB;AAE7D,MAAK,QAAQ,WAAW,SAAS,QAAQ,WAAW,WAAW,QAAQ,KACtE,OAAM,IAAI,gBAAgB,+CAA+C;AAG1E,KAAI,QAAQ,SAAS,KAAK,KAAK,CAC9B,OAAM,IAAI,gBAAgB,0CAA0C;CA4BrE,MAAM,kBAAkB,OAKvB,GAAG,aAe+D;EAClE,MAAM,UAAW,SAAS,MAAM,EAAE;EAClC,MAAM,EAAE,MAAM,iBAAiB,OAAO,oBAAoB,MAAM,SAC/D,sBAAsB,SAAS;GAC9B;GACA;GACA,CAAC,CACF;AAED,MAAI,iBAAiB;AAEpB,OAAI,EAAE,2BAA2B,iBAAkB,OAAM;AAGzD,OAAI,QAAQ,kBAEX,OAAM,QAAQ,kBAAkB;IAC/B,SAAS,gBAAgB;IACzB,QAAQ,gBAAgB;IACxB,CAAC;AAGH,SAAM,IAAI,SAAS,KAAK;IACvB,SAAS,gBAAgB;IACzB,MAAM;IACN,CAAC;;EAEH,MAAM,WAAW,MAAM,QAAQ,gBAAuB,CAAC,MAAM,OAAO,MAAM;AACzE,OAAI,WAAW,EAAE,EAAE;IAClB,MAAM,aAAa,QAAQ;AAC3B,QAAI,WACH,OAAM,WAAW,EAAE;AAEpB,QAAI,QAAQ,WACX,QAAO;;AAGT,SAAM;IACL;EACF,MAAM,UAAU,gBAAgB;EAChC,MAAM,SAAS,gBAAgB;AAE/B,SACC,QAAQ,aACL,WAAW,UAAU;GACrB;GACA;GACA,CAAC,GACD,QAAQ,gBACP,QAAQ,eACP;GACA;GACA;GACA;GACA,GACA;GACA;GACA;GACA,GACD,QAAQ,eACP;GAAE;GAAU;GAAQ,GACpB;;AAGP,iBAAgB,UAAU;AAC1B,iBAAgB,OAAO;AACvB,QAAO;;AAOR,eAAe,UAA4C,SAAa;AACvE,SAKC,MACA,SACA,YACI;AACJ,SAAO,eACN,MACA;GACC,GAAG;GACH,KAAK,CAAC,GAAI,SAAS,OAAO,EAAE,EAAG,GAAI,MAAM,OAAO,EAAE,CAAE;GACpD,EACD,QACA"}
@@ -11,19 +11,27 @@ function isJSONSerializable(value) {
11
11
  if (value.buffer) return false;
12
12
  return value.constructor && value.constructor.name === "Object" || typeof value.toJSON === "function";
13
13
  }
14
- function safeStringify(obj, replacer, space) {
14
+ function safeStringify(obj) {
15
+ const parents = /* @__PURE__ */ new WeakMap();
16
+ const ids = /* @__PURE__ */ new WeakMap();
15
17
  let id = 0;
16
- const seen = /* @__PURE__ */ new WeakMap();
17
- const safeReplacer = (key, value) => {
18
+ const isAncestor = (value, holder) => {
19
+ let curr = holder;
20
+ while (curr) {
21
+ if (curr === value) return true;
22
+ curr = parents.get(curr);
23
+ }
24
+ return false;
25
+ };
26
+ return JSON.stringify(obj, function(_key, value) {
18
27
  if (typeof value === "bigint") return value.toString();
19
28
  if (typeof value === "object" && value !== null) {
20
- if (seen.has(value)) return `[Circular ref-${seen.get(value)}]`;
21
- seen.set(value, id++);
29
+ if (isAncestor(value, this)) return `[Circular ref-${ids.get(value)}]`;
30
+ parents.set(value, this);
31
+ if (!ids.has(value)) ids.set(value, id++);
22
32
  }
23
- if (replacer) return replacer(key, value);
24
33
  return value;
25
- };
26
- return JSON.stringify(obj, safeReplacer, space);
34
+ });
27
35
  }
28
36
  function isJSONResponse(value) {
29
37
  if (!value || typeof value !== "object") return false;
@@ -77,14 +85,22 @@ const REQUEST_ONLY_HEADERS = new Set([
77
85
  function stripRequestOnlyHeaders(headers) {
78
86
  for (const name of REQUEST_ONLY_HEADERS) headers.delete(name);
79
87
  }
88
+ /**
89
+ * Copy headers from `source` into `target`. `Set-Cookie` is appended (one
90
+ * header per cookie) because RFC 9110 §5.3 notes it cannot be combined
91
+ * into a single comma-separated value; other headers are set (replace).
92
+ */
93
+ function copyHeaders(target, source) {
94
+ if (!source) return;
95
+ for (const [key, value] of new Headers(source).entries()) if (key.toLowerCase() === "set-cookie") target.append(key, value);
96
+ else target.set(key, value);
97
+ }
80
98
  function toResponse(data, init) {
81
99
  if (data instanceof Response) {
82
100
  if (init?.headers) {
83
101
  const safeHeaders = new Headers(init.headers);
84
102
  stripRequestOnlyHeaders(safeHeaders);
85
- safeHeaders.forEach((value, key) => {
86
- data.headers.set(key, value);
87
- });
103
+ copyHeaders(data.headers, safeHeaders);
88
104
  }
89
105
  return data;
90
106
  }
@@ -93,15 +109,12 @@ function toResponse(data, init) {
93
109
  const routerResponse = data.routerResponse;
94
110
  if (routerResponse instanceof Response) return routerResponse;
95
111
  const headers = new Headers();
96
- if (routerResponse?.headers) {
97
- const headers = new Headers(routerResponse.headers);
98
- for (const [key, value] of headers.entries()) headers.set(key, value);
99
- }
100
- if (data.headers) for (const [key, value] of new Headers(data.headers).entries()) headers.set(key, value);
112
+ copyHeaders(headers, routerResponse?.headers);
113
+ copyHeaders(headers, data.headers);
101
114
  if (init?.headers) {
102
115
  const safeHeaders = new Headers(init.headers);
103
116
  stripRequestOnlyHeaders(safeHeaders);
104
- for (const [key, value] of safeHeaders.entries()) headers.set(key, value);
117
+ copyHeaders(headers, safeHeaders);
105
118
  }
106
119
  headers.set("Content-Type", "application/json");
107
120
  return new Response(JSON.stringify(body), {
@@ -1 +1 @@
1
- {"version":3,"file":"to-response.cjs","names":["isAPIError"],"sources":["../src/to-response.ts"],"sourcesContent":["import { APIError } from \"./error\";\nimport { isAPIError } from \"./utils\";\n\nfunction isJSONSerializable(value: any) {\n\tif (value === undefined) {\n\t\treturn false;\n\t}\n\tconst t = typeof value;\n\tif (t === \"string\" || t === \"number\" || t === \"boolean\" || t === null) {\n\t\treturn true;\n\t}\n\tif (t !== \"object\") {\n\t\treturn false;\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn true;\n\t}\n\tif (value.buffer) {\n\t\treturn false;\n\t}\n\treturn (\n\t\t(value.constructor && value.constructor.name === \"Object\") ||\n\t\ttypeof value.toJSON === \"function\"\n\t);\n}\n\nfunction safeStringify(\n\tobj: any,\n\treplacer?: (key: string, value: any) => any,\n\tspace?: string | number,\n): string {\n\tlet id = 0;\n\tconst seen = new WeakMap<object, number>(); // ref -> counter\n\n\tconst safeReplacer = (key: string, value: any) => {\n\t\t// Handle bigint first\n\t\tif (typeof value === \"bigint\") {\n\t\t\treturn value.toString();\n\t\t}\n\n\t\t// Then handle circular references\n\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\tif (seen.has(value)) {\n\t\t\t\treturn `[Circular ref-${seen.get(value)}]`;\n\t\t\t}\n\t\t\tseen.set(value, id++);\n\t\t}\n\n\t\t// Finally apply any custom replacer\n\t\tif (replacer) {\n\t\t\treturn replacer(key, value);\n\t\t}\n\n\t\treturn value;\n\t};\n\n\treturn JSON.stringify(obj, safeReplacer, space);\n}\n\nexport type JSONResponse = {\n\tbody: Record<string, any>;\n\trouterResponse: ResponseInit | undefined;\n\tstatus?: number;\n\theaders?: Record<string, string> | Headers;\n\t_flag: \"json\";\n};\n\nfunction isJSONResponse(value: any): value is JSONResponse {\n\tif (!value || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn \"_flag\" in value && value._flag === \"json\";\n}\n\n/**\n * Headers that MUST be stripped when building an HTTP response from\n * arbitrary header input. These are request-only, hop-by-hop, or\n * transport-managed headers that cause protocol violations when present\n * on responses (e.g. Content-Length mismatch → net::ERR_CONTENT_LENGTH_MISMATCH).\n *\n * Sources:\n * - RFC 9110 §10.1 (Request Context Fields)\n * - RFC 9110 §7.6.1 (Connection / hop-by-hop)\n * - RFC 9110 §11.6-7 (Authentication credentials)\n * - RFC 9110 §12.5 (Content negotiation)\n * - RFC 9110 §13.1 (Conditional request headers)\n * - RFC 9110 §14.2 (Range requests)\n * - RFC 6265 §5.4 (Cookie)\n * - RFC 6454 (Origin)\n */\nconst REQUEST_ONLY_HEADERS = new Set([\n\t// Request context (RFC 9110 §10.1)\n\t\"host\", // §7.2\n\t\"user-agent\", // §10.1.5\n\t\"referer\", // §10.1.3\n\t\"from\", // §10.1.2\n\t\"expect\", // §10.1.1\n\n\t// Authentication credentials (RFC 9110 §11.6-7)\n\t\"authorization\", // §11.6.2\n\t\"proxy-authorization\", // §11.7.2\n\t\"cookie\", // RFC 6265 §5.4\n\t\"origin\", // RFC 6454\n\n\t// Content negotiation (RFC 9110 §12.5)\n\t\"accept-charset\", // §12.5.2 (deprecated)\n\t\"accept-encoding\", // §12.5.3\n\t\"accept-language\", // §12.5.4\n\n\t// Conditional requests (RFC 9110 §13.1)\n\t\"if-match\", // §13.1.1\n\t\"if-none-match\", // §13.1.2\n\t\"if-modified-since\", // §13.1.3\n\t\"if-unmodified-since\", // §13.1.4\n\t\"if-range\", // §13.1.5\n\n\t// Range requests (RFC 9110 §14.2)\n\t\"range\", // §14.2\n\n\t// Forwarding control (RFC 9110 §7.6)\n\t\"max-forwards\", // §7.6.2\n\n\t// Hop-by-hop (RFC 9110 §7.6.1)\n\t\"connection\", // §7.6.1\n\t\"keep-alive\",\n\t\"transfer-encoding\",\n\t\"te\", // §10.1.4\n\t\"upgrade\",\n\t\"trailer\",\n\t\"proxy-connection\", // non-standard\n\n\t// Valid on responses but WRONG if copied from request (RFC 9110 §8.6)\n\t\"content-length\",\n]);\n\nfunction stripRequestOnlyHeaders(headers: Headers): void {\n\tfor (const name of REQUEST_ONLY_HEADERS) {\n\t\theaders.delete(name);\n\t}\n}\n\nexport function toResponse(data?: any, init?: ResponseInit): Response {\n\tif (data instanceof Response) {\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tsafeHeaders.forEach((value, key) => {\n\t\t\t\tdata.headers.set(key, value);\n\t\t\t});\n\t\t}\n\t\treturn data;\n\t}\n\tconst isJSON = isJSONResponse(data);\n\tif (isJSON) {\n\t\tconst body = data.body;\n\t\tconst routerResponse = data.routerResponse;\n\t\tif (routerResponse instanceof Response) {\n\t\t\treturn routerResponse;\n\t\t}\n\t\tconst headers = new Headers();\n\t\tif (routerResponse?.headers) {\n\t\t\tconst headers = new Headers(routerResponse.headers);\n\t\t\tfor (const [key, value] of headers.entries()) {\n\t\t\t\theaders.set(key, value);\n\t\t\t}\n\t\t}\n\t\tif (data.headers) {\n\t\t\tfor (const [key, value] of new Headers(data.headers).entries()) {\n\t\t\t\theaders.set(key, value);\n\t\t\t}\n\t\t}\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tfor (const [key, value] of safeHeaders.entries()) {\n\t\t\t\theaders.set(key, value);\n\t\t\t}\n\t\t}\n\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t\treturn new Response(JSON.stringify(body), {\n\t\t\t...routerResponse,\n\t\t\theaders,\n\t\t\tstatus: data.status ?? init?.status ?? routerResponse?.status,\n\t\t\tstatusText: init?.statusText ?? routerResponse?.statusText,\n\t\t});\n\t}\n\tif (isAPIError(data)) {\n\t\treturn toResponse(data.body, {\n\t\t\tstatus: init?.status ?? data.statusCode,\n\t\t\tstatusText: data.status.toString(),\n\t\t\theaders: init?.headers || data.headers,\n\t\t});\n\t}\n\tlet body = data;\n\tconst headers = new Headers(init?.headers);\n\tstripRequestOnlyHeaders(headers);\n\tif (!data) {\n\t\tif (data === null) {\n\t\t\tbody = JSON.stringify(null);\n\t\t}\n\t\theaders.set(\"content-type\", \"application/json\");\n\t} else if (typeof data === \"string\") {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"text/plain\");\n\t} else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (data instanceof Blob) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", data.type || \"application/octet-stream\");\n\t} else if (data instanceof FormData) {\n\t\tbody = data;\n\t} else if (data instanceof URLSearchParams) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/x-www-form-urlencoded\");\n\t} else if (data instanceof ReadableStream) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (isJSONSerializable(data)) {\n\t\tbody = safeStringify(data);\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t}\n\n\treturn new Response(body, {\n\t\t...init,\n\t\theaders,\n\t});\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"to-response.cjs","names":["isAPIError"],"sources":["../src/to-response.ts"],"sourcesContent":["import { APIError } from \"./error\";\nimport { isAPIError } from \"./utils\";\n\nfunction isJSONSerializable(value: any) {\n\tif (value === undefined) {\n\t\treturn false;\n\t}\n\tconst t = typeof value;\n\tif (t === \"string\" || t === \"number\" || t === \"boolean\" || t === null) {\n\t\treturn true;\n\t}\n\tif (t !== \"object\") {\n\t\treturn false;\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn true;\n\t}\n\tif (value.buffer) {\n\t\treturn false;\n\t}\n\treturn (\n\t\t(value.constructor && value.constructor.name === \"Object\") ||\n\t\ttypeof value.toJSON === \"function\"\n\t);\n}\n\nfunction safeStringify(obj: unknown): string {\n\tconst parents = new WeakMap<object, object>();\n\tconst ids = new WeakMap<object, number>();\n\tlet id = 0;\n\n\tconst isAncestor = (value: object, holder: object): boolean => {\n\t\tlet curr: object | undefined = holder;\n\t\twhile (curr) {\n\t\t\tif (curr === value) return true;\n\t\t\tcurr = parents.get(curr);\n\t\t}\n\t\treturn false;\n\t};\n\n\treturn JSON.stringify(obj, function (this: any, _key, value) {\n\t\tif (typeof value === \"bigint\") return value.toString();\n\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\tif (isAncestor(value, this)) {\n\t\t\t\treturn `[Circular ref-${ids.get(value)}]`;\n\t\t\t}\n\t\t\tparents.set(value, this);\n\t\t\tif (!ids.has(value)) ids.set(value, id++);\n\t\t}\n\t\treturn value;\n\t});\n}\n\nexport type JSONResponse = {\n\tbody: Record<string, any>;\n\trouterResponse: ResponseInit | undefined;\n\tstatus?: number;\n\theaders?: Record<string, string> | Headers;\n\t_flag: \"json\";\n};\n\nfunction isJSONResponse(value: any): value is JSONResponse {\n\tif (!value || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn \"_flag\" in value && value._flag === \"json\";\n}\n\n/**\n * Headers that MUST be stripped when building an HTTP response from\n * arbitrary header input. These are request-only, hop-by-hop, or\n * transport-managed headers that cause protocol violations when present\n * on responses (e.g. Content-Length mismatch → net::ERR_CONTENT_LENGTH_MISMATCH).\n *\n * Sources:\n * - RFC 9110 §10.1 (Request Context Fields)\n * - RFC 9110 §7.6.1 (Connection / hop-by-hop)\n * - RFC 9110 §11.6-7 (Authentication credentials)\n * - RFC 9110 §12.5 (Content negotiation)\n * - RFC 9110 §13.1 (Conditional request headers)\n * - RFC 9110 §14.2 (Range requests)\n * - RFC 6265 §5.4 (Cookie)\n * - RFC 6454 (Origin)\n */\nconst REQUEST_ONLY_HEADERS = new Set([\n\t// Request context (RFC 9110 §10.1)\n\t\"host\", // §7.2\n\t\"user-agent\", // §10.1.5\n\t\"referer\", // §10.1.3\n\t\"from\", // §10.1.2\n\t\"expect\", // §10.1.1\n\n\t// Authentication credentials (RFC 9110 §11.6-7)\n\t\"authorization\", // §11.6.2\n\t\"proxy-authorization\", // §11.7.2\n\t\"cookie\", // RFC 6265 §5.4\n\t\"origin\", // RFC 6454\n\n\t// Content negotiation (RFC 9110 §12.5)\n\t\"accept-charset\", // §12.5.2 (deprecated)\n\t\"accept-encoding\", // §12.5.3\n\t\"accept-language\", // §12.5.4\n\n\t// Conditional requests (RFC 9110 §13.1)\n\t\"if-match\", // §13.1.1\n\t\"if-none-match\", // §13.1.2\n\t\"if-modified-since\", // §13.1.3\n\t\"if-unmodified-since\", // §13.1.4\n\t\"if-range\", // §13.1.5\n\n\t// Range requests (RFC 9110 §14.2)\n\t\"range\", // §14.2\n\n\t// Forwarding control (RFC 9110 §7.6)\n\t\"max-forwards\", // §7.6.2\n\n\t// Hop-by-hop (RFC 9110 §7.6.1)\n\t\"connection\", // §7.6.1\n\t\"keep-alive\",\n\t\"transfer-encoding\",\n\t\"te\", // §10.1.4\n\t\"upgrade\",\n\t\"trailer\",\n\t\"proxy-connection\", // non-standard\n\n\t// Valid on responses but WRONG if copied from request (RFC 9110 §8.6)\n\t\"content-length\",\n]);\n\nfunction stripRequestOnlyHeaders(headers: Headers): void {\n\tfor (const name of REQUEST_ONLY_HEADERS) {\n\t\theaders.delete(name);\n\t}\n}\n\n/**\n * Copy headers from `source` into `target`. `Set-Cookie` is appended (one\n * header per cookie) because RFC 9110 §5.3 notes it cannot be combined\n * into a single comma-separated value; other headers are set (replace).\n */\nfunction copyHeaders(target: Headers, source: HeadersInit | undefined): void {\n\tif (!source) return;\n\tfor (const [key, value] of new Headers(source).entries()) {\n\t\tif (key.toLowerCase() === \"set-cookie\") {\n\t\t\ttarget.append(key, value);\n\t\t} else {\n\t\t\ttarget.set(key, value);\n\t\t}\n\t}\n}\n\nexport function toResponse(data?: any, init?: ResponseInit): Response {\n\tif (data instanceof Response) {\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tcopyHeaders(data.headers, safeHeaders);\n\t\t}\n\t\treturn data;\n\t}\n\tconst isJSON = isJSONResponse(data);\n\tif (isJSON) {\n\t\tconst body = data.body;\n\t\tconst routerResponse = data.routerResponse;\n\t\tif (routerResponse instanceof Response) {\n\t\t\treturn routerResponse;\n\t\t}\n\t\tconst headers = new Headers();\n\t\tcopyHeaders(headers, routerResponse?.headers);\n\t\tcopyHeaders(headers, data.headers);\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tcopyHeaders(headers, safeHeaders);\n\t\t}\n\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t\treturn new Response(JSON.stringify(body), {\n\t\t\t...routerResponse,\n\t\t\theaders,\n\t\t\tstatus: data.status ?? init?.status ?? routerResponse?.status,\n\t\t\tstatusText: init?.statusText ?? routerResponse?.statusText,\n\t\t});\n\t}\n\tif (isAPIError(data)) {\n\t\treturn toResponse(data.body, {\n\t\t\tstatus: init?.status ?? data.statusCode,\n\t\t\tstatusText: data.status.toString(),\n\t\t\theaders: init?.headers || data.headers,\n\t\t});\n\t}\n\tlet body = data;\n\tconst headers = new Headers(init?.headers);\n\tstripRequestOnlyHeaders(headers);\n\tif (!data) {\n\t\tif (data === null) {\n\t\t\tbody = JSON.stringify(null);\n\t\t}\n\t\theaders.set(\"content-type\", \"application/json\");\n\t} else if (typeof data === \"string\") {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"text/plain\");\n\t} else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (data instanceof Blob) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", data.type || \"application/octet-stream\");\n\t} else if (data instanceof FormData) {\n\t\tbody = data;\n\t} else if (data instanceof URLSearchParams) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/x-www-form-urlencoded\");\n\t} else if (data instanceof ReadableStream) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (isJSONSerializable(data)) {\n\t\tbody = safeStringify(data);\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t}\n\n\treturn new Response(body, {\n\t\t...init,\n\t\theaders,\n\t});\n}\n"],"mappings":"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"}
@@ -11,19 +11,27 @@ function isJSONSerializable(value) {
11
11
  if (value.buffer) return false;
12
12
  return value.constructor && value.constructor.name === "Object" || typeof value.toJSON === "function";
13
13
  }
14
- function safeStringify(obj, replacer, space) {
14
+ function safeStringify(obj) {
15
+ const parents = /* @__PURE__ */ new WeakMap();
16
+ const ids = /* @__PURE__ */ new WeakMap();
15
17
  let id = 0;
16
- const seen = /* @__PURE__ */ new WeakMap();
17
- const safeReplacer = (key, value) => {
18
+ const isAncestor = (value, holder) => {
19
+ let curr = holder;
20
+ while (curr) {
21
+ if (curr === value) return true;
22
+ curr = parents.get(curr);
23
+ }
24
+ return false;
25
+ };
26
+ return JSON.stringify(obj, function(_key, value) {
18
27
  if (typeof value === "bigint") return value.toString();
19
28
  if (typeof value === "object" && value !== null) {
20
- if (seen.has(value)) return `[Circular ref-${seen.get(value)}]`;
21
- seen.set(value, id++);
29
+ if (isAncestor(value, this)) return `[Circular ref-${ids.get(value)}]`;
30
+ parents.set(value, this);
31
+ if (!ids.has(value)) ids.set(value, id++);
22
32
  }
23
- if (replacer) return replacer(key, value);
24
33
  return value;
25
- };
26
- return JSON.stringify(obj, safeReplacer, space);
34
+ });
27
35
  }
28
36
  function isJSONResponse(value) {
29
37
  if (!value || typeof value !== "object") return false;
@@ -77,14 +85,22 @@ const REQUEST_ONLY_HEADERS = new Set([
77
85
  function stripRequestOnlyHeaders(headers) {
78
86
  for (const name of REQUEST_ONLY_HEADERS) headers.delete(name);
79
87
  }
88
+ /**
89
+ * Copy headers from `source` into `target`. `Set-Cookie` is appended (one
90
+ * header per cookie) because RFC 9110 §5.3 notes it cannot be combined
91
+ * into a single comma-separated value; other headers are set (replace).
92
+ */
93
+ function copyHeaders(target, source) {
94
+ if (!source) return;
95
+ for (const [key, value] of new Headers(source).entries()) if (key.toLowerCase() === "set-cookie") target.append(key, value);
96
+ else target.set(key, value);
97
+ }
80
98
  function toResponse(data, init) {
81
99
  if (data instanceof Response) {
82
100
  if (init?.headers) {
83
101
  const safeHeaders = new Headers(init.headers);
84
102
  stripRequestOnlyHeaders(safeHeaders);
85
- safeHeaders.forEach((value, key) => {
86
- data.headers.set(key, value);
87
- });
103
+ copyHeaders(data.headers, safeHeaders);
88
104
  }
89
105
  return data;
90
106
  }
@@ -93,15 +109,12 @@ function toResponse(data, init) {
93
109
  const routerResponse = data.routerResponse;
94
110
  if (routerResponse instanceof Response) return routerResponse;
95
111
  const headers = new Headers();
96
- if (routerResponse?.headers) {
97
- const headers = new Headers(routerResponse.headers);
98
- for (const [key, value] of headers.entries()) headers.set(key, value);
99
- }
100
- if (data.headers) for (const [key, value] of new Headers(data.headers).entries()) headers.set(key, value);
112
+ copyHeaders(headers, routerResponse?.headers);
113
+ copyHeaders(headers, data.headers);
101
114
  if (init?.headers) {
102
115
  const safeHeaders = new Headers(init.headers);
103
116
  stripRequestOnlyHeaders(safeHeaders);
104
- for (const [key, value] of safeHeaders.entries()) headers.set(key, value);
117
+ copyHeaders(headers, safeHeaders);
105
118
  }
106
119
  headers.set("Content-Type", "application/json");
107
120
  return new Response(JSON.stringify(body), {
@@ -1 +1 @@
1
- {"version":3,"file":"to-response.mjs","names":[],"sources":["../src/to-response.ts"],"sourcesContent":["import { APIError } from \"./error\";\nimport { isAPIError } from \"./utils\";\n\nfunction isJSONSerializable(value: any) {\n\tif (value === undefined) {\n\t\treturn false;\n\t}\n\tconst t = typeof value;\n\tif (t === \"string\" || t === \"number\" || t === \"boolean\" || t === null) {\n\t\treturn true;\n\t}\n\tif (t !== \"object\") {\n\t\treturn false;\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn true;\n\t}\n\tif (value.buffer) {\n\t\treturn false;\n\t}\n\treturn (\n\t\t(value.constructor && value.constructor.name === \"Object\") ||\n\t\ttypeof value.toJSON === \"function\"\n\t);\n}\n\nfunction safeStringify(\n\tobj: any,\n\treplacer?: (key: string, value: any) => any,\n\tspace?: string | number,\n): string {\n\tlet id = 0;\n\tconst seen = new WeakMap<object, number>(); // ref -> counter\n\n\tconst safeReplacer = (key: string, value: any) => {\n\t\t// Handle bigint first\n\t\tif (typeof value === \"bigint\") {\n\t\t\treturn value.toString();\n\t\t}\n\n\t\t// Then handle circular references\n\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\tif (seen.has(value)) {\n\t\t\t\treturn `[Circular ref-${seen.get(value)}]`;\n\t\t\t}\n\t\t\tseen.set(value, id++);\n\t\t}\n\n\t\t// Finally apply any custom replacer\n\t\tif (replacer) {\n\t\t\treturn replacer(key, value);\n\t\t}\n\n\t\treturn value;\n\t};\n\n\treturn JSON.stringify(obj, safeReplacer, space);\n}\n\nexport type JSONResponse = {\n\tbody: Record<string, any>;\n\trouterResponse: ResponseInit | undefined;\n\tstatus?: number;\n\theaders?: Record<string, string> | Headers;\n\t_flag: \"json\";\n};\n\nfunction isJSONResponse(value: any): value is JSONResponse {\n\tif (!value || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn \"_flag\" in value && value._flag === \"json\";\n}\n\n/**\n * Headers that MUST be stripped when building an HTTP response from\n * arbitrary header input. These are request-only, hop-by-hop, or\n * transport-managed headers that cause protocol violations when present\n * on responses (e.g. Content-Length mismatch → net::ERR_CONTENT_LENGTH_MISMATCH).\n *\n * Sources:\n * - RFC 9110 §10.1 (Request Context Fields)\n * - RFC 9110 §7.6.1 (Connection / hop-by-hop)\n * - RFC 9110 §11.6-7 (Authentication credentials)\n * - RFC 9110 §12.5 (Content negotiation)\n * - RFC 9110 §13.1 (Conditional request headers)\n * - RFC 9110 §14.2 (Range requests)\n * - RFC 6265 §5.4 (Cookie)\n * - RFC 6454 (Origin)\n */\nconst REQUEST_ONLY_HEADERS = new Set([\n\t// Request context (RFC 9110 §10.1)\n\t\"host\", // §7.2\n\t\"user-agent\", // §10.1.5\n\t\"referer\", // §10.1.3\n\t\"from\", // §10.1.2\n\t\"expect\", // §10.1.1\n\n\t// Authentication credentials (RFC 9110 §11.6-7)\n\t\"authorization\", // §11.6.2\n\t\"proxy-authorization\", // §11.7.2\n\t\"cookie\", // RFC 6265 §5.4\n\t\"origin\", // RFC 6454\n\n\t// Content negotiation (RFC 9110 §12.5)\n\t\"accept-charset\", // §12.5.2 (deprecated)\n\t\"accept-encoding\", // §12.5.3\n\t\"accept-language\", // §12.5.4\n\n\t// Conditional requests (RFC 9110 §13.1)\n\t\"if-match\", // §13.1.1\n\t\"if-none-match\", // §13.1.2\n\t\"if-modified-since\", // §13.1.3\n\t\"if-unmodified-since\", // §13.1.4\n\t\"if-range\", // §13.1.5\n\n\t// Range requests (RFC 9110 §14.2)\n\t\"range\", // §14.2\n\n\t// Forwarding control (RFC 9110 §7.6)\n\t\"max-forwards\", // §7.6.2\n\n\t// Hop-by-hop (RFC 9110 §7.6.1)\n\t\"connection\", // §7.6.1\n\t\"keep-alive\",\n\t\"transfer-encoding\",\n\t\"te\", // §10.1.4\n\t\"upgrade\",\n\t\"trailer\",\n\t\"proxy-connection\", // non-standard\n\n\t// Valid on responses but WRONG if copied from request (RFC 9110 §8.6)\n\t\"content-length\",\n]);\n\nfunction stripRequestOnlyHeaders(headers: Headers): void {\n\tfor (const name of REQUEST_ONLY_HEADERS) {\n\t\theaders.delete(name);\n\t}\n}\n\nexport function toResponse(data?: any, init?: ResponseInit): Response {\n\tif (data instanceof Response) {\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tsafeHeaders.forEach((value, key) => {\n\t\t\t\tdata.headers.set(key, value);\n\t\t\t});\n\t\t}\n\t\treturn data;\n\t}\n\tconst isJSON = isJSONResponse(data);\n\tif (isJSON) {\n\t\tconst body = data.body;\n\t\tconst routerResponse = data.routerResponse;\n\t\tif (routerResponse instanceof Response) {\n\t\t\treturn routerResponse;\n\t\t}\n\t\tconst headers = new Headers();\n\t\tif (routerResponse?.headers) {\n\t\t\tconst headers = new Headers(routerResponse.headers);\n\t\t\tfor (const [key, value] of headers.entries()) {\n\t\t\t\theaders.set(key, value);\n\t\t\t}\n\t\t}\n\t\tif (data.headers) {\n\t\t\tfor (const [key, value] of new Headers(data.headers).entries()) {\n\t\t\t\theaders.set(key, value);\n\t\t\t}\n\t\t}\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tfor (const [key, value] of safeHeaders.entries()) {\n\t\t\t\theaders.set(key, value);\n\t\t\t}\n\t\t}\n\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t\treturn new Response(JSON.stringify(body), {\n\t\t\t...routerResponse,\n\t\t\theaders,\n\t\t\tstatus: data.status ?? init?.status ?? routerResponse?.status,\n\t\t\tstatusText: init?.statusText ?? routerResponse?.statusText,\n\t\t});\n\t}\n\tif (isAPIError(data)) {\n\t\treturn toResponse(data.body, {\n\t\t\tstatus: init?.status ?? data.statusCode,\n\t\t\tstatusText: data.status.toString(),\n\t\t\theaders: init?.headers || data.headers,\n\t\t});\n\t}\n\tlet body = data;\n\tconst headers = new Headers(init?.headers);\n\tstripRequestOnlyHeaders(headers);\n\tif (!data) {\n\t\tif (data === null) {\n\t\t\tbody = JSON.stringify(null);\n\t\t}\n\t\theaders.set(\"content-type\", \"application/json\");\n\t} else if (typeof data === \"string\") {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"text/plain\");\n\t} else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (data instanceof Blob) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", data.type || \"application/octet-stream\");\n\t} else if (data instanceof FormData) {\n\t\tbody = data;\n\t} else if (data instanceof URLSearchParams) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/x-www-form-urlencoded\");\n\t} else if (data instanceof ReadableStream) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (isJSONSerializable(data)) {\n\t\tbody = safeStringify(data);\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t}\n\n\treturn new Response(body, {\n\t\t...init,\n\t\theaders,\n\t});\n}\n"],"mappings":";;;;AAGA,SAAS,mBAAmB,OAAY;AACvC,KAAI,UAAU,OACb,QAAO;CAER,MAAM,IAAI,OAAO;AACjB,KAAI,MAAM,YAAY,MAAM,YAAY,MAAM,aAAa,MAAM,KAChE,QAAO;AAER,KAAI,MAAM,SACT,QAAO;AAER,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO;AAER,KAAI,MAAM,OACT,QAAO;AAER,QACE,MAAM,eAAe,MAAM,YAAY,SAAS,YACjD,OAAO,MAAM,WAAW;;AAI1B,SAAS,cACR,KACA,UACA,OACS;CACT,IAAI,KAAK;CACT,MAAM,uBAAO,IAAI,SAAyB;CAE1C,MAAM,gBAAgB,KAAa,UAAe;AAEjD,MAAI,OAAO,UAAU,SACpB,QAAO,MAAM,UAAU;AAIxB,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAChD,OAAI,KAAK,IAAI,MAAM,CAClB,QAAO,iBAAiB,KAAK,IAAI,MAAM,CAAC;AAEzC,QAAK,IAAI,OAAO,KAAK;;AAItB,MAAI,SACH,QAAO,SAAS,KAAK,MAAM;AAG5B,SAAO;;AAGR,QAAO,KAAK,UAAU,KAAK,cAAc,MAAM;;AAWhD,SAAS,eAAe,OAAmC;AAC1D,KAAI,CAAC,SAAS,OAAO,UAAU,SAC9B,QAAO;AAER,QAAO,WAAW,SAAS,MAAM,UAAU;;;;;;;;;;;;;;;;;;AAmB5C,MAAM,uBAAuB,IAAI,IAAI;CAEpC;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CAGA;CAGA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA,CAAC;AAEF,SAAS,wBAAwB,SAAwB;AACxD,MAAK,MAAM,QAAQ,qBAClB,SAAQ,OAAO,KAAK;;AAItB,SAAgB,WAAW,MAAY,MAA+B;AACrE,KAAI,gBAAgB,UAAU;AAC7B,MAAI,MAAM,SAAS;GAClB,MAAM,cAAc,IAAI,QAAQ,KAAK,QAAQ;AAC7C,2BAAwB,YAAY;AACpC,eAAY,SAAS,OAAO,QAAQ;AACnC,SAAK,QAAQ,IAAI,KAAK,MAAM;KAC3B;;AAEH,SAAO;;AAGR,KADe,eAAe,KAAK,EACvB;EACX,MAAM,OAAO,KAAK;EAClB,MAAM,iBAAiB,KAAK;AAC5B,MAAI,0BAA0B,SAC7B,QAAO;EAER,MAAM,UAAU,IAAI,SAAS;AAC7B,MAAI,gBAAgB,SAAS;GAC5B,MAAM,UAAU,IAAI,QAAQ,eAAe,QAAQ;AACnD,QAAK,MAAM,CAAC,KAAK,UAAU,QAAQ,SAAS,CAC3C,SAAQ,IAAI,KAAK,MAAM;;AAGzB,MAAI,KAAK,QACR,MAAK,MAAM,CAAC,KAAK,UAAU,IAAI,QAAQ,KAAK,QAAQ,CAAC,SAAS,CAC7D,SAAQ,IAAI,KAAK,MAAM;AAGzB,MAAI,MAAM,SAAS;GAClB,MAAM,cAAc,IAAI,QAAQ,KAAK,QAAQ;AAC7C,2BAAwB,YAAY;AACpC,QAAK,MAAM,CAAC,KAAK,UAAU,YAAY,SAAS,CAC/C,SAAQ,IAAI,KAAK,MAAM;;AAIzB,UAAQ,IAAI,gBAAgB,mBAAmB;AAC/C,SAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE;GACzC,GAAG;GACH;GACA,QAAQ,KAAK,UAAU,MAAM,UAAU,gBAAgB;GACvD,YAAY,MAAM,cAAc,gBAAgB;GAChD,CAAC;;AAEH,KAAI,WAAW,KAAK,CACnB,QAAO,WAAW,KAAK,MAAM;EAC5B,QAAQ,MAAM,UAAU,KAAK;EAC7B,YAAY,KAAK,OAAO,UAAU;EAClC,SAAS,MAAM,WAAW,KAAK;EAC/B,CAAC;CAEH,IAAI,OAAO;CACX,MAAM,UAAU,IAAI,QAAQ,MAAM,QAAQ;AAC1C,yBAAwB,QAAQ;AAChC,KAAI,CAAC,MAAM;AACV,MAAI,SAAS,KACZ,QAAO,KAAK,UAAU,KAAK;AAE5B,UAAQ,IAAI,gBAAgB,mBAAmB;YACrC,OAAO,SAAS,UAAU;AACpC,SAAO;AACP,UAAQ,IAAI,gBAAgB,aAAa;YAC/B,gBAAgB,eAAe,YAAY,OAAO,KAAK,EAAE;AACnE,SAAO;AACP,UAAQ,IAAI,gBAAgB,2BAA2B;YAC7C,gBAAgB,MAAM;AAChC,SAAO;AACP,UAAQ,IAAI,gBAAgB,KAAK,QAAQ,2BAA2B;YAC1D,gBAAgB,SAC1B,QAAO;UACG,gBAAgB,iBAAiB;AAC3C,SAAO;AACP,UAAQ,IAAI,gBAAgB,oCAAoC;YACtD,gBAAgB,gBAAgB;AAC1C,SAAO;AACP,UAAQ,IAAI,gBAAgB,2BAA2B;YAC7C,mBAAmB,KAAK,EAAE;AACpC,SAAO,cAAc,KAAK;AAC1B,UAAQ,IAAI,gBAAgB,mBAAmB;;AAGhD,QAAO,IAAI,SAAS,MAAM;EACzB,GAAG;EACH;EACA,CAAC"}
1
+ {"version":3,"file":"to-response.mjs","names":[],"sources":["../src/to-response.ts"],"sourcesContent":["import { APIError } from \"./error\";\nimport { isAPIError } from \"./utils\";\n\nfunction isJSONSerializable(value: any) {\n\tif (value === undefined) {\n\t\treturn false;\n\t}\n\tconst t = typeof value;\n\tif (t === \"string\" || t === \"number\" || t === \"boolean\" || t === null) {\n\t\treturn true;\n\t}\n\tif (t !== \"object\") {\n\t\treturn false;\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn true;\n\t}\n\tif (value.buffer) {\n\t\treturn false;\n\t}\n\treturn (\n\t\t(value.constructor && value.constructor.name === \"Object\") ||\n\t\ttypeof value.toJSON === \"function\"\n\t);\n}\n\nfunction safeStringify(obj: unknown): string {\n\tconst parents = new WeakMap<object, object>();\n\tconst ids = new WeakMap<object, number>();\n\tlet id = 0;\n\n\tconst isAncestor = (value: object, holder: object): boolean => {\n\t\tlet curr: object | undefined = holder;\n\t\twhile (curr) {\n\t\t\tif (curr === value) return true;\n\t\t\tcurr = parents.get(curr);\n\t\t}\n\t\treturn false;\n\t};\n\n\treturn JSON.stringify(obj, function (this: any, _key, value) {\n\t\tif (typeof value === \"bigint\") return value.toString();\n\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\tif (isAncestor(value, this)) {\n\t\t\t\treturn `[Circular ref-${ids.get(value)}]`;\n\t\t\t}\n\t\t\tparents.set(value, this);\n\t\t\tif (!ids.has(value)) ids.set(value, id++);\n\t\t}\n\t\treturn value;\n\t});\n}\n\nexport type JSONResponse = {\n\tbody: Record<string, any>;\n\trouterResponse: ResponseInit | undefined;\n\tstatus?: number;\n\theaders?: Record<string, string> | Headers;\n\t_flag: \"json\";\n};\n\nfunction isJSONResponse(value: any): value is JSONResponse {\n\tif (!value || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn \"_flag\" in value && value._flag === \"json\";\n}\n\n/**\n * Headers that MUST be stripped when building an HTTP response from\n * arbitrary header input. These are request-only, hop-by-hop, or\n * transport-managed headers that cause protocol violations when present\n * on responses (e.g. Content-Length mismatch → net::ERR_CONTENT_LENGTH_MISMATCH).\n *\n * Sources:\n * - RFC 9110 §10.1 (Request Context Fields)\n * - RFC 9110 §7.6.1 (Connection / hop-by-hop)\n * - RFC 9110 §11.6-7 (Authentication credentials)\n * - RFC 9110 §12.5 (Content negotiation)\n * - RFC 9110 §13.1 (Conditional request headers)\n * - RFC 9110 §14.2 (Range requests)\n * - RFC 6265 §5.4 (Cookie)\n * - RFC 6454 (Origin)\n */\nconst REQUEST_ONLY_HEADERS = new Set([\n\t// Request context (RFC 9110 §10.1)\n\t\"host\", // §7.2\n\t\"user-agent\", // §10.1.5\n\t\"referer\", // §10.1.3\n\t\"from\", // §10.1.2\n\t\"expect\", // §10.1.1\n\n\t// Authentication credentials (RFC 9110 §11.6-7)\n\t\"authorization\", // §11.6.2\n\t\"proxy-authorization\", // §11.7.2\n\t\"cookie\", // RFC 6265 §5.4\n\t\"origin\", // RFC 6454\n\n\t// Content negotiation (RFC 9110 §12.5)\n\t\"accept-charset\", // §12.5.2 (deprecated)\n\t\"accept-encoding\", // §12.5.3\n\t\"accept-language\", // §12.5.4\n\n\t// Conditional requests (RFC 9110 §13.1)\n\t\"if-match\", // §13.1.1\n\t\"if-none-match\", // §13.1.2\n\t\"if-modified-since\", // §13.1.3\n\t\"if-unmodified-since\", // §13.1.4\n\t\"if-range\", // §13.1.5\n\n\t// Range requests (RFC 9110 §14.2)\n\t\"range\", // §14.2\n\n\t// Forwarding control (RFC 9110 §7.6)\n\t\"max-forwards\", // §7.6.2\n\n\t// Hop-by-hop (RFC 9110 §7.6.1)\n\t\"connection\", // §7.6.1\n\t\"keep-alive\",\n\t\"transfer-encoding\",\n\t\"te\", // §10.1.4\n\t\"upgrade\",\n\t\"trailer\",\n\t\"proxy-connection\", // non-standard\n\n\t// Valid on responses but WRONG if copied from request (RFC 9110 §8.6)\n\t\"content-length\",\n]);\n\nfunction stripRequestOnlyHeaders(headers: Headers): void {\n\tfor (const name of REQUEST_ONLY_HEADERS) {\n\t\theaders.delete(name);\n\t}\n}\n\n/**\n * Copy headers from `source` into `target`. `Set-Cookie` is appended (one\n * header per cookie) because RFC 9110 §5.3 notes it cannot be combined\n * into a single comma-separated value; other headers are set (replace).\n */\nfunction copyHeaders(target: Headers, source: HeadersInit | undefined): void {\n\tif (!source) return;\n\tfor (const [key, value] of new Headers(source).entries()) {\n\t\tif (key.toLowerCase() === \"set-cookie\") {\n\t\t\ttarget.append(key, value);\n\t\t} else {\n\t\t\ttarget.set(key, value);\n\t\t}\n\t}\n}\n\nexport function toResponse(data?: any, init?: ResponseInit): Response {\n\tif (data instanceof Response) {\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tcopyHeaders(data.headers, safeHeaders);\n\t\t}\n\t\treturn data;\n\t}\n\tconst isJSON = isJSONResponse(data);\n\tif (isJSON) {\n\t\tconst body = data.body;\n\t\tconst routerResponse = data.routerResponse;\n\t\tif (routerResponse instanceof Response) {\n\t\t\treturn routerResponse;\n\t\t}\n\t\tconst headers = new Headers();\n\t\tcopyHeaders(headers, routerResponse?.headers);\n\t\tcopyHeaders(headers, data.headers);\n\t\tif (init?.headers) {\n\t\t\tconst safeHeaders = new Headers(init.headers);\n\t\t\tstripRequestOnlyHeaders(safeHeaders);\n\t\t\tcopyHeaders(headers, safeHeaders);\n\t\t}\n\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t\treturn new Response(JSON.stringify(body), {\n\t\t\t...routerResponse,\n\t\t\theaders,\n\t\t\tstatus: data.status ?? init?.status ?? routerResponse?.status,\n\t\t\tstatusText: init?.statusText ?? routerResponse?.statusText,\n\t\t});\n\t}\n\tif (isAPIError(data)) {\n\t\treturn toResponse(data.body, {\n\t\t\tstatus: init?.status ?? data.statusCode,\n\t\t\tstatusText: data.status.toString(),\n\t\t\theaders: init?.headers || data.headers,\n\t\t});\n\t}\n\tlet body = data;\n\tconst headers = new Headers(init?.headers);\n\tstripRequestOnlyHeaders(headers);\n\tif (!data) {\n\t\tif (data === null) {\n\t\t\tbody = JSON.stringify(null);\n\t\t}\n\t\theaders.set(\"content-type\", \"application/json\");\n\t} else if (typeof data === \"string\") {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"text/plain\");\n\t} else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (data instanceof Blob) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", data.type || \"application/octet-stream\");\n\t} else if (data instanceof FormData) {\n\t\tbody = data;\n\t} else if (data instanceof URLSearchParams) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/x-www-form-urlencoded\");\n\t} else if (data instanceof ReadableStream) {\n\t\tbody = data;\n\t\theaders.set(\"Content-Type\", \"application/octet-stream\");\n\t} else if (isJSONSerializable(data)) {\n\t\tbody = safeStringify(data);\n\t\theaders.set(\"Content-Type\", \"application/json\");\n\t}\n\n\treturn new Response(body, {\n\t\t...init,\n\t\theaders,\n\t});\n}\n"],"mappings":"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"}
package/dist/utils.cjs CHANGED
@@ -22,7 +22,15 @@ async function getBody(request, allowedMediaTypes) {
22
22
  });
23
23
  }
24
24
  }
25
- if (jsonContentTypeRegex.test(normalizedContentType)) return await request.json();
25
+ if (jsonContentTypeRegex.test(normalizedContentType)) try {
26
+ return await request.json();
27
+ } catch (e) {
28
+ if (e instanceof SyntaxError) throw new require_error.APIError(400, {
29
+ message: "Invalid JSON in request body",
30
+ code: "BAD_REQUEST"
31
+ });
32
+ throw e;
33
+ }
26
34
  if (normalizedContentType.includes("application/x-www-form-urlencoded")) {
27
35
  const formData = await request.formData();
28
36
  const result = {};
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":["APIError"],"sources":["../src/utils.ts"],"sourcesContent":["import { APIError } from \"./error\";\n\nconst jsonContentTypeRegex = /^application\\/([a-z0-9.+-]*\\+)?json/i;\n\nexport async function getBody(request: Request, allowedMediaTypes?: string[]) {\n\tconst contentType = request.headers.get(\"content-type\") || \"\";\n\tconst normalizedContentType = contentType.toLowerCase();\n\n\tif (!request.body) {\n\t\treturn undefined;\n\t}\n\n\t// Validate content-type if allowedMediaTypes is provided\n\tif (allowedMediaTypes && allowedMediaTypes.length > 0) {\n\t\tconst isAllowed = allowedMediaTypes.some((allowed) => {\n\t\t\t// Normalize both content types for comparison\n\t\t\tconst normalizedContentTypeBase = normalizedContentType\n\t\t\t\t.split(\";\")[0]!\n\t\t\t\t.trim();\n\t\t\tconst normalizedAllowed = allowed.toLowerCase().trim();\n\t\t\treturn (\n\t\t\t\tnormalizedContentTypeBase === normalizedAllowed ||\n\t\t\t\tnormalizedContentTypeBase.includes(normalizedAllowed)\n\t\t\t);\n\t\t});\n\n\t\tif (!isAllowed) {\n\t\t\tif (!normalizedContentType) {\n\t\t\t\tthrow new APIError(415, {\n\t\t\t\t\tmessage: `Content-Type is required. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow new APIError(415, {\n\t\t\t\tmessage: `Content-Type \"${contentType}\" is not allowed. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (jsonContentTypeRegex.test(normalizedContentType)) {\n\t\treturn await request.json();\n\t}\n\n\tif (normalizedContentType.includes(\"application/x-www-form-urlencoded\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, string> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value.toString();\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"multipart/form-data\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, any> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value;\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"text/plain\")) {\n\t\treturn await request.text();\n\t}\n\n\tif (normalizedContentType.includes(\"application/octet-stream\")) {\n\t\treturn await request.arrayBuffer();\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/pdf\") ||\n\t\tnormalizedContentType.includes(\"image/\") ||\n\t\tnormalizedContentType.includes(\"video/\")\n\t) {\n\t\tconst blob = await request.blob();\n\t\treturn blob;\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/stream\") ||\n\t\trequest.body instanceof ReadableStream\n\t) {\n\t\treturn request.body;\n\t}\n\n\treturn await request.text();\n}\n\nexport function isAPIError(error: any): error is APIError {\n\treturn error instanceof APIError || error?.name === \"APIError\";\n}\n\nexport function tryDecode(str: string) {\n\ttry {\n\t\treturn str.includes(\"%\") ? decodeURIComponent(str) : str;\n\t} catch {\n\t\treturn str;\n\t}\n}\n\ntype Success<T> = {\n\tdata: T;\n\terror: null;\n};\n\ntype Failure<E> = {\n\tdata: null;\n\terror: E;\n};\n\ntype Result<T, E = Error> = Success<T> | Failure<E>;\n\nexport async function tryCatch<T, E = Error>(\n\tpromise: Promise<T>,\n): Promise<Result<T, E>> {\n\ttry {\n\t\tconst data = await promise;\n\t\treturn { data, error: null };\n\t} catch (error) {\n\t\treturn { data: null, error: error as E };\n\t}\n}\n\n/**\n * Check if an object is a `Request`\n * - `instanceof`: works for native Request instances\n * - `toString`: handles where instanceof check fails but the object is still a valid Request\n */\nexport function isRequest(obj: unknown): obj is Request {\n\treturn (\n\t\tobj instanceof Request ||\n\t\tObject.prototype.toString.call(obj) === \"[object Request]\"\n\t);\n}\n"],"mappings":";;;AAEA,MAAM,uBAAuB;AAE7B,eAAsB,QAAQ,SAAkB,mBAA8B;CAC7E,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe,IAAI;CAC3D,MAAM,wBAAwB,YAAY,aAAa;AAEvD,KAAI,CAAC,QAAQ,KACZ;AAID,KAAI,qBAAqB,kBAAkB,SAAS,GAanD;MAAI,CAZc,kBAAkB,MAAM,YAAY;GAErD,MAAM,4BAA4B,sBAChC,MAAM,IAAI,CAAC,GACX,MAAM;GACR,MAAM,oBAAoB,QAAQ,aAAa,CAAC,MAAM;AACtD,UACC,8BAA8B,qBAC9B,0BAA0B,SAAS,kBAAkB;IAErD,EAEc;AACf,OAAI,CAAC,sBACJ,OAAM,IAAIA,uBAAS,KAAK;IACvB,SAAS,4CAA4C,kBAAkB,KAAK,KAAK;IACjF,MAAM;IACN,CAAC;AAEH,SAAM,IAAIA,uBAAS,KAAK;IACvB,SAAS,iBAAiB,YAAY,mCAAmC,kBAAkB,KAAK,KAAK;IACrG,MAAM;IACN,CAAC;;;AAIJ,KAAI,qBAAqB,KAAK,sBAAsB,CACnD,QAAO,MAAM,QAAQ,MAAM;AAG5B,KAAI,sBAAsB,SAAS,oCAAoC,EAAE;EACxE,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAAiC,EAAE;AACzC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO,MAAM,UAAU;IAC7B;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,sBAAsB,EAAE;EAC1D,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAA8B,EAAE;AACtC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO;IACb;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,aAAa,CAC/C,QAAO,MAAM,QAAQ,MAAM;AAG5B,KAAI,sBAAsB,SAAS,2BAA2B,CAC7D,QAAO,MAAM,QAAQ,aAAa;AAGnC,KACC,sBAAsB,SAAS,kBAAkB,IACjD,sBAAsB,SAAS,SAAS,IACxC,sBAAsB,SAAS,SAAS,CAGxC,QADa,MAAM,QAAQ,MAAM;AAIlC,KACC,sBAAsB,SAAS,qBAAqB,IACpD,QAAQ,gBAAgB,eAExB,QAAO,QAAQ;AAGhB,QAAO,MAAM,QAAQ,MAAM;;AAG5B,SAAgB,WAAW,OAA+B;AACzD,QAAO,iBAAiBA,0BAAY,OAAO,SAAS;;AAGrD,SAAgB,UAAU,KAAa;AACtC,KAAI;AACH,SAAO,IAAI,SAAS,IAAI,GAAG,mBAAmB,IAAI,GAAG;SAC9C;AACP,SAAO;;;AAgBT,eAAsB,SACrB,SACwB;AACxB,KAAI;AAEH,SAAO;GAAE,MADI,MAAM;GACJ,OAAO;GAAM;UACpB,OAAO;AACf,SAAO;GAAE,MAAM;GAAa;GAAY;;;;;;;;AAS1C,SAAgB,UAAU,KAA8B;AACvD,QACC,eAAe,WACf,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK"}
1
+ {"version":3,"file":"utils.cjs","names":["APIError"],"sources":["../src/utils.ts"],"sourcesContent":["import { APIError } from \"./error\";\n\nconst jsonContentTypeRegex = /^application\\/([a-z0-9.+-]*\\+)?json/i;\n\nexport async function getBody(request: Request, allowedMediaTypes?: string[]) {\n\tconst contentType = request.headers.get(\"content-type\") || \"\";\n\tconst normalizedContentType = contentType.toLowerCase();\n\n\tif (!request.body) {\n\t\treturn undefined;\n\t}\n\n\t// Validate content-type if allowedMediaTypes is provided\n\tif (allowedMediaTypes && allowedMediaTypes.length > 0) {\n\t\tconst isAllowed = allowedMediaTypes.some((allowed) => {\n\t\t\t// Normalize both content types for comparison\n\t\t\tconst normalizedContentTypeBase = normalizedContentType\n\t\t\t\t.split(\";\")[0]!\n\t\t\t\t.trim();\n\t\t\tconst normalizedAllowed = allowed.toLowerCase().trim();\n\t\t\treturn (\n\t\t\t\tnormalizedContentTypeBase === normalizedAllowed ||\n\t\t\t\tnormalizedContentTypeBase.includes(normalizedAllowed)\n\t\t\t);\n\t\t});\n\n\t\tif (!isAllowed) {\n\t\t\tif (!normalizedContentType) {\n\t\t\t\tthrow new APIError(415, {\n\t\t\t\t\tmessage: `Content-Type is required. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow new APIError(415, {\n\t\t\t\tmessage: `Content-Type \"${contentType}\" is not allowed. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (jsonContentTypeRegex.test(normalizedContentType)) {\n\t\ttry {\n\t\t\treturn await request.json();\n\t\t} catch (e) {\n\t\t\tif (e instanceof SyntaxError) {\n\t\t\t\tthrow new APIError(400, {\n\t\t\t\t\tmessage: \"Invalid JSON in request body\",\n\t\t\t\t\tcode: \"BAD_REQUEST\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\tif (normalizedContentType.includes(\"application/x-www-form-urlencoded\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, string> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value.toString();\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"multipart/form-data\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, any> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value;\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"text/plain\")) {\n\t\treturn await request.text();\n\t}\n\n\tif (normalizedContentType.includes(\"application/octet-stream\")) {\n\t\treturn await request.arrayBuffer();\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/pdf\") ||\n\t\tnormalizedContentType.includes(\"image/\") ||\n\t\tnormalizedContentType.includes(\"video/\")\n\t) {\n\t\tconst blob = await request.blob();\n\t\treturn blob;\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/stream\") ||\n\t\trequest.body instanceof ReadableStream\n\t) {\n\t\treturn request.body;\n\t}\n\n\treturn await request.text();\n}\n\nexport function isAPIError(error: any): error is APIError {\n\treturn error instanceof APIError || error?.name === \"APIError\";\n}\n\nexport function tryDecode(str: string) {\n\ttry {\n\t\treturn str.includes(\"%\") ? decodeURIComponent(str) : str;\n\t} catch {\n\t\treturn str;\n\t}\n}\n\ntype Success<T> = {\n\tdata: T;\n\terror: null;\n};\n\ntype Failure<E> = {\n\tdata: null;\n\terror: E;\n};\n\ntype Result<T, E = Error> = Success<T> | Failure<E>;\n\nexport async function tryCatch<T, E = Error>(\n\tpromise: Promise<T>,\n): Promise<Result<T, E>> {\n\ttry {\n\t\tconst data = await promise;\n\t\treturn { data, error: null };\n\t} catch (error) {\n\t\treturn { data: null, error: error as E };\n\t}\n}\n\n/**\n * Check if an object is a `Request`\n * - `instanceof`: works for native Request instances\n * - `toString`: handles where instanceof check fails but the object is still a valid Request\n */\nexport function isRequest(obj: unknown): obj is Request {\n\treturn (\n\t\tobj instanceof Request ||\n\t\tObject.prototype.toString.call(obj) === \"[object Request]\"\n\t);\n}\n"],"mappings":";;;AAEA,MAAM,uBAAuB;AAE7B,eAAsB,QAAQ,SAAkB,mBAA8B;CAC7E,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe,IAAI;CAC3D,MAAM,wBAAwB,YAAY,aAAa;AAEvD,KAAI,CAAC,QAAQ,KACZ;AAID,KAAI,qBAAqB,kBAAkB,SAAS,GAanD;MAAI,CAZc,kBAAkB,MAAM,YAAY;GAErD,MAAM,4BAA4B,sBAChC,MAAM,IAAI,CAAC,GACX,MAAM;GACR,MAAM,oBAAoB,QAAQ,aAAa,CAAC,MAAM;AACtD,UACC,8BAA8B,qBAC9B,0BAA0B,SAAS,kBAAkB;IAErD,EAEc;AACf,OAAI,CAAC,sBACJ,OAAM,IAAIA,uBAAS,KAAK;IACvB,SAAS,4CAA4C,kBAAkB,KAAK,KAAK;IACjF,MAAM;IACN,CAAC;AAEH,SAAM,IAAIA,uBAAS,KAAK;IACvB,SAAS,iBAAiB,YAAY,mCAAmC,kBAAkB,KAAK,KAAK;IACrG,MAAM;IACN,CAAC;;;AAIJ,KAAI,qBAAqB,KAAK,sBAAsB,CACnD,KAAI;AACH,SAAO,MAAM,QAAQ,MAAM;UACnB,GAAG;AACX,MAAI,aAAa,YAChB,OAAM,IAAIA,uBAAS,KAAK;GACvB,SAAS;GACT,MAAM;GACN,CAAC;AAEH,QAAM;;AAIR,KAAI,sBAAsB,SAAS,oCAAoC,EAAE;EACxE,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAAiC,EAAE;AACzC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO,MAAM,UAAU;IAC7B;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,sBAAsB,EAAE;EAC1D,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAA8B,EAAE;AACtC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO;IACb;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,aAAa,CAC/C,QAAO,MAAM,QAAQ,MAAM;AAG5B,KAAI,sBAAsB,SAAS,2BAA2B,CAC7D,QAAO,MAAM,QAAQ,aAAa;AAGnC,KACC,sBAAsB,SAAS,kBAAkB,IACjD,sBAAsB,SAAS,SAAS,IACxC,sBAAsB,SAAS,SAAS,CAGxC,QADa,MAAM,QAAQ,MAAM;AAIlC,KACC,sBAAsB,SAAS,qBAAqB,IACpD,QAAQ,gBAAgB,eAExB,QAAO,QAAQ;AAGhB,QAAO,MAAM,QAAQ,MAAM;;AAG5B,SAAgB,WAAW,OAA+B;AACzD,QAAO,iBAAiBA,0BAAY,OAAO,SAAS;;AAGrD,SAAgB,UAAU,KAAa;AACtC,KAAI;AACH,SAAO,IAAI,SAAS,IAAI,GAAG,mBAAmB,IAAI,GAAG;SAC9C;AACP,SAAO;;;AAgBT,eAAsB,SACrB,SACwB;AACxB,KAAI;AAEH,SAAO;GAAE,MADI,MAAM;GACJ,OAAO;GAAM;UACpB,OAAO;AACf,SAAO;GAAE,MAAM;GAAa;GAAY;;;;;;;;AAS1C,SAAgB,UAAU,KAA8B;AACvD,QACC,eAAe,WACf,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK"}
package/dist/utils.mjs CHANGED
@@ -22,7 +22,15 @@ async function getBody(request, allowedMediaTypes) {
22
22
  });
23
23
  }
24
24
  }
25
- if (jsonContentTypeRegex.test(normalizedContentType)) return await request.json();
25
+ if (jsonContentTypeRegex.test(normalizedContentType)) try {
26
+ return await request.json();
27
+ } catch (e) {
28
+ if (e instanceof SyntaxError) throw new APIError(400, {
29
+ message: "Invalid JSON in request body",
30
+ code: "BAD_REQUEST"
31
+ });
32
+ throw e;
33
+ }
26
34
  if (normalizedContentType.includes("application/x-www-form-urlencoded")) {
27
35
  const formData = await request.formData();
28
36
  const result = {};
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { APIError } from \"./error\";\n\nconst jsonContentTypeRegex = /^application\\/([a-z0-9.+-]*\\+)?json/i;\n\nexport async function getBody(request: Request, allowedMediaTypes?: string[]) {\n\tconst contentType = request.headers.get(\"content-type\") || \"\";\n\tconst normalizedContentType = contentType.toLowerCase();\n\n\tif (!request.body) {\n\t\treturn undefined;\n\t}\n\n\t// Validate content-type if allowedMediaTypes is provided\n\tif (allowedMediaTypes && allowedMediaTypes.length > 0) {\n\t\tconst isAllowed = allowedMediaTypes.some((allowed) => {\n\t\t\t// Normalize both content types for comparison\n\t\t\tconst normalizedContentTypeBase = normalizedContentType\n\t\t\t\t.split(\";\")[0]!\n\t\t\t\t.trim();\n\t\t\tconst normalizedAllowed = allowed.toLowerCase().trim();\n\t\t\treturn (\n\t\t\t\tnormalizedContentTypeBase === normalizedAllowed ||\n\t\t\t\tnormalizedContentTypeBase.includes(normalizedAllowed)\n\t\t\t);\n\t\t});\n\n\t\tif (!isAllowed) {\n\t\t\tif (!normalizedContentType) {\n\t\t\t\tthrow new APIError(415, {\n\t\t\t\t\tmessage: `Content-Type is required. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow new APIError(415, {\n\t\t\t\tmessage: `Content-Type \"${contentType}\" is not allowed. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (jsonContentTypeRegex.test(normalizedContentType)) {\n\t\treturn await request.json();\n\t}\n\n\tif (normalizedContentType.includes(\"application/x-www-form-urlencoded\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, string> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value.toString();\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"multipart/form-data\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, any> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value;\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"text/plain\")) {\n\t\treturn await request.text();\n\t}\n\n\tif (normalizedContentType.includes(\"application/octet-stream\")) {\n\t\treturn await request.arrayBuffer();\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/pdf\") ||\n\t\tnormalizedContentType.includes(\"image/\") ||\n\t\tnormalizedContentType.includes(\"video/\")\n\t) {\n\t\tconst blob = await request.blob();\n\t\treturn blob;\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/stream\") ||\n\t\trequest.body instanceof ReadableStream\n\t) {\n\t\treturn request.body;\n\t}\n\n\treturn await request.text();\n}\n\nexport function isAPIError(error: any): error is APIError {\n\treturn error instanceof APIError || error?.name === \"APIError\";\n}\n\nexport function tryDecode(str: string) {\n\ttry {\n\t\treturn str.includes(\"%\") ? decodeURIComponent(str) : str;\n\t} catch {\n\t\treturn str;\n\t}\n}\n\ntype Success<T> = {\n\tdata: T;\n\terror: null;\n};\n\ntype Failure<E> = {\n\tdata: null;\n\terror: E;\n};\n\ntype Result<T, E = Error> = Success<T> | Failure<E>;\n\nexport async function tryCatch<T, E = Error>(\n\tpromise: Promise<T>,\n): Promise<Result<T, E>> {\n\ttry {\n\t\tconst data = await promise;\n\t\treturn { data, error: null };\n\t} catch (error) {\n\t\treturn { data: null, error: error as E };\n\t}\n}\n\n/**\n * Check if an object is a `Request`\n * - `instanceof`: works for native Request instances\n * - `toString`: handles where instanceof check fails but the object is still a valid Request\n */\nexport function isRequest(obj: unknown): obj is Request {\n\treturn (\n\t\tobj instanceof Request ||\n\t\tObject.prototype.toString.call(obj) === \"[object Request]\"\n\t);\n}\n"],"mappings":";;;AAEA,MAAM,uBAAuB;AAE7B,eAAsB,QAAQ,SAAkB,mBAA8B;CAC7E,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe,IAAI;CAC3D,MAAM,wBAAwB,YAAY,aAAa;AAEvD,KAAI,CAAC,QAAQ,KACZ;AAID,KAAI,qBAAqB,kBAAkB,SAAS,GAanD;MAAI,CAZc,kBAAkB,MAAM,YAAY;GAErD,MAAM,4BAA4B,sBAChC,MAAM,IAAI,CAAC,GACX,MAAM;GACR,MAAM,oBAAoB,QAAQ,aAAa,CAAC,MAAM;AACtD,UACC,8BAA8B,qBAC9B,0BAA0B,SAAS,kBAAkB;IAErD,EAEc;AACf,OAAI,CAAC,sBACJ,OAAM,IAAI,SAAS,KAAK;IACvB,SAAS,4CAA4C,kBAAkB,KAAK,KAAK;IACjF,MAAM;IACN,CAAC;AAEH,SAAM,IAAI,SAAS,KAAK;IACvB,SAAS,iBAAiB,YAAY,mCAAmC,kBAAkB,KAAK,KAAK;IACrG,MAAM;IACN,CAAC;;;AAIJ,KAAI,qBAAqB,KAAK,sBAAsB,CACnD,QAAO,MAAM,QAAQ,MAAM;AAG5B,KAAI,sBAAsB,SAAS,oCAAoC,EAAE;EACxE,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAAiC,EAAE;AACzC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO,MAAM,UAAU;IAC7B;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,sBAAsB,EAAE;EAC1D,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAA8B,EAAE;AACtC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO;IACb;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,aAAa,CAC/C,QAAO,MAAM,QAAQ,MAAM;AAG5B,KAAI,sBAAsB,SAAS,2BAA2B,CAC7D,QAAO,MAAM,QAAQ,aAAa;AAGnC,KACC,sBAAsB,SAAS,kBAAkB,IACjD,sBAAsB,SAAS,SAAS,IACxC,sBAAsB,SAAS,SAAS,CAGxC,QADa,MAAM,QAAQ,MAAM;AAIlC,KACC,sBAAsB,SAAS,qBAAqB,IACpD,QAAQ,gBAAgB,eAExB,QAAO,QAAQ;AAGhB,QAAO,MAAM,QAAQ,MAAM;;AAG5B,SAAgB,WAAW,OAA+B;AACzD,QAAO,iBAAiB,YAAY,OAAO,SAAS;;AAGrD,SAAgB,UAAU,KAAa;AACtC,KAAI;AACH,SAAO,IAAI,SAAS,IAAI,GAAG,mBAAmB,IAAI,GAAG;SAC9C;AACP,SAAO;;;AAgBT,eAAsB,SACrB,SACwB;AACxB,KAAI;AAEH,SAAO;GAAE,MADI,MAAM;GACJ,OAAO;GAAM;UACpB,OAAO;AACf,SAAO;GAAE,MAAM;GAAa;GAAY;;;;;;;;AAS1C,SAAgB,UAAU,KAA8B;AACvD,QACC,eAAe,WACf,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { APIError } from \"./error\";\n\nconst jsonContentTypeRegex = /^application\\/([a-z0-9.+-]*\\+)?json/i;\n\nexport async function getBody(request: Request, allowedMediaTypes?: string[]) {\n\tconst contentType = request.headers.get(\"content-type\") || \"\";\n\tconst normalizedContentType = contentType.toLowerCase();\n\n\tif (!request.body) {\n\t\treturn undefined;\n\t}\n\n\t// Validate content-type if allowedMediaTypes is provided\n\tif (allowedMediaTypes && allowedMediaTypes.length > 0) {\n\t\tconst isAllowed = allowedMediaTypes.some((allowed) => {\n\t\t\t// Normalize both content types for comparison\n\t\t\tconst normalizedContentTypeBase = normalizedContentType\n\t\t\t\t.split(\";\")[0]!\n\t\t\t\t.trim();\n\t\t\tconst normalizedAllowed = allowed.toLowerCase().trim();\n\t\t\treturn (\n\t\t\t\tnormalizedContentTypeBase === normalizedAllowed ||\n\t\t\t\tnormalizedContentTypeBase.includes(normalizedAllowed)\n\t\t\t);\n\t\t});\n\n\t\tif (!isAllowed) {\n\t\t\tif (!normalizedContentType) {\n\t\t\t\tthrow new APIError(415, {\n\t\t\t\t\tmessage: `Content-Type is required. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow new APIError(415, {\n\t\t\t\tmessage: `Content-Type \"${contentType}\" is not allowed. Allowed types: ${allowedMediaTypes.join(\", \")}`,\n\t\t\t\tcode: \"UNSUPPORTED_MEDIA_TYPE\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (jsonContentTypeRegex.test(normalizedContentType)) {\n\t\ttry {\n\t\t\treturn await request.json();\n\t\t} catch (e) {\n\t\t\tif (e instanceof SyntaxError) {\n\t\t\t\tthrow new APIError(400, {\n\t\t\t\t\tmessage: \"Invalid JSON in request body\",\n\t\t\t\t\tcode: \"BAD_REQUEST\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\tif (normalizedContentType.includes(\"application/x-www-form-urlencoded\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, string> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value.toString();\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"multipart/form-data\")) {\n\t\tconst formData = await request.formData();\n\t\tconst result: Record<string, any> = {};\n\t\tformData.forEach((value, key) => {\n\t\t\tresult[key] = value;\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (normalizedContentType.includes(\"text/plain\")) {\n\t\treturn await request.text();\n\t}\n\n\tif (normalizedContentType.includes(\"application/octet-stream\")) {\n\t\treturn await request.arrayBuffer();\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/pdf\") ||\n\t\tnormalizedContentType.includes(\"image/\") ||\n\t\tnormalizedContentType.includes(\"video/\")\n\t) {\n\t\tconst blob = await request.blob();\n\t\treturn blob;\n\t}\n\n\tif (\n\t\tnormalizedContentType.includes(\"application/stream\") ||\n\t\trequest.body instanceof ReadableStream\n\t) {\n\t\treturn request.body;\n\t}\n\n\treturn await request.text();\n}\n\nexport function isAPIError(error: any): error is APIError {\n\treturn error instanceof APIError || error?.name === \"APIError\";\n}\n\nexport function tryDecode(str: string) {\n\ttry {\n\t\treturn str.includes(\"%\") ? decodeURIComponent(str) : str;\n\t} catch {\n\t\treturn str;\n\t}\n}\n\ntype Success<T> = {\n\tdata: T;\n\terror: null;\n};\n\ntype Failure<E> = {\n\tdata: null;\n\terror: E;\n};\n\ntype Result<T, E = Error> = Success<T> | Failure<E>;\n\nexport async function tryCatch<T, E = Error>(\n\tpromise: Promise<T>,\n): Promise<Result<T, E>> {\n\ttry {\n\t\tconst data = await promise;\n\t\treturn { data, error: null };\n\t} catch (error) {\n\t\treturn { data: null, error: error as E };\n\t}\n}\n\n/**\n * Check if an object is a `Request`\n * - `instanceof`: works for native Request instances\n * - `toString`: handles where instanceof check fails but the object is still a valid Request\n */\nexport function isRequest(obj: unknown): obj is Request {\n\treturn (\n\t\tobj instanceof Request ||\n\t\tObject.prototype.toString.call(obj) === \"[object Request]\"\n\t);\n}\n"],"mappings":";;;AAEA,MAAM,uBAAuB;AAE7B,eAAsB,QAAQ,SAAkB,mBAA8B;CAC7E,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe,IAAI;CAC3D,MAAM,wBAAwB,YAAY,aAAa;AAEvD,KAAI,CAAC,QAAQ,KACZ;AAID,KAAI,qBAAqB,kBAAkB,SAAS,GAanD;MAAI,CAZc,kBAAkB,MAAM,YAAY;GAErD,MAAM,4BAA4B,sBAChC,MAAM,IAAI,CAAC,GACX,MAAM;GACR,MAAM,oBAAoB,QAAQ,aAAa,CAAC,MAAM;AACtD,UACC,8BAA8B,qBAC9B,0BAA0B,SAAS,kBAAkB;IAErD,EAEc;AACf,OAAI,CAAC,sBACJ,OAAM,IAAI,SAAS,KAAK;IACvB,SAAS,4CAA4C,kBAAkB,KAAK,KAAK;IACjF,MAAM;IACN,CAAC;AAEH,SAAM,IAAI,SAAS,KAAK;IACvB,SAAS,iBAAiB,YAAY,mCAAmC,kBAAkB,KAAK,KAAK;IACrG,MAAM;IACN,CAAC;;;AAIJ,KAAI,qBAAqB,KAAK,sBAAsB,CACnD,KAAI;AACH,SAAO,MAAM,QAAQ,MAAM;UACnB,GAAG;AACX,MAAI,aAAa,YAChB,OAAM,IAAI,SAAS,KAAK;GACvB,SAAS;GACT,MAAM;GACN,CAAC;AAEH,QAAM;;AAIR,KAAI,sBAAsB,SAAS,oCAAoC,EAAE;EACxE,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAAiC,EAAE;AACzC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO,MAAM,UAAU;IAC7B;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,sBAAsB,EAAE;EAC1D,MAAM,WAAW,MAAM,QAAQ,UAAU;EACzC,MAAM,SAA8B,EAAE;AACtC,WAAS,SAAS,OAAO,QAAQ;AAChC,UAAO,OAAO;IACb;AACF,SAAO;;AAGR,KAAI,sBAAsB,SAAS,aAAa,CAC/C,QAAO,MAAM,QAAQ,MAAM;AAG5B,KAAI,sBAAsB,SAAS,2BAA2B,CAC7D,QAAO,MAAM,QAAQ,aAAa;AAGnC,KACC,sBAAsB,SAAS,kBAAkB,IACjD,sBAAsB,SAAS,SAAS,IACxC,sBAAsB,SAAS,SAAS,CAGxC,QADa,MAAM,QAAQ,MAAM;AAIlC,KACC,sBAAsB,SAAS,qBAAqB,IACpD,QAAQ,gBAAgB,eAExB,QAAO,QAAQ;AAGhB,QAAO,MAAM,QAAQ,MAAM;;AAG5B,SAAgB,WAAW,OAA+B;AACzD,QAAO,iBAAiB,YAAY,OAAO,SAAS;;AAGrD,SAAgB,UAAU,KAAa;AACtC,KAAI;AACH,SAAO,IAAI,SAAS,IAAI,GAAG,mBAAmB,IAAI,GAAG;SAC9C;AACP,SAAO;;;AAgBT,eAAsB,SACrB,SACwB;AACxB,KAAI;AAEH,SAAO;GAAE,MADI,MAAM;GACJ,OAAO;GAAM;UACpB,OAAO;AACf,SAAO;GAAE,MAAM;GAAa;GAAY;;;;;;;;AAS1C,SAAgB,UAAU,KAA8B;AACvD,QACC,eAAe,WACf,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-call",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",