better-call 2.0.4 → 2.0.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.
Files changed (83) hide show
  1. package/README.md +870 -0
  2. package/dist/_virtual/_rolldown/runtime.cjs +7 -13
  3. package/dist/adapters/node/request.cjs +3 -4
  4. package/dist/adapters/node/request.cjs.map +1 -1
  5. package/dist/adapters/node/request.d.cts +1 -6
  6. package/dist/adapters/node/request.d.mts +1 -6
  7. package/dist/adapters/node/request.mjs +1 -2
  8. package/dist/adapters/node/request.mjs.map +1 -1
  9. package/dist/client.cjs +3 -5
  10. package/dist/client.cjs.map +1 -1
  11. package/dist/client.d.cts +3 -4
  12. package/dist/client.d.mts +3 -4
  13. package/dist/client.mjs +1 -2
  14. package/dist/client.mjs.map +1 -1
  15. package/dist/context.cjs +6 -7
  16. package/dist/context.cjs.map +1 -1
  17. package/dist/context.d.cts +0 -1
  18. package/dist/context.d.mts +0 -1
  19. package/dist/context.mjs +1 -2
  20. package/dist/context.mjs.map +1 -1
  21. package/dist/cookies.cjs +3 -4
  22. package/dist/cookies.cjs.map +1 -1
  23. package/dist/cookies.mjs +1 -2
  24. package/dist/cookies.mjs.map +1 -1
  25. package/dist/crypto.cjs +1 -3
  26. package/dist/crypto.cjs.map +1 -1
  27. package/dist/crypto.mjs +1 -2
  28. package/dist/crypto.mjs.map +1 -1
  29. package/dist/endpoint.cjs +5 -6
  30. package/dist/endpoint.cjs.map +1 -1
  31. package/dist/endpoint.d.cts +1 -2
  32. package/dist/endpoint.d.mts +1 -2
  33. package/dist/endpoint.mjs +1 -2
  34. package/dist/endpoint.mjs.map +1 -1
  35. package/dist/error.cjs +8 -3
  36. package/dist/error.cjs.map +1 -1
  37. package/dist/error.d.cts +0 -1
  38. package/dist/error.d.mts +0 -1
  39. package/dist/error.mjs +7 -1
  40. package/dist/error.mjs.map +1 -1
  41. package/dist/helper.d.cts +5 -5
  42. package/dist/helper.d.mts +5 -5
  43. package/dist/index.cjs +9 -10
  44. package/dist/index.mjs +1 -2
  45. package/dist/middleware.cjs +4 -5
  46. package/dist/middleware.cjs.map +1 -1
  47. package/dist/middleware.d.cts +0 -1
  48. package/dist/middleware.d.mts +0 -1
  49. package/dist/middleware.mjs +1 -2
  50. package/dist/middleware.mjs.map +1 -1
  51. package/dist/node.cjs +3 -4
  52. package/dist/node.cjs.map +1 -1
  53. package/dist/node.d.cts +0 -1
  54. package/dist/node.d.mts +0 -1
  55. package/dist/node.mjs +1 -2
  56. package/dist/node.mjs.map +1 -1
  57. package/dist/openapi.cjs +1 -3
  58. package/dist/openapi.cjs.map +1 -1
  59. package/dist/openapi.d.cts +2 -3
  60. package/dist/openapi.d.mts +2 -3
  61. package/dist/openapi.mjs +1 -2
  62. package/dist/openapi.mjs.map +1 -1
  63. package/dist/router.cjs +15 -17
  64. package/dist/router.cjs.map +1 -1
  65. package/dist/router.d.cts +0 -1
  66. package/dist/router.d.mts +0 -1
  67. package/dist/router.mjs +11 -12
  68. package/dist/router.mjs.map +1 -1
  69. package/dist/to-response.cjs +4 -5
  70. package/dist/to-response.cjs.map +1 -1
  71. package/dist/to-response.mjs +2 -3
  72. package/dist/to-response.mjs.map +1 -1
  73. package/dist/types.d.cts +1 -2
  74. package/dist/types.d.mts +1 -2
  75. package/dist/utils.cjs +2 -3
  76. package/dist/utils.cjs.map +1 -1
  77. package/dist/utils.mjs +1 -2
  78. package/dist/utils.mjs.map +1 -1
  79. package/dist/validator.cjs +1 -2
  80. package/dist/validator.cjs.map +1 -1
  81. package/dist/validator.mjs +1 -1
  82. package/dist/validator.mjs.map +1 -1
  83. package/package.json +1 -1
package/dist/router.cjs CHANGED
@@ -1,10 +1,8 @@
1
- const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
- const require_utils = require('./utils.cjs');
3
- const require_to_response = require('./to-response.cjs');
4
- const require_endpoint = require('./endpoint.cjs');
5
- const require_openapi = require('./openapi.cjs');
1
+ const require_utils = require("./utils.cjs");
2
+ const require_to_response = require("./to-response.cjs");
3
+ const require_endpoint = require("./endpoint.cjs");
4
+ const require_openapi = require("./openapi.cjs");
6
5
  let rou3 = require("rou3");
7
-
8
6
  //#region src/router.ts
9
7
  const createRouter = (endpoints, config) => {
10
8
  if (!config?.openapi?.disabled) {
@@ -26,19 +24,19 @@ const createRouter = (endpoints, config) => {
26
24
  for (const method of methods) (0, rou3.addRoute)(router, method, endpoint.path, endpoint);
27
25
  }
28
26
  if (config?.routerMiddleware?.length) for (const { path, middleware } of config.routerMiddleware) (0, rou3.addRoute)(middlewareRouter, "*", path, middleware);
27
+ const basePath = config?.basePath && config.basePath !== "/" ? config.basePath.replace(/\/+$/, "") : "";
29
28
  const processRequest = async (request) => {
30
29
  const url = new URL(request.url);
31
30
  const pathname = url.pathname;
32
- const path = config?.basePath && config.basePath !== "/" ? pathname.split(config.basePath).reduce((acc, curr, index) => {
33
- if (index !== 0) if (index > 1) acc.push(`${config.basePath}${curr}`);
34
- else acc.push(curr);
35
- return acc;
36
- }, []).join("") : url.pathname;
37
- if (!path?.length) return new Response(null, {
38
- status: 404,
39
- statusText: "Not Found"
40
- });
41
- if (/\/{2,}/.test(path)) return new Response(null, {
31
+ let path;
32
+ if (basePath) {
33
+ if (!pathname.startsWith(`${basePath}/`)) return new Response(null, {
34
+ status: 404,
35
+ statusText: "Not Found"
36
+ });
37
+ path = pathname.slice(basePath.length);
38
+ } else path = pathname;
39
+ if (path.length === 0 || /\/{2,}/.test(path)) return new Response(null, {
42
40
  status: 404,
43
41
  statusText: "Not Found"
44
42
  });
@@ -112,7 +110,7 @@ const createRouter = (endpoints, config) => {
112
110
  endpoints
113
111
  };
114
112
  };
115
-
116
113
  //#endregion
117
114
  exports.createRouter = createRouter;
115
+
118
116
  //# sourceMappingURL=router.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs","names":["createEndpoint","generator","getHTML","getBody","toResponse","isAPIError","isRequest"],"sources":["../src/router.ts"],"sourcesContent":["import {\n\taddRoute,\n\tcreateRouter as createRou3Router,\n\tfindAllRoutes,\n\tfindRoute,\n} from \"rou3\";\nimport { createEndpoint, type Endpoint } from \"./endpoint\";\nimport type { Middleware } from \"./middleware\";\nimport { generator, getHTML } from \"./openapi\";\nimport { toResponse } from \"./to-response\";\nimport { getBody, isAPIError, isRequest } from \"./utils\";\n\nexport interface RouterConfig {\n\tthrowError?: boolean;\n\tbasePath?: string;\n\trouterMiddleware?: Array<{\n\t\tpath: string;\n\t\tmiddleware: Middleware;\n\t}>;\n\t/**\n\t * additional Context that needs to passed to endpoints\n\t *\n\t * this will be available on `ctx.context` on endpoints\n\t */\n\trouterContext?: Record<string, any>;\n\t/**\n\t * A callback to run before any response\n\t */\n\tonResponse?: (response: Response, request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run before any request\n\t */\n\tonRequest?: (request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run when an error is thrown in the router or middleware.\n\t *\n\t * @param error - the error that was thrown in the router or middleware.\n\t * @returns a Response object that will be returned to the client.\n\t */\n\tonError?: (\n\t\terror: unknown,\n\t\trequest: Request,\n\t) => void | Promise<void> | Response | Promise<Response>;\n\t/**\n\t * List of allowed media types (MIME types) for the router\n\t *\n\t * if provided, only the media types in the list will be allowed to be passed in the body.\n\t *\n\t * If an endpoint has allowed media types, it will override the router's allowed media types.\n\t *\n\t * @example\n\t * ```ts\n\t * const router = createRouter({\n\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t * \t})\n\t */\n\tallowedMediaTypes?: string[];\n\t/**\n\t * Skip trailing slashes\n\t *\n\t * @default false\n\t */\n\tskipTrailingSlashes?: boolean;\n\t/**\n\t * Open API route configuration\n\t */\n\topenapi?: {\n\t\t/**\n\t\t * Disable openapi route\n\t\t *\n\t\t * @default false\n\t\t */\n\t\tdisabled?: boolean;\n\t\t/**\n\t\t * A path to display open api using scalar\n\t\t *\n\t\t * @default \"/api/reference\"\n\t\t */\n\t\tpath?: string;\n\t\t/**\n\t\t * Scalar Configuration\n\t\t */\n\t\tscalar?: {\n\t\t\t/**\n\t\t\t * Title\n\t\t\t * @default \"Open API Reference\"\n\t\t\t */\n\t\t\ttitle?: string;\n\t\t\t/**\n\t\t\t * Description\n\t\t\t *\n\t\t\t * @default \"Better Call Open API Reference\"\n\t\t\t */\n\t\t\tdescription?: string;\n\t\t\t/**\n\t\t\t * Logo URL\n\t\t\t */\n\t\t\tlogo?: string;\n\t\t\t/**\n\t\t\t * Scalar theme\n\t\t\t * @default \"saturn\"\n\t\t\t */\n\t\t\ttheme?: string;\n\t\t};\n\t};\n}\n\nexport const createRouter = <\n\tE extends Record<string, Endpoint>,\n\tConfig extends RouterConfig,\n>(\n\tendpoints: E,\n\tconfig?: Config,\n) => {\n\tif (!config?.openapi?.disabled) {\n\t\tconst openapi = {\n\t\t\tpath: \"/api/reference\",\n\t\t\t...config?.openapi,\n\t\t};\n\t\t//@ts-expect-error\n\t\tendpoints[\"openapi\"] = createEndpoint(\n\t\t\topenapi.path,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t},\n\t\t\tasync (c) => {\n\t\t\t\tconst schema = await generator(endpoints as any);\n\t\t\t\treturn new Response(getHTML(schema, openapi.scalar), {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t}\n\tconst router = createRou3Router();\n\tconst middlewareRouter = createRou3Router();\n\n\tfor (const endpoint of Object.values(endpoints)) {\n\t\tif (!endpoint.options || !endpoint.path) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (endpoint.options?.metadata?.SERVER_ONLY) continue;\n\n\t\tconst methods = Array.isArray(endpoint.options?.method)\n\t\t\t? endpoint.options.method\n\t\t\t: [endpoint.options?.method];\n\n\t\tfor (const method of methods) {\n\t\t\taddRoute(router, method as string, endpoint.path, endpoint);\n\t\t}\n\t}\n\n\tif (config?.routerMiddleware?.length) {\n\t\tfor (const { path, middleware } of config.routerMiddleware) {\n\t\t\taddRoute(middlewareRouter, \"*\", path, middleware);\n\t\t}\n\t}\n\n\tconst processRequest = async (request: Request) => {\n\t\tconst url = new URL(request.url);\n\t\tconst pathname = url.pathname;\n\t\tconst path =\n\t\t\tconfig?.basePath && config.basePath !== \"/\"\n\t\t\t\t? pathname\n\t\t\t\t\t\t.split(config.basePath)\n\t\t\t\t\t\t.reduce((acc, curr, index) => {\n\t\t\t\t\t\t\tif (index !== 0) {\n\t\t\t\t\t\t\t\tif (index > 1) {\n\t\t\t\t\t\t\t\t\tacc.push(`${config.basePath}${curr}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tacc.push(curr);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn acc;\n\t\t\t\t\t\t}, [] as string[])\n\t\t\t\t\t\t.join(\"\")\n\t\t\t\t: url.pathname;\n\t\tif (!path?.length) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\n\t\t// Reject paths with consecutive slashes\n\t\tif (/\\/{2,}/.test(path)) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\n\t\tconst route = findRoute(router, request.method, path) as {\n\t\t\tdata: Endpoint & { path: string };\n\t\t\tparams: Record<string, string>;\n\t\t};\n\t\tconst hasTrailingSlash = path.endsWith(\"/\");\n\t\tconst routeHasTrailingSlash = route?.data?.path?.endsWith(\"/\");\n\n\t\t// If the path has a trailing slash and the route doesn't have a trailing slash and skipTrailingSlashes is not set, return 404\n\t\tif (\n\t\t\thasTrailingSlash !== routeHasTrailingSlash &&\n\t\t\t!config?.skipTrailingSlashes\n\t\t) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\t\tif (!route?.data)\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\n\t\tconst query: Record<string, string | string[]> = {};\n\t\turl.searchParams.forEach((value, key) => {\n\t\t\tif (key in query) {\n\t\t\t\tif (Array.isArray(query[key])) {\n\t\t\t\t\t(query[key] as string[]).push(value);\n\t\t\t\t} else {\n\t\t\t\t\tquery[key] = [query[key] as string, value];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[key] = value;\n\t\t\t}\n\t\t});\n\n\t\tconst handler = route.data as Endpoint;\n\n\t\ttry {\n\t\t\t// Determine which allowedMediaTypes to use: endpoint-level overrides router-level\n\t\t\tconst allowedMediaTypes =\n\t\t\t\thandler.options.metadata?.allowedMediaTypes ||\n\t\t\t\tconfig?.allowedMediaTypes;\n\t\t\tconst context = {\n\t\t\t\tpath,\n\t\t\t\tmethod: request.method as \"GET\",\n\t\t\t\theaders: request.headers,\n\t\t\t\tparams: route.params\n\t\t\t\t\t? (JSON.parse(JSON.stringify(route.params)) as any)\n\t\t\t\t\t: {},\n\t\t\t\trequest: request,\n\t\t\t\tbody: handler.options.disableBody\n\t\t\t\t\t? undefined\n\t\t\t\t\t: await getBody(\n\t\t\t\t\t\t\thandler.options.cloneRequest ? request.clone() : request,\n\t\t\t\t\t\t\tallowedMediaTypes,\n\t\t\t\t\t\t),\n\t\t\t\tquery,\n\t\t\t\t_flag: \"router\" as const,\n\t\t\t\tasResponse: true,\n\t\t\t\tcontext: config?.routerContext,\n\t\t\t};\n\t\t\tconst middlewareRoutes = findAllRoutes(middlewareRouter, \"*\", path);\n\t\t\tif (middlewareRoutes?.length) {\n\t\t\t\tfor (const { data: middleware, params } of middlewareRoutes) {\n\t\t\t\t\tconst res = await (middleware as Endpoint)({\n\t\t\t\t\t\t...context,\n\t\t\t\t\t\tparams,\n\t\t\t\t\t\tasResponse: false,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (res instanceof Response) return res;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst response = (await handler(context)) as Response;\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tif (config?.onError) {\n\t\t\t\ttry {\n\t\t\t\t\tconst errorResponse = await config.onError(error, request);\n\n\t\t\t\t\tif (errorResponse instanceof Response) {\n\t\t\t\t\t\treturn toResponse(errorResponse);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (isAPIError(error)) {\n\t\t\t\t\t\treturn toResponse(error);\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (config?.throwError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (isAPIError(error)) {\n\t\t\t\treturn toResponse(error);\n\t\t\t}\n\n\t\t\tconsole.error(`# SERVER_ERROR: `, error);\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 500,\n\t\t\t\tstatusText: \"Internal Server Error\",\n\t\t\t});\n\t\t}\n\t};\n\n\treturn {\n\t\thandler: async (request: Request) => {\n\t\t\tconst onReq = await config?.onRequest?.(request);\n\t\t\tif (onReq instanceof Response) {\n\t\t\t\treturn onReq;\n\t\t\t}\n\t\t\tconst req = isRequest(onReq) ? onReq : request;\n\t\t\tconst res = await processRequest(req);\n\t\t\tconst onRes = await config?.onResponse?.(res, req);\n\t\t\tif (onRes instanceof Response) {\n\t\t\t\treturn onRes;\n\t\t\t}\n\t\t\treturn res;\n\t\t},\n\t\tendpoints,\n\t};\n};\n\nexport type Router = ReturnType<typeof createRouter>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs","names":["createEndpoint","generator","getHTML","getBody","toResponse","isAPIError","isRequest"],"sources":["../src/router.ts"],"sourcesContent":["import {\n\taddRoute,\n\tcreateRouter as createRou3Router,\n\tfindAllRoutes,\n\tfindRoute,\n} from \"rou3\";\nimport { createEndpoint, type Endpoint } from \"./endpoint\";\nimport type { Middleware } from \"./middleware\";\nimport { generator, getHTML } from \"./openapi\";\nimport { toResponse } from \"./to-response\";\nimport { getBody, isAPIError, isRequest } from \"./utils\";\n\nexport interface RouterConfig {\n\tthrowError?: boolean;\n\tbasePath?: string;\n\trouterMiddleware?: Array<{\n\t\tpath: string;\n\t\tmiddleware: Middleware;\n\t}>;\n\t/**\n\t * additional Context that needs to passed to endpoints\n\t *\n\t * this will be available on `ctx.context` on endpoints\n\t */\n\trouterContext?: Record<string, any>;\n\t/**\n\t * A callback to run before any response\n\t */\n\tonResponse?: (response: Response, request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run before any request\n\t */\n\tonRequest?: (request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run when an error is thrown in the router or middleware.\n\t *\n\t * @param error - the error that was thrown in the router or middleware.\n\t * @returns a Response object that will be returned to the client.\n\t */\n\tonError?: (\n\t\terror: unknown,\n\t\trequest: Request,\n\t) => void | Promise<void> | Response | Promise<Response>;\n\t/**\n\t * List of allowed media types (MIME types) for the router\n\t *\n\t * if provided, only the media types in the list will be allowed to be passed in the body.\n\t *\n\t * If an endpoint has allowed media types, it will override the router's allowed media types.\n\t *\n\t * @example\n\t * ```ts\n\t * const router = createRouter({\n\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t * \t})\n\t */\n\tallowedMediaTypes?: string[];\n\t/**\n\t * Skip trailing slashes\n\t *\n\t * @default false\n\t */\n\tskipTrailingSlashes?: boolean;\n\t/**\n\t * Open API route configuration\n\t */\n\topenapi?: {\n\t\t/**\n\t\t * Disable openapi route\n\t\t *\n\t\t * @default false\n\t\t */\n\t\tdisabled?: boolean;\n\t\t/**\n\t\t * A path to display open api using scalar\n\t\t *\n\t\t * @default \"/api/reference\"\n\t\t */\n\t\tpath?: string;\n\t\t/**\n\t\t * Scalar Configuration\n\t\t */\n\t\tscalar?: {\n\t\t\t/**\n\t\t\t * Title\n\t\t\t * @default \"Open API Reference\"\n\t\t\t */\n\t\t\ttitle?: string;\n\t\t\t/**\n\t\t\t * Description\n\t\t\t *\n\t\t\t * @default \"Better Call Open API Reference\"\n\t\t\t */\n\t\t\tdescription?: string;\n\t\t\t/**\n\t\t\t * Logo URL\n\t\t\t */\n\t\t\tlogo?: string;\n\t\t\t/**\n\t\t\t * Scalar theme\n\t\t\t * @default \"saturn\"\n\t\t\t */\n\t\t\ttheme?: string;\n\t\t};\n\t};\n}\n\nexport const createRouter = <\n\tE extends Record<string, Endpoint>,\n\tConfig extends RouterConfig,\n>(\n\tendpoints: E,\n\tconfig?: Config,\n) => {\n\tif (!config?.openapi?.disabled) {\n\t\tconst openapi = {\n\t\t\tpath: \"/api/reference\",\n\t\t\t...config?.openapi,\n\t\t};\n\t\t//@ts-expect-error\n\t\tendpoints[\"openapi\"] = createEndpoint(\n\t\t\topenapi.path,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t},\n\t\t\tasync (c) => {\n\t\t\t\tconst schema = await generator(endpoints as any);\n\t\t\t\treturn new Response(getHTML(schema, openapi.scalar), {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t}\n\tconst router = createRou3Router();\n\tconst middlewareRouter = createRou3Router();\n\n\tfor (const endpoint of Object.values(endpoints)) {\n\t\tif (!endpoint.options || !endpoint.path) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (endpoint.options?.metadata?.SERVER_ONLY) continue;\n\n\t\tconst methods = Array.isArray(endpoint.options?.method)\n\t\t\t? endpoint.options.method\n\t\t\t: [endpoint.options?.method];\n\n\t\tfor (const method of methods) {\n\t\t\taddRoute(router, method as string, endpoint.path, endpoint);\n\t\t}\n\t}\n\n\tif (config?.routerMiddleware?.length) {\n\t\tfor (const { path, middleware } of config.routerMiddleware) {\n\t\t\taddRoute(middlewareRouter, \"*\", path, middleware);\n\t\t}\n\t}\n\n\t// Normalize the configured base path once. `basePath` is configuration, not\n\t// per-request input, so trailing-slash normalization belongs here rather than\n\t// in the request hot path. An empty result (unset, \"/\", or all slashes) means\n\t// \"no base path\": route on the full pathname.\n\tconst basePath =\n\t\tconfig?.basePath && config.basePath !== \"/\"\n\t\t\t? config.basePath.replace(/\\/+$/, \"\")\n\t\t\t: \"\";\n\n\tconst processRequest = async (request: Request) => {\n\t\tconst url = new URL(request.url);\n\t\tconst pathname = url.pathname;\n\t\t// Strip `basePath` only when it is a leading, \"/\"-boundary prefix of the\n\t\t// request pathname. A pathname that does not start with the configured\n\t\t// basePath is outside this router and resolves to a 404, so a path like\n\t\t// \"/x/api/test\" never reaches \"/test\". The \"/\" boundary also rejects a\n\t\t// path where basePath is only a leading substring, not a full segment.\n\t\t// The previous implementation stripped basePath wherever it occurred\n\t\t// (`pathname.split(basePath)`).\n\t\tlet path: string;\n\t\tif (basePath) {\n\t\t\tif (!pathname.startsWith(`${basePath}/`)) {\n\t\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t\t}\n\t\t\tpath = pathname.slice(basePath.length);\n\t\t} else {\n\t\t\tpath = pathname;\n\t\t}\n\n\t\t// Reject empty paths and paths with consecutive slashes.\n\t\tif (path.length === 0 || /\\/{2,}/.test(path)) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\n\t\tconst route = findRoute(router, request.method, path) as {\n\t\t\tdata: Endpoint & { path: string };\n\t\t\tparams: Record<string, string>;\n\t\t};\n\t\tconst hasTrailingSlash = path.endsWith(\"/\");\n\t\tconst routeHasTrailingSlash = route?.data?.path?.endsWith(\"/\");\n\n\t\t// If the path has a trailing slash and the route doesn't have a trailing slash and skipTrailingSlashes is not set, return 404\n\t\tif (\n\t\t\thasTrailingSlash !== routeHasTrailingSlash &&\n\t\t\t!config?.skipTrailingSlashes\n\t\t) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\t\tif (!route?.data)\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\n\t\tconst query: Record<string, string | string[]> = {};\n\t\turl.searchParams.forEach((value, key) => {\n\t\t\tif (key in query) {\n\t\t\t\tif (Array.isArray(query[key])) {\n\t\t\t\t\t(query[key] as string[]).push(value);\n\t\t\t\t} else {\n\t\t\t\t\tquery[key] = [query[key] as string, value];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[key] = value;\n\t\t\t}\n\t\t});\n\n\t\tconst handler = route.data as Endpoint;\n\n\t\ttry {\n\t\t\t// Determine which allowedMediaTypes to use: endpoint-level overrides router-level\n\t\t\tconst allowedMediaTypes =\n\t\t\t\thandler.options.metadata?.allowedMediaTypes ||\n\t\t\t\tconfig?.allowedMediaTypes;\n\t\t\tconst context = {\n\t\t\t\tpath,\n\t\t\t\tmethod: request.method as \"GET\",\n\t\t\t\theaders: request.headers,\n\t\t\t\tparams: route.params\n\t\t\t\t\t? (JSON.parse(JSON.stringify(route.params)) as any)\n\t\t\t\t\t: {},\n\t\t\t\trequest: request,\n\t\t\t\tbody: handler.options.disableBody\n\t\t\t\t\t? undefined\n\t\t\t\t\t: await getBody(\n\t\t\t\t\t\t\thandler.options.cloneRequest ? request.clone() : request,\n\t\t\t\t\t\t\tallowedMediaTypes,\n\t\t\t\t\t\t),\n\t\t\t\tquery,\n\t\t\t\t_flag: \"router\" as const,\n\t\t\t\tasResponse: true,\n\t\t\t\tcontext: config?.routerContext,\n\t\t\t};\n\t\t\tconst middlewareRoutes = findAllRoutes(middlewareRouter, \"*\", path);\n\t\t\tif (middlewareRoutes?.length) {\n\t\t\t\tfor (const { data: middleware, params } of middlewareRoutes) {\n\t\t\t\t\tconst res = await (middleware as Endpoint)({\n\t\t\t\t\t\t...context,\n\t\t\t\t\t\tparams,\n\t\t\t\t\t\tasResponse: false,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (res instanceof Response) return res;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst response = (await handler(context)) as Response;\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tif (config?.onError) {\n\t\t\t\ttry {\n\t\t\t\t\tconst errorResponse = await config.onError(error, request);\n\n\t\t\t\t\tif (errorResponse instanceof Response) {\n\t\t\t\t\t\treturn toResponse(errorResponse);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (isAPIError(error)) {\n\t\t\t\t\t\treturn toResponse(error);\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (config?.throwError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (isAPIError(error)) {\n\t\t\t\treturn toResponse(error);\n\t\t\t}\n\n\t\t\tconsole.error(`# SERVER_ERROR: `, error);\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 500,\n\t\t\t\tstatusText: \"Internal Server Error\",\n\t\t\t});\n\t\t}\n\t};\n\n\treturn {\n\t\thandler: async (request: Request) => {\n\t\t\tconst onReq = await config?.onRequest?.(request);\n\t\t\tif (onReq instanceof Response) {\n\t\t\t\treturn onReq;\n\t\t\t}\n\t\t\tconst req = isRequest(onReq) ? onReq : request;\n\t\t\tconst res = await processRequest(req);\n\t\t\tconst onRes = await config?.onResponse?.(res, req);\n\t\t\tif (onRes instanceof Response) {\n\t\t\t\treturn onRes;\n\t\t\t}\n\t\t\treturn res;\n\t\t},\n\t\tendpoints,\n\t};\n};\n\nexport type Router = ReturnType<typeof createRouter>;\n"],"mappings":"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"}
package/dist/router.d.cts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Middleware } from "./middleware.cjs";
2
2
  import { Endpoint } from "./endpoint.cjs";
3
-
4
3
  //#region src/router.d.ts
5
4
  interface RouterConfig {
6
5
  throwError?: boolean;
package/dist/router.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Middleware } from "./middleware.mjs";
2
2
  import { Endpoint } from "./endpoint.mjs";
3
-
4
3
  //#region src/router.d.ts
5
4
  interface RouterConfig {
6
5
  throwError?: boolean;
package/dist/router.mjs CHANGED
@@ -3,7 +3,6 @@ import { toResponse } from "./to-response.mjs";
3
3
  import { createEndpoint } from "./endpoint.mjs";
4
4
  import { generator, getHTML } from "./openapi.mjs";
5
5
  import { addRoute, createRouter, findAllRoutes, findRoute } from "rou3";
6
-
7
6
  //#region src/router.ts
8
7
  const createRouter$1 = (endpoints, config) => {
9
8
  if (!config?.openapi?.disabled) {
@@ -25,19 +24,19 @@ const createRouter$1 = (endpoints, config) => {
25
24
  for (const method of methods) addRoute(router, method, endpoint.path, endpoint);
26
25
  }
27
26
  if (config?.routerMiddleware?.length) for (const { path, middleware } of config.routerMiddleware) addRoute(middlewareRouter, "*", path, middleware);
27
+ const basePath = config?.basePath && config.basePath !== "/" ? config.basePath.replace(/\/+$/, "") : "";
28
28
  const processRequest = async (request) => {
29
29
  const url = new URL(request.url);
30
30
  const pathname = url.pathname;
31
- const path = config?.basePath && config.basePath !== "/" ? pathname.split(config.basePath).reduce((acc, curr, index) => {
32
- if (index !== 0) if (index > 1) acc.push(`${config.basePath}${curr}`);
33
- else acc.push(curr);
34
- return acc;
35
- }, []).join("") : url.pathname;
36
- if (!path?.length) return new Response(null, {
37
- status: 404,
38
- statusText: "Not Found"
39
- });
40
- if (/\/{2,}/.test(path)) return new Response(null, {
31
+ let path;
32
+ if (basePath) {
33
+ if (!pathname.startsWith(`${basePath}/`)) return new Response(null, {
34
+ status: 404,
35
+ statusText: "Not Found"
36
+ });
37
+ path = pathname.slice(basePath.length);
38
+ } else path = pathname;
39
+ if (path.length === 0 || /\/{2,}/.test(path)) return new Response(null, {
41
40
  status: 404,
42
41
  statusText: "Not Found"
43
42
  });
@@ -111,7 +110,7 @@ const createRouter$1 = (endpoints, config) => {
111
110
  endpoints
112
111
  };
113
112
  };
114
-
115
113
  //#endregion
116
114
  export { createRouter$1 as createRouter };
115
+
117
116
  //# sourceMappingURL=router.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.mjs","names":["createRouter","createRou3Router"],"sources":["../src/router.ts"],"sourcesContent":["import {\n\taddRoute,\n\tcreateRouter as createRou3Router,\n\tfindAllRoutes,\n\tfindRoute,\n} from \"rou3\";\nimport { createEndpoint, type Endpoint } from \"./endpoint\";\nimport type { Middleware } from \"./middleware\";\nimport { generator, getHTML } from \"./openapi\";\nimport { toResponse } from \"./to-response\";\nimport { getBody, isAPIError, isRequest } from \"./utils\";\n\nexport interface RouterConfig {\n\tthrowError?: boolean;\n\tbasePath?: string;\n\trouterMiddleware?: Array<{\n\t\tpath: string;\n\t\tmiddleware: Middleware;\n\t}>;\n\t/**\n\t * additional Context that needs to passed to endpoints\n\t *\n\t * this will be available on `ctx.context` on endpoints\n\t */\n\trouterContext?: Record<string, any>;\n\t/**\n\t * A callback to run before any response\n\t */\n\tonResponse?: (response: Response, request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run before any request\n\t */\n\tonRequest?: (request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run when an error is thrown in the router or middleware.\n\t *\n\t * @param error - the error that was thrown in the router or middleware.\n\t * @returns a Response object that will be returned to the client.\n\t */\n\tonError?: (\n\t\terror: unknown,\n\t\trequest: Request,\n\t) => void | Promise<void> | Response | Promise<Response>;\n\t/**\n\t * List of allowed media types (MIME types) for the router\n\t *\n\t * if provided, only the media types in the list will be allowed to be passed in the body.\n\t *\n\t * If an endpoint has allowed media types, it will override the router's allowed media types.\n\t *\n\t * @example\n\t * ```ts\n\t * const router = createRouter({\n\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t * \t})\n\t */\n\tallowedMediaTypes?: string[];\n\t/**\n\t * Skip trailing slashes\n\t *\n\t * @default false\n\t */\n\tskipTrailingSlashes?: boolean;\n\t/**\n\t * Open API route configuration\n\t */\n\topenapi?: {\n\t\t/**\n\t\t * Disable openapi route\n\t\t *\n\t\t * @default false\n\t\t */\n\t\tdisabled?: boolean;\n\t\t/**\n\t\t * A path to display open api using scalar\n\t\t *\n\t\t * @default \"/api/reference\"\n\t\t */\n\t\tpath?: string;\n\t\t/**\n\t\t * Scalar Configuration\n\t\t */\n\t\tscalar?: {\n\t\t\t/**\n\t\t\t * Title\n\t\t\t * @default \"Open API Reference\"\n\t\t\t */\n\t\t\ttitle?: string;\n\t\t\t/**\n\t\t\t * Description\n\t\t\t *\n\t\t\t * @default \"Better Call Open API Reference\"\n\t\t\t */\n\t\t\tdescription?: string;\n\t\t\t/**\n\t\t\t * Logo URL\n\t\t\t */\n\t\t\tlogo?: string;\n\t\t\t/**\n\t\t\t * Scalar theme\n\t\t\t * @default \"saturn\"\n\t\t\t */\n\t\t\ttheme?: string;\n\t\t};\n\t};\n}\n\nexport const createRouter = <\n\tE extends Record<string, Endpoint>,\n\tConfig extends RouterConfig,\n>(\n\tendpoints: E,\n\tconfig?: Config,\n) => {\n\tif (!config?.openapi?.disabled) {\n\t\tconst openapi = {\n\t\t\tpath: \"/api/reference\",\n\t\t\t...config?.openapi,\n\t\t};\n\t\t//@ts-expect-error\n\t\tendpoints[\"openapi\"] = createEndpoint(\n\t\t\topenapi.path,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t},\n\t\t\tasync (c) => {\n\t\t\t\tconst schema = await generator(endpoints as any);\n\t\t\t\treturn new Response(getHTML(schema, openapi.scalar), {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t}\n\tconst router = createRou3Router();\n\tconst middlewareRouter = createRou3Router();\n\n\tfor (const endpoint of Object.values(endpoints)) {\n\t\tif (!endpoint.options || !endpoint.path) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (endpoint.options?.metadata?.SERVER_ONLY) continue;\n\n\t\tconst methods = Array.isArray(endpoint.options?.method)\n\t\t\t? endpoint.options.method\n\t\t\t: [endpoint.options?.method];\n\n\t\tfor (const method of methods) {\n\t\t\taddRoute(router, method as string, endpoint.path, endpoint);\n\t\t}\n\t}\n\n\tif (config?.routerMiddleware?.length) {\n\t\tfor (const { path, middleware } of config.routerMiddleware) {\n\t\t\taddRoute(middlewareRouter, \"*\", path, middleware);\n\t\t}\n\t}\n\n\tconst processRequest = async (request: Request) => {\n\t\tconst url = new URL(request.url);\n\t\tconst pathname = url.pathname;\n\t\tconst path =\n\t\t\tconfig?.basePath && config.basePath !== \"/\"\n\t\t\t\t? pathname\n\t\t\t\t\t\t.split(config.basePath)\n\t\t\t\t\t\t.reduce((acc, curr, index) => {\n\t\t\t\t\t\t\tif (index !== 0) {\n\t\t\t\t\t\t\t\tif (index > 1) {\n\t\t\t\t\t\t\t\t\tacc.push(`${config.basePath}${curr}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tacc.push(curr);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn acc;\n\t\t\t\t\t\t}, [] as string[])\n\t\t\t\t\t\t.join(\"\")\n\t\t\t\t: url.pathname;\n\t\tif (!path?.length) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\n\t\t// Reject paths with consecutive slashes\n\t\tif (/\\/{2,}/.test(path)) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\n\t\tconst route = findRoute(router, request.method, path) as {\n\t\t\tdata: Endpoint & { path: string };\n\t\t\tparams: Record<string, string>;\n\t\t};\n\t\tconst hasTrailingSlash = path.endsWith(\"/\");\n\t\tconst routeHasTrailingSlash = route?.data?.path?.endsWith(\"/\");\n\n\t\t// If the path has a trailing slash and the route doesn't have a trailing slash and skipTrailingSlashes is not set, return 404\n\t\tif (\n\t\t\thasTrailingSlash !== routeHasTrailingSlash &&\n\t\t\t!config?.skipTrailingSlashes\n\t\t) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\t\tif (!route?.data)\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\n\t\tconst query: Record<string, string | string[]> = {};\n\t\turl.searchParams.forEach((value, key) => {\n\t\t\tif (key in query) {\n\t\t\t\tif (Array.isArray(query[key])) {\n\t\t\t\t\t(query[key] as string[]).push(value);\n\t\t\t\t} else {\n\t\t\t\t\tquery[key] = [query[key] as string, value];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[key] = value;\n\t\t\t}\n\t\t});\n\n\t\tconst handler = route.data as Endpoint;\n\n\t\ttry {\n\t\t\t// Determine which allowedMediaTypes to use: endpoint-level overrides router-level\n\t\t\tconst allowedMediaTypes =\n\t\t\t\thandler.options.metadata?.allowedMediaTypes ||\n\t\t\t\tconfig?.allowedMediaTypes;\n\t\t\tconst context = {\n\t\t\t\tpath,\n\t\t\t\tmethod: request.method as \"GET\",\n\t\t\t\theaders: request.headers,\n\t\t\t\tparams: route.params\n\t\t\t\t\t? (JSON.parse(JSON.stringify(route.params)) as any)\n\t\t\t\t\t: {},\n\t\t\t\trequest: request,\n\t\t\t\tbody: handler.options.disableBody\n\t\t\t\t\t? undefined\n\t\t\t\t\t: await getBody(\n\t\t\t\t\t\t\thandler.options.cloneRequest ? request.clone() : request,\n\t\t\t\t\t\t\tallowedMediaTypes,\n\t\t\t\t\t\t),\n\t\t\t\tquery,\n\t\t\t\t_flag: \"router\" as const,\n\t\t\t\tasResponse: true,\n\t\t\t\tcontext: config?.routerContext,\n\t\t\t};\n\t\t\tconst middlewareRoutes = findAllRoutes(middlewareRouter, \"*\", path);\n\t\t\tif (middlewareRoutes?.length) {\n\t\t\t\tfor (const { data: middleware, params } of middlewareRoutes) {\n\t\t\t\t\tconst res = await (middleware as Endpoint)({\n\t\t\t\t\t\t...context,\n\t\t\t\t\t\tparams,\n\t\t\t\t\t\tasResponse: false,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (res instanceof Response) return res;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst response = (await handler(context)) as Response;\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tif (config?.onError) {\n\t\t\t\ttry {\n\t\t\t\t\tconst errorResponse = await config.onError(error, request);\n\n\t\t\t\t\tif (errorResponse instanceof Response) {\n\t\t\t\t\t\treturn toResponse(errorResponse);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (isAPIError(error)) {\n\t\t\t\t\t\treturn toResponse(error);\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (config?.throwError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (isAPIError(error)) {\n\t\t\t\treturn toResponse(error);\n\t\t\t}\n\n\t\t\tconsole.error(`# SERVER_ERROR: `, error);\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 500,\n\t\t\t\tstatusText: \"Internal Server Error\",\n\t\t\t});\n\t\t}\n\t};\n\n\treturn {\n\t\thandler: async (request: Request) => {\n\t\t\tconst onReq = await config?.onRequest?.(request);\n\t\t\tif (onReq instanceof Response) {\n\t\t\t\treturn onReq;\n\t\t\t}\n\t\t\tconst req = isRequest(onReq) ? onReq : request;\n\t\t\tconst res = await processRequest(req);\n\t\t\tconst onRes = await config?.onResponse?.(res, req);\n\t\t\tif (onRes instanceof Response) {\n\t\t\t\treturn onRes;\n\t\t\t}\n\t\t\treturn res;\n\t\t},\n\t\tendpoints,\n\t};\n};\n\nexport type Router = ReturnType<typeof createRouter>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"router.mjs","names":["createRouter","createRou3Router"],"sources":["../src/router.ts"],"sourcesContent":["import {\n\taddRoute,\n\tcreateRouter as createRou3Router,\n\tfindAllRoutes,\n\tfindRoute,\n} from \"rou3\";\nimport { createEndpoint, type Endpoint } from \"./endpoint\";\nimport type { Middleware } from \"./middleware\";\nimport { generator, getHTML } from \"./openapi\";\nimport { toResponse } from \"./to-response\";\nimport { getBody, isAPIError, isRequest } from \"./utils\";\n\nexport interface RouterConfig {\n\tthrowError?: boolean;\n\tbasePath?: string;\n\trouterMiddleware?: Array<{\n\t\tpath: string;\n\t\tmiddleware: Middleware;\n\t}>;\n\t/**\n\t * additional Context that needs to passed to endpoints\n\t *\n\t * this will be available on `ctx.context` on endpoints\n\t */\n\trouterContext?: Record<string, any>;\n\t/**\n\t * A callback to run before any response\n\t */\n\tonResponse?: (response: Response, request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run before any request\n\t */\n\tonRequest?: (request: Request) => any | Promise<any>;\n\t/**\n\t * A callback to run when an error is thrown in the router or middleware.\n\t *\n\t * @param error - the error that was thrown in the router or middleware.\n\t * @returns a Response object that will be returned to the client.\n\t */\n\tonError?: (\n\t\terror: unknown,\n\t\trequest: Request,\n\t) => void | Promise<void> | Response | Promise<Response>;\n\t/**\n\t * List of allowed media types (MIME types) for the router\n\t *\n\t * if provided, only the media types in the list will be allowed to be passed in the body.\n\t *\n\t * If an endpoint has allowed media types, it will override the router's allowed media types.\n\t *\n\t * @example\n\t * ```ts\n\t * const router = createRouter({\n\t * \t\tallowedMediaTypes: [\"application/json\", \"application/x-www-form-urlencoded\"],\n\t * \t})\n\t */\n\tallowedMediaTypes?: string[];\n\t/**\n\t * Skip trailing slashes\n\t *\n\t * @default false\n\t */\n\tskipTrailingSlashes?: boolean;\n\t/**\n\t * Open API route configuration\n\t */\n\topenapi?: {\n\t\t/**\n\t\t * Disable openapi route\n\t\t *\n\t\t * @default false\n\t\t */\n\t\tdisabled?: boolean;\n\t\t/**\n\t\t * A path to display open api using scalar\n\t\t *\n\t\t * @default \"/api/reference\"\n\t\t */\n\t\tpath?: string;\n\t\t/**\n\t\t * Scalar Configuration\n\t\t */\n\t\tscalar?: {\n\t\t\t/**\n\t\t\t * Title\n\t\t\t * @default \"Open API Reference\"\n\t\t\t */\n\t\t\ttitle?: string;\n\t\t\t/**\n\t\t\t * Description\n\t\t\t *\n\t\t\t * @default \"Better Call Open API Reference\"\n\t\t\t */\n\t\t\tdescription?: string;\n\t\t\t/**\n\t\t\t * Logo URL\n\t\t\t */\n\t\t\tlogo?: string;\n\t\t\t/**\n\t\t\t * Scalar theme\n\t\t\t * @default \"saturn\"\n\t\t\t */\n\t\t\ttheme?: string;\n\t\t};\n\t};\n}\n\nexport const createRouter = <\n\tE extends Record<string, Endpoint>,\n\tConfig extends RouterConfig,\n>(\n\tendpoints: E,\n\tconfig?: Config,\n) => {\n\tif (!config?.openapi?.disabled) {\n\t\tconst openapi = {\n\t\t\tpath: \"/api/reference\",\n\t\t\t...config?.openapi,\n\t\t};\n\t\t//@ts-expect-error\n\t\tendpoints[\"openapi\"] = createEndpoint(\n\t\t\topenapi.path,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t},\n\t\t\tasync (c) => {\n\t\t\t\tconst schema = await generator(endpoints as any);\n\t\t\t\treturn new Response(getHTML(schema, openapi.scalar), {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t}\n\tconst router = createRou3Router();\n\tconst middlewareRouter = createRou3Router();\n\n\tfor (const endpoint of Object.values(endpoints)) {\n\t\tif (!endpoint.options || !endpoint.path) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (endpoint.options?.metadata?.SERVER_ONLY) continue;\n\n\t\tconst methods = Array.isArray(endpoint.options?.method)\n\t\t\t? endpoint.options.method\n\t\t\t: [endpoint.options?.method];\n\n\t\tfor (const method of methods) {\n\t\t\taddRoute(router, method as string, endpoint.path, endpoint);\n\t\t}\n\t}\n\n\tif (config?.routerMiddleware?.length) {\n\t\tfor (const { path, middleware } of config.routerMiddleware) {\n\t\t\taddRoute(middlewareRouter, \"*\", path, middleware);\n\t\t}\n\t}\n\n\t// Normalize the configured base path once. `basePath` is configuration, not\n\t// per-request input, so trailing-slash normalization belongs here rather than\n\t// in the request hot path. An empty result (unset, \"/\", or all slashes) means\n\t// \"no base path\": route on the full pathname.\n\tconst basePath =\n\t\tconfig?.basePath && config.basePath !== \"/\"\n\t\t\t? config.basePath.replace(/\\/+$/, \"\")\n\t\t\t: \"\";\n\n\tconst processRequest = async (request: Request) => {\n\t\tconst url = new URL(request.url);\n\t\tconst pathname = url.pathname;\n\t\t// Strip `basePath` only when it is a leading, \"/\"-boundary prefix of the\n\t\t// request pathname. A pathname that does not start with the configured\n\t\t// basePath is outside this router and resolves to a 404, so a path like\n\t\t// \"/x/api/test\" never reaches \"/test\". The \"/\" boundary also rejects a\n\t\t// path where basePath is only a leading substring, not a full segment.\n\t\t// The previous implementation stripped basePath wherever it occurred\n\t\t// (`pathname.split(basePath)`).\n\t\tlet path: string;\n\t\tif (basePath) {\n\t\t\tif (!pathname.startsWith(`${basePath}/`)) {\n\t\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t\t}\n\t\t\tpath = pathname.slice(basePath.length);\n\t\t} else {\n\t\t\tpath = pathname;\n\t\t}\n\n\t\t// Reject empty paths and paths with consecutive slashes.\n\t\tif (path.length === 0 || /\\/{2,}/.test(path)) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\n\t\tconst route = findRoute(router, request.method, path) as {\n\t\t\tdata: Endpoint & { path: string };\n\t\t\tparams: Record<string, string>;\n\t\t};\n\t\tconst hasTrailingSlash = path.endsWith(\"/\");\n\t\tconst routeHasTrailingSlash = route?.data?.path?.endsWith(\"/\");\n\n\t\t// If the path has a trailing slash and the route doesn't have a trailing slash and skipTrailingSlashes is not set, return 404\n\t\tif (\n\t\t\thasTrailingSlash !== routeHasTrailingSlash &&\n\t\t\t!config?.skipTrailingSlashes\n\t\t) {\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\t\t}\n\t\tif (!route?.data)\n\t\t\treturn new Response(null, { status: 404, statusText: \"Not Found\" });\n\n\t\tconst query: Record<string, string | string[]> = {};\n\t\turl.searchParams.forEach((value, key) => {\n\t\t\tif (key in query) {\n\t\t\t\tif (Array.isArray(query[key])) {\n\t\t\t\t\t(query[key] as string[]).push(value);\n\t\t\t\t} else {\n\t\t\t\t\tquery[key] = [query[key] as string, value];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[key] = value;\n\t\t\t}\n\t\t});\n\n\t\tconst handler = route.data as Endpoint;\n\n\t\ttry {\n\t\t\t// Determine which allowedMediaTypes to use: endpoint-level overrides router-level\n\t\t\tconst allowedMediaTypes =\n\t\t\t\thandler.options.metadata?.allowedMediaTypes ||\n\t\t\t\tconfig?.allowedMediaTypes;\n\t\t\tconst context = {\n\t\t\t\tpath,\n\t\t\t\tmethod: request.method as \"GET\",\n\t\t\t\theaders: request.headers,\n\t\t\t\tparams: route.params\n\t\t\t\t\t? (JSON.parse(JSON.stringify(route.params)) as any)\n\t\t\t\t\t: {},\n\t\t\t\trequest: request,\n\t\t\t\tbody: handler.options.disableBody\n\t\t\t\t\t? undefined\n\t\t\t\t\t: await getBody(\n\t\t\t\t\t\t\thandler.options.cloneRequest ? request.clone() : request,\n\t\t\t\t\t\t\tallowedMediaTypes,\n\t\t\t\t\t\t),\n\t\t\t\tquery,\n\t\t\t\t_flag: \"router\" as const,\n\t\t\t\tasResponse: true,\n\t\t\t\tcontext: config?.routerContext,\n\t\t\t};\n\t\t\tconst middlewareRoutes = findAllRoutes(middlewareRouter, \"*\", path);\n\t\t\tif (middlewareRoutes?.length) {\n\t\t\t\tfor (const { data: middleware, params } of middlewareRoutes) {\n\t\t\t\t\tconst res = await (middleware as Endpoint)({\n\t\t\t\t\t\t...context,\n\t\t\t\t\t\tparams,\n\t\t\t\t\t\tasResponse: false,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (res instanceof Response) return res;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst response = (await handler(context)) as Response;\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tif (config?.onError) {\n\t\t\t\ttry {\n\t\t\t\t\tconst errorResponse = await config.onError(error, request);\n\n\t\t\t\t\tif (errorResponse instanceof Response) {\n\t\t\t\t\t\treturn toResponse(errorResponse);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (isAPIError(error)) {\n\t\t\t\t\t\treturn toResponse(error);\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (config?.throwError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (isAPIError(error)) {\n\t\t\t\treturn toResponse(error);\n\t\t\t}\n\n\t\t\tconsole.error(`# SERVER_ERROR: `, error);\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 500,\n\t\t\t\tstatusText: \"Internal Server Error\",\n\t\t\t});\n\t\t}\n\t};\n\n\treturn {\n\t\thandler: async (request: Request) => {\n\t\t\tconst onReq = await config?.onRequest?.(request);\n\t\t\tif (onReq instanceof Response) {\n\t\t\t\treturn onReq;\n\t\t\t}\n\t\t\tconst req = isRequest(onReq) ? onReq : request;\n\t\t\tconst res = await processRequest(req);\n\t\t\tconst onRes = await config?.onResponse?.(res, req);\n\t\t\tif (onRes instanceof Response) {\n\t\t\t\treturn onRes;\n\t\t\t}\n\t\t\treturn res;\n\t\t},\n\t\tendpoints,\n\t};\n};\n\nexport type Router = ReturnType<typeof createRouter>;\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
- require('./error.cjs');
2
- const require_utils = require('./utils.cjs');
3
-
1
+ require("./error.cjs");
2
+ const require_utils = require("./utils.cjs");
4
3
  //#region src/to-response.ts
5
4
  function isJSONSerializable(value) {
6
5
  if (value === void 0) return false;
@@ -53,7 +52,7 @@ function isJSONResponse(value) {
53
52
  * - RFC 6265 §5.4 (Cookie)
54
53
  * - RFC 6454 (Origin)
55
54
  */
56
- const REQUEST_ONLY_HEADERS = new Set([
55
+ const REQUEST_ONLY_HEADERS = /* @__PURE__ */ new Set([
57
56
  "host",
58
57
  "user-agent",
59
58
  "referer",
@@ -160,7 +159,7 @@ function toResponse(data, init) {
160
159
  headers
161
160
  });
162
161
  }
163
-
164
162
  //#endregion
165
163
  exports.toResponse = toResponse;
164
+
166
165
  //# sourceMappingURL=to-response.cjs.map
@@ -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(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":";;;;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,cAAc,KAAsB;CAC5C,MAAM,0BAAU,IAAI,SAAyB;CAC7C,MAAM,sBAAM,IAAI,SAAyB;CACzC,IAAI,KAAK;CAET,MAAM,cAAc,OAAe,WAA4B;EAC9D,IAAI,OAA2B;AAC/B,SAAO,MAAM;AACZ,OAAI,SAAS,MAAO,QAAO;AAC3B,UAAO,QAAQ,IAAI,KAAK;;AAEzB,SAAO;;AAGR,QAAO,KAAK,UAAU,KAAK,SAAqB,MAAM,OAAO;AAC5D,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAChD,OAAI,WAAW,OAAO,KAAK,CAC1B,QAAO,iBAAiB,IAAI,IAAI,MAAM,CAAC;AAExC,WAAQ,IAAI,OAAO,KAAK;AACxB,OAAI,CAAC,IAAI,IAAI,MAAM,CAAE,KAAI,IAAI,OAAO,KAAK;;AAE1C,SAAO;GACN;;AAWH,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;;;;;;;AAStB,SAAS,YAAY,QAAiB,QAAuC;AAC5E,KAAI,CAAC,OAAQ;AACb,MAAK,MAAM,CAAC,KAAK,UAAU,IAAI,QAAQ,OAAO,CAAC,SAAS,CACvD,KAAI,IAAI,aAAa,KAAK,aACzB,QAAO,OAAO,KAAK,MAAM;KAEzB,QAAO,IAAI,KAAK,MAAM;;AAKzB,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,KAAK,SAAS,YAAY;;AAEvC,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,cAAY,SAAS,gBAAgB,QAAQ;AAC7C,cAAY,SAAS,KAAK,QAAQ;AAClC,MAAI,MAAM,SAAS;GAClB,MAAM,cAAc,IAAI,QAAQ,KAAK,QAAQ;AAC7C,2BAAwB,YAAY;AACpC,eAAY,SAAS,YAAY;;AAGlC,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,KAAIA,yBAAW,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.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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"}
@@ -1,6 +1,5 @@
1
1
  import "./error.mjs";
2
2
  import { isAPIError } from "./utils.mjs";
3
-
4
3
  //#region src/to-response.ts
5
4
  function isJSONSerializable(value) {
6
5
  if (value === void 0) return false;
@@ -53,7 +52,7 @@ function isJSONResponse(value) {
53
52
  * - RFC 6265 §5.4 (Cookie)
54
53
  * - RFC 6454 (Origin)
55
54
  */
56
- const REQUEST_ONLY_HEADERS = new Set([
55
+ const REQUEST_ONLY_HEADERS = /* @__PURE__ */ new Set([
57
56
  "host",
58
57
  "user-agent",
59
58
  "referer",
@@ -160,7 +159,7 @@ function toResponse(data, init) {
160
159
  headers
161
160
  });
162
161
  }
163
-
164
162
  //#endregion
165
163
  export { toResponse };
164
+
166
165
  //# sourceMappingURL=to-response.mjs.map
@@ -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(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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"}
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/types.d.cts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { StandardSchemaV1 } from "./standard-schema.cjs";
2
2
  import { InferParamPath, InferParamWildCard, IsEmptyObject, Prettify, UnionToIntersection } from "./helper.cjs";
3
3
  import { Middleware } from "./middleware.cjs";
4
-
5
4
  //#region src/types.d.ts
6
5
  type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
7
6
  /**
@@ -60,7 +59,7 @@ type ResolveErrorInput<S, Meta = undefined> = Meta extends {
60
59
  */
61
60
  type ResolveMetaInput<Meta> = Meta extends {
62
61
  $Infer: infer I;
63
- } ? { [K in keyof Meta]: K extends "$Infer" ? { [J in keyof I]: ResolveInferValueInput<I[J]> } : Meta[K] } : Meta;
62
+ } ? { [K in keyof Meta]: K extends "$Infer" ? { [J in keyof I]: ResolveInferValueInput<I[J]>; } : Meta[K]; } : Meta;
64
63
  /**
65
64
  * Constraint: body is `never` for GET/HEAD methods.
66
65
  */
package/dist/types.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { StandardSchemaV1 } from "./standard-schema.mjs";
2
2
  import { InferParamPath, InferParamWildCard, IsEmptyObject, Prettify, UnionToIntersection } from "./helper.mjs";
3
3
  import { Middleware } from "./middleware.mjs";
4
-
5
4
  //#region src/types.d.ts
6
5
  type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
7
6
  /**
@@ -60,7 +59,7 @@ type ResolveErrorInput<S, Meta = undefined> = Meta extends {
60
59
  */
61
60
  type ResolveMetaInput<Meta> = Meta extends {
62
61
  $Infer: infer I;
63
- } ? { [K in keyof Meta]: K extends "$Infer" ? { [J in keyof I]: ResolveInferValueInput<I[J]> } : Meta[K] } : Meta;
62
+ } ? { [K in keyof Meta]: K extends "$Infer" ? { [J in keyof I]: ResolveInferValueInput<I[J]>; } : Meta[K]; } : Meta;
64
63
  /**
65
64
  * Constraint: body is `never` for GET/HEAD methods.
66
65
  */
package/dist/utils.cjs CHANGED
@@ -1,5 +1,4 @@
1
- const require_error = require('./error.cjs');
2
-
1
+ const require_error = require("./error.cjs");
3
2
  //#region src/utils.ts
4
3
  const jsonContentTypeRegex = /^application\/([a-z0-9.+-]*\+)?json/i;
5
4
  async function getBody(request, allowedMediaTypes) {
@@ -84,11 +83,11 @@ async function tryCatch(promise) {
84
83
  function isRequest(obj) {
85
84
  return obj instanceof Request || Object.prototype.toString.call(obj) === "[object Request]";
86
85
  }
87
-
88
86
  //#endregion
89
87
  exports.getBody = getBody;
90
88
  exports.isAPIError = isAPIError;
91
89
  exports.isRequest = isRequest;
92
90
  exports.tryCatch = tryCatch;
93
91
  exports.tryDecode = tryDecode;
92
+
94
93
  //# sourceMappingURL=utils.cjs.map
@@ -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\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"}
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,cAAc,KAAK;CAC3D,MAAM,wBAAwB,YAAY,YAAY;CAEtD,IAAI,CAAC,QAAQ,MACZ;CAID,IAAI,qBAAqB,kBAAkB,SAAS,GAa/C;MAAA,CAZc,kBAAkB,MAAM,YAAY;GAErD,MAAM,4BAA4B,sBAChC,MAAM,GAAG,CAAC,CAAC,EAAE,CACb,KAAK;GACP,MAAM,oBAAoB,QAAQ,YAAY,CAAC,CAAC,KAAK;GACrD,OACC,8BAA8B,qBAC9B,0BAA0B,SAAS,iBAAiB;EAEtD,CAEa,GAAG;GACf,IAAI,CAAC,uBACJ,MAAM,IAAIA,cAAAA,SAAS,KAAK;IACvB,SAAS,4CAA4C,kBAAkB,KAAK,IAAI;IAChF,MAAM;GACP,CAAC;GAEF,MAAM,IAAIA,cAAAA,SAAS,KAAK;IACvB,SAAS,iBAAiB,YAAY,mCAAmC,kBAAkB,KAAK,IAAI;IACpG,MAAM;GACP,CAAC;EACF;;CAGD,IAAI,qBAAqB,KAAK,qBAAqB,GAClD,IAAI;EACH,OAAO,MAAM,QAAQ,KAAK;CAC3B,SAAS,GAAG;EACX,IAAI,aAAa,aAChB,MAAM,IAAIA,cAAAA,SAAS,KAAK;GACvB,SAAS;GACT,MAAM;EACP,CAAC;EAEF,MAAM;CACP;CAGD,IAAI,sBAAsB,SAAS,mCAAmC,GAAG;EACxE,MAAM,WAAW,MAAM,QAAQ,SAAS;EACxC,MAAM,SAAiC,CAAC;EACxC,SAAS,SAAS,OAAO,QAAQ;GAChC,OAAO,OAAO,MAAM,SAAS;EAC9B,CAAC;EACD,OAAO;CACR;CAEA,IAAI,sBAAsB,SAAS,qBAAqB,GAAG;EAC1D,MAAM,WAAW,MAAM,QAAQ,SAAS;EACxC,MAAM,SAA8B,CAAC;EACrC,SAAS,SAAS,OAAO,QAAQ;GAChC,OAAO,OAAO;EACf,CAAC;EACD,OAAO;CACR;CAEA,IAAI,sBAAsB,SAAS,YAAY,GAC9C,OAAO,MAAM,QAAQ,KAAK;CAG3B,IAAI,sBAAsB,SAAS,0BAA0B,GAC5D,OAAO,MAAM,QAAQ,YAAY;CAGlC,IACC,sBAAsB,SAAS,iBAAiB,KAChD,sBAAsB,SAAS,QAAQ,KACvC,sBAAsB,SAAS,QAAQ,GAGvC,OAAO,MADY,QAAQ,KAAK;CAIjC,IACC,sBAAsB,SAAS,oBAAoB,KACnD,QAAQ,gBAAgB,gBAExB,OAAO,QAAQ;CAGhB,OAAO,MAAM,QAAQ,KAAK;AAC3B;AAEA,SAAgB,WAAW,OAA+B;CACzD,OAAO,iBAAiBA,cAAAA,YAAY,OAAO,SAAS;AACrD;AAEA,SAAgB,UAAU,KAAa;CACtC,IAAI;EACH,OAAO,IAAI,SAAS,GAAG,IAAI,mBAAmB,GAAG,IAAI;CACtD,QAAQ;EACP,OAAO;CACR;AACD;AAcA,eAAsB,SACrB,SACwB;CACxB,IAAI;EAEH,OAAO;GAAE,MAAA,MADU;GACJ,OAAO;EAAK;CAC5B,SAAS,OAAO;EACf,OAAO;GAAE,MAAM;GAAa;EAAW;CACxC;AACD;;;;;;AAOA,SAAgB,UAAU,KAA8B;CACvD,OACC,eAAe,WACf,OAAO,UAAU,SAAS,KAAK,GAAG,MAAM;AAE1C"}
package/dist/utils.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { APIError } from "./error.mjs";
2
-
3
2
  //#region src/utils.ts
4
3
  const jsonContentTypeRegex = /^application\/([a-z0-9.+-]*\+)?json/i;
5
4
  async function getBody(request, allowedMediaTypes) {
@@ -84,7 +83,7 @@ async function tryCatch(promise) {
84
83
  function isRequest(obj) {
85
84
  return obj instanceof Request || Object.prototype.toString.call(obj) === "[object Request]";
86
85
  }
87
-
88
86
  //#endregion
89
87
  export { getBody, isAPIError, isRequest, tryCatch, tryDecode };
88
+
90
89
  //# sourceMappingURL=utils.mjs.map
@@ -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\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"}
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,cAAc,KAAK;CAC3D,MAAM,wBAAwB,YAAY,YAAY;CAEtD,IAAI,CAAC,QAAQ,MACZ;CAID,IAAI,qBAAqB,kBAAkB,SAAS,GAa/C;MAAA,CAZc,kBAAkB,MAAM,YAAY;GAErD,MAAM,4BAA4B,sBAChC,MAAM,GAAG,CAAC,CAAC,EAAE,CACb,KAAK;GACP,MAAM,oBAAoB,QAAQ,YAAY,CAAC,CAAC,KAAK;GACrD,OACC,8BAA8B,qBAC9B,0BAA0B,SAAS,iBAAiB;EAEtD,CAEa,GAAG;GACf,IAAI,CAAC,uBACJ,MAAM,IAAI,SAAS,KAAK;IACvB,SAAS,4CAA4C,kBAAkB,KAAK,IAAI;IAChF,MAAM;GACP,CAAC;GAEF,MAAM,IAAI,SAAS,KAAK;IACvB,SAAS,iBAAiB,YAAY,mCAAmC,kBAAkB,KAAK,IAAI;IACpG,MAAM;GACP,CAAC;EACF;;CAGD,IAAI,qBAAqB,KAAK,qBAAqB,GAClD,IAAI;EACH,OAAO,MAAM,QAAQ,KAAK;CAC3B,SAAS,GAAG;EACX,IAAI,aAAa,aAChB,MAAM,IAAI,SAAS,KAAK;GACvB,SAAS;GACT,MAAM;EACP,CAAC;EAEF,MAAM;CACP;CAGD,IAAI,sBAAsB,SAAS,mCAAmC,GAAG;EACxE,MAAM,WAAW,MAAM,QAAQ,SAAS;EACxC,MAAM,SAAiC,CAAC;EACxC,SAAS,SAAS,OAAO,QAAQ;GAChC,OAAO,OAAO,MAAM,SAAS;EAC9B,CAAC;EACD,OAAO;CACR;CAEA,IAAI,sBAAsB,SAAS,qBAAqB,GAAG;EAC1D,MAAM,WAAW,MAAM,QAAQ,SAAS;EACxC,MAAM,SAA8B,CAAC;EACrC,SAAS,SAAS,OAAO,QAAQ;GAChC,OAAO,OAAO;EACf,CAAC;EACD,OAAO;CACR;CAEA,IAAI,sBAAsB,SAAS,YAAY,GAC9C,OAAO,MAAM,QAAQ,KAAK;CAG3B,IAAI,sBAAsB,SAAS,0BAA0B,GAC5D,OAAO,MAAM,QAAQ,YAAY;CAGlC,IACC,sBAAsB,SAAS,iBAAiB,KAChD,sBAAsB,SAAS,QAAQ,KACvC,sBAAsB,SAAS,QAAQ,GAGvC,OAAO,MADY,QAAQ,KAAK;CAIjC,IACC,sBAAsB,SAAS,oBAAoB,KACnD,QAAQ,gBAAgB,gBAExB,OAAO,QAAQ;CAGhB,OAAO,MAAM,QAAQ,KAAK;AAC3B;AAEA,SAAgB,WAAW,OAA+B;CACzD,OAAO,iBAAiB,YAAY,OAAO,SAAS;AACrD;AAEA,SAAgB,UAAU,KAAa;CACtC,IAAI;EACH,OAAO,IAAI,SAAS,GAAG,IAAI,mBAAmB,GAAG,IAAI;CACtD,QAAQ;EACP,OAAO;CACR;AACD;AAcA,eAAsB,SACrB,SACwB;CACxB,IAAI;EAEH,OAAO;GAAE,MAAA,MADU;GACJ,OAAO;EAAK;CAC5B,SAAS,OAAO;EACf,OAAO;GAAE,MAAM;GAAa;EAAW;CACxC;AACD;;;;;;AAOA,SAAgB,UAAU,KAA8B;CACvD,OACC,eAAe,WACf,OAAO,UAAU,SAAS,KAAK,GAAG,MAAM;AAE1C"}
@@ -1,4 +1,3 @@
1
-
2
1
  //#region src/validator.ts
3
2
  /**
4
3
  * Runs validation on body and query
@@ -52,7 +51,7 @@ function fromError(error, validating) {
52
51
  issues: error
53
52
  };
54
53
  }
55
-
56
54
  //#endregion
57
55
  exports.runValidation = runValidation;
56
+
58
57
  //# sourceMappingURL=validator.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"validator.cjs","names":[],"sources":["../src/validator.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"./standard-schema\";\n\ntype ValidationResponse =\n\t| {\n\t\t\tdata: {\n\t\t\t\tbody: any;\n\t\t\t\tquery: any;\n\t\t\t};\n\t\t\terror: null;\n\t }\n\t| {\n\t\t\tdata: null;\n\t\t\terror: {\n\t\t\t\tmessage: string;\n\t\t\t\tissues: readonly StandardSchemaV1.Issue[];\n\t\t\t};\n\t };\n\n/**\n * Runs validation on body and query\n * @returns error and data object\n */\nexport async function runValidation(\n\toptions: {\n\t\tbody?: StandardSchemaV1;\n\t\tquery?: StandardSchemaV1;\n\t\trequireHeaders?: boolean;\n\t\trequireRequest?: boolean;\n\t},\n\tcontext: Record<string, any> = {},\n): Promise<ValidationResponse> {\n\tconst request = {\n\t\tbody: context.body,\n\t\tquery: context.query,\n\t} as {\n\t\tbody: any;\n\t\tquery: any;\n\t};\n\tif (options.body) {\n\t\tconst result = await options.body[\"~standard\"].validate(context.body);\n\t\tif (result.issues) {\n\t\t\treturn {\n\t\t\t\tdata: null,\n\t\t\t\terror: fromError(result.issues, \"body\"),\n\t\t\t};\n\t\t}\n\t\trequest.body = result.value;\n\t}\n\n\tif (options.query) {\n\t\tconst result = await options.query[\"~standard\"].validate(context.query);\n\t\tif (result.issues) {\n\t\t\treturn {\n\t\t\t\tdata: null,\n\t\t\t\terror: fromError(result.issues, \"query\"),\n\t\t\t};\n\t\t}\n\t\trequest.query = result.value;\n\t}\n\tif (options.requireHeaders && !context.headers) {\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: { message: \"Headers is required\", issues: [] },\n\t\t};\n\t}\n\tif (options.requireRequest && !context.request) {\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: { message: \"Request is required\", issues: [] },\n\t\t};\n\t}\n\treturn {\n\t\tdata: request,\n\t\terror: null,\n\t};\n}\n\nfunction fromError(\n\terror: readonly StandardSchemaV1.Issue[],\n\tvalidating: string,\n) {\n\tconst message = error\n\t\t.map((e) => {\n\t\t\treturn `[${e.path?.length ? `${validating}.` + e.path.map((x) => (typeof x === \"object\" ? x.key : x)).join(\".\") : validating}] ${e.message}`;\n\t\t})\n\t\t.join(\"; \");\n\n\treturn {\n\t\tmessage,\n\t\tissues: error,\n\t};\n}\n"],"mappings":";;;;;;AAsBA,eAAsB,cACrB,SAMA,UAA+B,EAAE,EACH;CAC9B,MAAM,UAAU;EACf,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf;AAID,KAAI,QAAQ,MAAM;EACjB,MAAM,SAAS,MAAM,QAAQ,KAAK,aAAa,SAAS,QAAQ,KAAK;AACrE,MAAI,OAAO,OACV,QAAO;GACN,MAAM;GACN,OAAO,UAAU,OAAO,QAAQ,OAAO;GACvC;AAEF,UAAQ,OAAO,OAAO;;AAGvB,KAAI,QAAQ,OAAO;EAClB,MAAM,SAAS,MAAM,QAAQ,MAAM,aAAa,SAAS,QAAQ,MAAM;AACvE,MAAI,OAAO,OACV,QAAO;GACN,MAAM;GACN,OAAO,UAAU,OAAO,QAAQ,QAAQ;GACxC;AAEF,UAAQ,QAAQ,OAAO;;AAExB,KAAI,QAAQ,kBAAkB,CAAC,QAAQ,QACtC,QAAO;EACN,MAAM;EACN,OAAO;GAAE,SAAS;GAAuB,QAAQ,EAAE;GAAE;EACrD;AAEF,KAAI,QAAQ,kBAAkB,CAAC,QAAQ,QACtC,QAAO;EACN,MAAM;EACN,OAAO;GAAE,SAAS;GAAuB,QAAQ,EAAE;GAAE;EACrD;AAEF,QAAO;EACN,MAAM;EACN,OAAO;EACP;;AAGF,SAAS,UACR,OACA,YACC;AAOD,QAAO;EACN,SAPe,MACd,KAAK,MAAM;AACX,UAAO,IAAI,EAAE,MAAM,SAAS,GAAG,WAAW,KAAK,EAAE,KAAK,KAAK,MAAO,OAAO,MAAM,WAAW,EAAE,MAAM,EAAG,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,EAAE;IAClI,CACD,KAAK,KAAK;EAIX,QAAQ;EACR"}
1
+ {"version":3,"file":"validator.cjs","names":[],"sources":["../src/validator.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"./standard-schema\";\n\ntype ValidationResponse =\n\t| {\n\t\t\tdata: {\n\t\t\t\tbody: any;\n\t\t\t\tquery: any;\n\t\t\t};\n\t\t\terror: null;\n\t }\n\t| {\n\t\t\tdata: null;\n\t\t\terror: {\n\t\t\t\tmessage: string;\n\t\t\t\tissues: readonly StandardSchemaV1.Issue[];\n\t\t\t};\n\t };\n\n/**\n * Runs validation on body and query\n * @returns error and data object\n */\nexport async function runValidation(\n\toptions: {\n\t\tbody?: StandardSchemaV1;\n\t\tquery?: StandardSchemaV1;\n\t\trequireHeaders?: boolean;\n\t\trequireRequest?: boolean;\n\t},\n\tcontext: Record<string, any> = {},\n): Promise<ValidationResponse> {\n\tconst request = {\n\t\tbody: context.body,\n\t\tquery: context.query,\n\t} as {\n\t\tbody: any;\n\t\tquery: any;\n\t};\n\tif (options.body) {\n\t\tconst result = await options.body[\"~standard\"].validate(context.body);\n\t\tif (result.issues) {\n\t\t\treturn {\n\t\t\t\tdata: null,\n\t\t\t\terror: fromError(result.issues, \"body\"),\n\t\t\t};\n\t\t}\n\t\trequest.body = result.value;\n\t}\n\n\tif (options.query) {\n\t\tconst result = await options.query[\"~standard\"].validate(context.query);\n\t\tif (result.issues) {\n\t\t\treturn {\n\t\t\t\tdata: null,\n\t\t\t\terror: fromError(result.issues, \"query\"),\n\t\t\t};\n\t\t}\n\t\trequest.query = result.value;\n\t}\n\tif (options.requireHeaders && !context.headers) {\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: { message: \"Headers is required\", issues: [] },\n\t\t};\n\t}\n\tif (options.requireRequest && !context.request) {\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: { message: \"Request is required\", issues: [] },\n\t\t};\n\t}\n\treturn {\n\t\tdata: request,\n\t\terror: null,\n\t};\n}\n\nfunction fromError(\n\terror: readonly StandardSchemaV1.Issue[],\n\tvalidating: string,\n) {\n\tconst message = error\n\t\t.map((e) => {\n\t\t\treturn `[${e.path?.length ? `${validating}.` + e.path.map((x) => (typeof x === \"object\" ? x.key : x)).join(\".\") : validating}] ${e.message}`;\n\t\t})\n\t\t.join(\"; \");\n\n\treturn {\n\t\tmessage,\n\t\tissues: error,\n\t};\n}\n"],"mappings":";;;;;AAsBA,eAAsB,cACrB,SAMA,UAA+B,CAAC,GACF;CAC9B,MAAM,UAAU;EACf,MAAM,QAAQ;EACd,OAAO,QAAQ;CAChB;CAIA,IAAI,QAAQ,MAAM;EACjB,MAAM,SAAS,MAAM,QAAQ,KAAK,YAAY,CAAC,SAAS,QAAQ,IAAI;EACpE,IAAI,OAAO,QACV,OAAO;GACN,MAAM;GACN,OAAO,UAAU,OAAO,QAAQ,MAAM;EACvC;EAED,QAAQ,OAAO,OAAO;CACvB;CAEA,IAAI,QAAQ,OAAO;EAClB,MAAM,SAAS,MAAM,QAAQ,MAAM,YAAY,CAAC,SAAS,QAAQ,KAAK;EACtE,IAAI,OAAO,QACV,OAAO;GACN,MAAM;GACN,OAAO,UAAU,OAAO,QAAQ,OAAO;EACxC;EAED,QAAQ,QAAQ,OAAO;CACxB;CACA,IAAI,QAAQ,kBAAkB,CAAC,QAAQ,SACtC,OAAO;EACN,MAAM;EACN,OAAO;GAAE,SAAS;GAAuB,QAAQ,CAAC;EAAE;CACrD;CAED,IAAI,QAAQ,kBAAkB,CAAC,QAAQ,SACtC,OAAO;EACN,MAAM;EACN,OAAO;GAAE,SAAS;GAAuB,QAAQ,CAAC;EAAE;CACrD;CAED,OAAO;EACN,MAAM;EACN,OAAO;CACR;AACD;AAEA,SAAS,UACR,OACA,YACC;CAOD,OAAO;EACN,SAPe,MACd,KAAK,MAAM;GACX,OAAO,IAAI,EAAE,MAAM,SAAS,GAAG,WAAW,KAAK,EAAE,KAAK,KAAK,MAAO,OAAO,MAAM,WAAW,EAAE,MAAM,CAAE,CAAC,CAAC,KAAK,GAAG,IAAI,WAAW,IAAI,EAAE;EACpI,CAAC,CAAC,CACD,KAAK,IAGA;EACN,QAAQ;CACT;AACD"}