better-call 1.3.6 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/router.cjs CHANGED
@@ -26,19 +26,19 @@ const createRouter = (endpoints, config) => {
26
26
  for (const method of methods) (0, rou3.addRoute)(router, method, endpoint.path, endpoint);
27
27
  }
28
28
  if (config?.routerMiddleware?.length) for (const { path, middleware } of config.routerMiddleware) (0, rou3.addRoute)(middlewareRouter, "*", path, middleware);
29
+ const basePath = config?.basePath && config.basePath !== "/" ? config.basePath.replace(/\/+$/, "") : "";
29
30
  const processRequest = async (request) => {
30
31
  const url = new URL(request.url);
31
32
  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, {
33
+ let path;
34
+ if (basePath) {
35
+ if (!pathname.startsWith(`${basePath}/`)) return new Response(null, {
36
+ status: 404,
37
+ statusText: "Not Found"
38
+ });
39
+ path = pathname.slice(basePath.length);
40
+ } else path = pathname;
41
+ if (path.length === 0 || /\/{2,}/.test(path)) return new Response(null, {
42
42
  status: 404,
43
43
  statusText: "Not Found"
44
44
  });
@@ -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 { type Endpoint, createEndpoint } 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);\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, 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":";;;;;;;;AA2GA,MAAa,gBAIZ,WACA,WACI;AACJ,KAAI,CAAC,QAAQ,SAAS,UAAU;EAC/B,MAAM,UAAU;GACf,MAAM;GACN,GAAG,QAAQ;GACX;AAED,YAAU,aAAaA,gCACtB,QAAQ,MACR,EACC,QAAQ,OACR,EACD,OAAO,MAAM;GACZ,MAAM,SAAS,MAAMC,0BAAU,UAAU;AACzC,UAAO,IAAI,SAASC,wBAAQ,QAAQ,QAAQ,OAAO,EAAE,EACpD,SAAS,EACR,gBAAgB,aAChB,EACD,CAAC;IAEH;;CAEF,MAAM,iCAA2B;CACjC,MAAM,2CAAqC;AAE3C,MAAK,MAAM,YAAY,OAAO,OAAO,UAAU,EAAE;AAChD,MAAI,CAAC,SAAS,WAAW,CAAC,SAAS,KAClC;AAED,MAAI,SAAS,SAAS,UAAU,YAAa;EAE7C,MAAM,UAAU,MAAM,QAAQ,SAAS,SAAS,OAAO,GACpD,SAAS,QAAQ,SACjB,CAAC,SAAS,SAAS,OAAO;AAE7B,OAAK,MAAM,UAAU,QACpB,oBAAS,QAAQ,QAAQ,SAAS,MAAM,SAAS;;AAInD,KAAI,QAAQ,kBAAkB,OAC7B,MAAK,MAAM,EAAE,MAAM,gBAAgB,OAAO,iBACzC,oBAAS,kBAAkB,KAAK,MAAM,WAAW;CAInD,MAAM,iBAAiB,OAAO,YAAqB;EAClD,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAChC,MAAM,WAAW,IAAI;EACrB,MAAM,OACL,QAAQ,YAAY,OAAO,aAAa,MACrC,SACC,MAAM,OAAO,SAAS,CACtB,QAAQ,KAAK,MAAM,UAAU;AAC7B,OAAI,UAAU,EACb,KAAI,QAAQ,EACX,KAAI,KAAK,GAAG,OAAO,WAAW,OAAO;OAErC,KAAI,KAAK,KAAK;AAGhB,UAAO;KACL,EAAE,CAAa,CACjB,KAAK,GAAG,GACT,IAAI;AACR,MAAI,CAAC,MAAM,OACV,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;AAIpE,MAAI,SAAS,KAAK,KAAK,CACtB,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;EAGpE,MAAM,4BAAkB,QAAQ,QAAQ,QAAQ,KAAK;AAQrD,MAJyB,KAAK,SAAS,IAAI,KACb,OAAO,MAAM,MAAM,SAAS,IAAI,IAK7D,CAAC,QAAQ,oBAET,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;AAEpE,MAAI,CAAC,OAAO,KACX,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;EAEpE,MAAM,QAA2C,EAAE;AACnD,MAAI,aAAa,SAAS,OAAO,QAAQ;AACxC,OAAI,OAAO,MACV,KAAI,MAAM,QAAQ,MAAM,KAAK,CAC5B,CAAC,MAAM,KAAkB,KAAK,MAAM;OAEpC,OAAM,OAAO,CAAC,MAAM,MAAgB,MAAM;OAG3C,OAAM,OAAO;IAEb;EAEF,MAAM,UAAU,MAAM;AAEtB,MAAI;GAEH,MAAM,oBACL,QAAQ,QAAQ,UAAU,qBAC1B,QAAQ;GACT,MAAM,UAAU;IACf;IACA,QAAQ,QAAQ;IAChB,SAAS,QAAQ;IACjB,QAAQ,MAAM,SACV,KAAK,MAAM,KAAK,UAAU,MAAM,OAAO,CAAC,GACzC,EAAE;IACI;IACT,MAAM,QAAQ,QAAQ,cACnB,SACA,MAAMC,sBACN,QAAQ,QAAQ,eAAe,QAAQ,OAAO,GAAG,SACjD,kBACA;IACH;IACA,OAAO;IACP,YAAY;IACZ,SAAS,QAAQ;IACjB;GACD,MAAM,2CAAiC,kBAAkB,KAAK,KAAK;AACnE,OAAI,kBAAkB,OACrB,MAAK,MAAM,EAAE,MAAM,YAAY,YAAY,kBAAkB;IAC5D,MAAM,MAAM,MAAO,WAAwB;KAC1C,GAAG;KACH;KACA,YAAY;KACZ,CAAC;AAEF,QAAI,eAAe,SAAU,QAAO;;AAKtC,UADkB,MAAM,QAAQ,QAAQ;WAEhC,OAAO;AACf,OAAI,QAAQ,QACX,KAAI;IACH,MAAM,gBAAgB,MAAM,OAAO,QAAQ,OAAO,QAAQ;AAE1D,QAAI,yBAAyB,SAC5B,QAAOC,+BAAW,cAAc;YAEzB,OAAO;AACf,QAAIC,yBAAW,MAAM,CACpB,QAAOD,+BAAW,MAAM;AAGzB,UAAM;;AAIR,OAAI,QAAQ,WACX,OAAM;AAGP,OAAIC,yBAAW,MAAM,CACpB,QAAOD,+BAAW,MAAM;AAGzB,WAAQ,MAAM,oBAAoB,MAAM;AACxC,UAAO,IAAI,SAAS,MAAM;IACzB,QAAQ;IACR,YAAY;IACZ,CAAC;;;AAIJ,QAAO;EACN,SAAS,OAAO,YAAqB;GACpC,MAAM,QAAQ,MAAM,QAAQ,YAAY,QAAQ;AAChD,OAAI,iBAAiB,SACpB,QAAO;GAER,MAAM,MAAME,wBAAU,MAAM,GAAG,QAAQ;GACvC,MAAM,MAAM,MAAM,eAAe,IAAI;GACrC,MAAM,QAAQ,MAAM,QAAQ,aAAa,KAAK,IAAI;AAClD,OAAI,iBAAiB,SACpB,QAAO;AAER,UAAO;;EAER;EACA"}
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 { type Endpoint, createEndpoint } 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);\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, 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.mjs CHANGED
@@ -25,19 +25,19 @@ const createRouter$1 = (endpoints, config) => {
25
25
  for (const method of methods) addRoute(router, method, endpoint.path, endpoint);
26
26
  }
27
27
  if (config?.routerMiddleware?.length) for (const { path, middleware } of config.routerMiddleware) addRoute(middlewareRouter, "*", path, middleware);
28
+ const basePath = config?.basePath && config.basePath !== "/" ? config.basePath.replace(/\/+$/, "") : "";
28
29
  const processRequest = async (request) => {
29
30
  const url = new URL(request.url);
30
31
  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, {
32
+ let path;
33
+ if (basePath) {
34
+ if (!pathname.startsWith(`${basePath}/`)) return new Response(null, {
35
+ status: 404,
36
+ statusText: "Not Found"
37
+ });
38
+ path = pathname.slice(basePath.length);
39
+ } else path = pathname;
40
+ if (path.length === 0 || /\/{2,}/.test(path)) return new Response(null, {
41
41
  status: 404,
42
42
  statusText: "Not Found"
43
43
  });
@@ -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 { type Endpoint, createEndpoint } 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);\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, 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":";;;;;;;AA2GA,MAAaA,kBAIZ,WACA,WACI;AACJ,KAAI,CAAC,QAAQ,SAAS,UAAU;EAC/B,MAAM,UAAU;GACf,MAAM;GACN,GAAG,QAAQ;GACX;AAED,YAAU,aAAa,eACtB,QAAQ,MACR,EACC,QAAQ,OACR,EACD,OAAO,MAAM;GACZ,MAAM,SAAS,MAAM,UAAU,UAAU;AACzC,UAAO,IAAI,SAAS,QAAQ,QAAQ,QAAQ,OAAO,EAAE,EACpD,SAAS,EACR,gBAAgB,aAChB,EACD,CAAC;IAEH;;CAEF,MAAM,SAASC,cAAkB;CACjC,MAAM,mBAAmBA,cAAkB;AAE3C,MAAK,MAAM,YAAY,OAAO,OAAO,UAAU,EAAE;AAChD,MAAI,CAAC,SAAS,WAAW,CAAC,SAAS,KAClC;AAED,MAAI,SAAS,SAAS,UAAU,YAAa;EAE7C,MAAM,UAAU,MAAM,QAAQ,SAAS,SAAS,OAAO,GACpD,SAAS,QAAQ,SACjB,CAAC,SAAS,SAAS,OAAO;AAE7B,OAAK,MAAM,UAAU,QACpB,UAAS,QAAQ,QAAQ,SAAS,MAAM,SAAS;;AAInD,KAAI,QAAQ,kBAAkB,OAC7B,MAAK,MAAM,EAAE,MAAM,gBAAgB,OAAO,iBACzC,UAAS,kBAAkB,KAAK,MAAM,WAAW;CAInD,MAAM,iBAAiB,OAAO,YAAqB;EAClD,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAChC,MAAM,WAAW,IAAI;EACrB,MAAM,OACL,QAAQ,YAAY,OAAO,aAAa,MACrC,SACC,MAAM,OAAO,SAAS,CACtB,QAAQ,KAAK,MAAM,UAAU;AAC7B,OAAI,UAAU,EACb,KAAI,QAAQ,EACX,KAAI,KAAK,GAAG,OAAO,WAAW,OAAO;OAErC,KAAI,KAAK,KAAK;AAGhB,UAAO;KACL,EAAE,CAAa,CACjB,KAAK,GAAG,GACT,IAAI;AACR,MAAI,CAAC,MAAM,OACV,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;AAIpE,MAAI,SAAS,KAAK,KAAK,CACtB,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;EAGpE,MAAM,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,KAAK;AAQrD,MAJyB,KAAK,SAAS,IAAI,KACb,OAAO,MAAM,MAAM,SAAS,IAAI,IAK7D,CAAC,QAAQ,oBAET,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;AAEpE,MAAI,CAAC,OAAO,KACX,QAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;GAAa,CAAC;EAEpE,MAAM,QAA2C,EAAE;AACnD,MAAI,aAAa,SAAS,OAAO,QAAQ;AACxC,OAAI,OAAO,MACV,KAAI,MAAM,QAAQ,MAAM,KAAK,CAC5B,CAAC,MAAM,KAAkB,KAAK,MAAM;OAEpC,OAAM,OAAO,CAAC,MAAM,MAAgB,MAAM;OAG3C,OAAM,OAAO;IAEb;EAEF,MAAM,UAAU,MAAM;AAEtB,MAAI;GAEH,MAAM,oBACL,QAAQ,QAAQ,UAAU,qBAC1B,QAAQ;GACT,MAAM,UAAU;IACf;IACA,QAAQ,QAAQ;IAChB,SAAS,QAAQ;IACjB,QAAQ,MAAM,SACV,KAAK,MAAM,KAAK,UAAU,MAAM,OAAO,CAAC,GACzC,EAAE;IACI;IACT,MAAM,QAAQ,QAAQ,cACnB,SACA,MAAM,QACN,QAAQ,QAAQ,eAAe,QAAQ,OAAO,GAAG,SACjD,kBACA;IACH;IACA,OAAO;IACP,YAAY;IACZ,SAAS,QAAQ;IACjB;GACD,MAAM,mBAAmB,cAAc,kBAAkB,KAAK,KAAK;AACnE,OAAI,kBAAkB,OACrB,MAAK,MAAM,EAAE,MAAM,YAAY,YAAY,kBAAkB;IAC5D,MAAM,MAAM,MAAO,WAAwB;KAC1C,GAAG;KACH;KACA,YAAY;KACZ,CAAC;AAEF,QAAI,eAAe,SAAU,QAAO;;AAKtC,UADkB,MAAM,QAAQ,QAAQ;WAEhC,OAAO;AACf,OAAI,QAAQ,QACX,KAAI;IACH,MAAM,gBAAgB,MAAM,OAAO,QAAQ,OAAO,QAAQ;AAE1D,QAAI,yBAAyB,SAC5B,QAAO,WAAW,cAAc;YAEzB,OAAO;AACf,QAAI,WAAW,MAAM,CACpB,QAAO,WAAW,MAAM;AAGzB,UAAM;;AAIR,OAAI,QAAQ,WACX,OAAM;AAGP,OAAI,WAAW,MAAM,CACpB,QAAO,WAAW,MAAM;AAGzB,WAAQ,MAAM,oBAAoB,MAAM;AACxC,UAAO,IAAI,SAAS,MAAM;IACzB,QAAQ;IACR,YAAY;IACZ,CAAC;;;AAIJ,QAAO;EACN,SAAS,OAAO,YAAqB;GACpC,MAAM,QAAQ,MAAM,QAAQ,YAAY,QAAQ;AAChD,OAAI,iBAAiB,SACpB,QAAO;GAER,MAAM,MAAM,UAAU,MAAM,GAAG,QAAQ;GACvC,MAAM,MAAM,MAAM,eAAe,IAAI;GACrC,MAAM,QAAQ,MAAM,QAAQ,aAAa,KAAK,IAAI;AAClD,OAAI,iBAAiB,SACpB,QAAO;AAER,UAAO;;EAER;EACA"}
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 { type Endpoint, createEndpoint } 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);\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, 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-call",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",