better-call 1.3.8 → 1.4.1
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/context.cjs.map +1 -1
- package/dist/context.d.cts +25 -18
- package/dist/context.d.mts +25 -18
- package/dist/context.mjs.map +1 -1
- package/dist/cookies.cjs +40 -34
- package/dist/cookies.cjs.map +1 -1
- package/dist/cookies.d.cts +1 -1
- package/dist/cookies.d.mts +1 -1
- package/dist/cookies.mjs +40 -34
- package/dist/cookies.mjs.map +1 -1
- package/dist/endpoint.cjs +16 -4
- package/dist/endpoint.cjs.map +1 -1
- package/dist/endpoint.d.cts +19 -13
- package/dist/endpoint.d.mts +19 -13
- package/dist/endpoint.mjs +16 -4
- package/dist/endpoint.mjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +10 -20
- package/dist/middleware.d.mts +10 -20
- package/dist/middleware.mjs.map +1 -1
- package/dist/router.cjs +2 -2
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +2 -2
- package/dist/router.d.mts +2 -2
- package/dist/router.mjs +2 -2
- package/dist/router.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -2,10 +2,10 @@ import { CookieOptions, CookiePrefixOptions, getCookieKey, parseCookies, seriali
|
|
|
2
2
|
import { StandardSchemaV1 } from "./standard-schema.mjs";
|
|
3
3
|
import { APIError, BetterCallError, Status, ValidationError, hideInternalStackFrames, kAPIErrorHeaderSymbol, makeErrorForHideStackFrame, statusCodes } from "./error.mjs";
|
|
4
4
|
import { HasRequiredKeys, InferParamPath, InferParamWildCard, IsEmptyObject, MergeObject, Prettify, RequiredKeysOf, UnionToIntersection } from "./helper.mjs";
|
|
5
|
-
import { Middleware, MiddlewareContext, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, createMiddleware } from "./middleware.mjs";
|
|
5
|
+
import { Middleware, MiddlewareContext, MiddlewareHandler, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, createMiddleware } from "./middleware.mjs";
|
|
6
6
|
import { HTTPMethod, InferBody, InferBodyInput, InferHeaders, InferHeadersInput, InferInputMethod, InferMethod, InferMiddlewareBody, InferMiddlewareQuery, InferParam, InferParamInput, InferQuery, InferQueryInput, InferRequest, InferRequestInput, InferUse, InputContext, Method, createInternalContext } from "./context.mjs";
|
|
7
7
|
import { OpenAPIParameter, OpenAPISchemaType, Path, generator, getHTML } from "./openapi.mjs";
|
|
8
8
|
import { Endpoint, EndpointBaseOptions, EndpointBodyMethodOptions, EndpointContext, EndpointHandler, EndpointOptions, StrictEndpoint, createEndpoint } from "./endpoint.mjs";
|
|
9
9
|
import { Router, RouterConfig, createRouter } from "./router.mjs";
|
|
10
10
|
import { JSONResponse, toResponse } from "./to-response.mjs";
|
|
11
|
-
export { APIError, BetterCallError, CookieOptions, CookiePrefixOptions, Endpoint, EndpointBaseOptions, EndpointBodyMethodOptions, EndpointContext, EndpointHandler, EndpointOptions, HTTPMethod, HasRequiredKeys, InferBody, InferBodyInput, InferHeaders, InferHeadersInput, InferInputMethod, InferMethod, InferMiddlewareBody, InferMiddlewareQuery, InferParam, InferParamInput, InferParamPath, InferParamWildCard, InferQuery, InferQueryInput, InferRequest, InferRequestInput, InferUse, InputContext, IsEmptyObject, JSONResponse, MergeObject, Method, Middleware, MiddlewareContext, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, OpenAPIParameter, OpenAPISchemaType, Path, Prettify, RequiredKeysOf, Router, RouterConfig, StandardSchemaV1, Status, StrictEndpoint, UnionToIntersection, ValidationError, createEndpoint, createInternalContext, createMiddleware, createRouter, generator, getCookieKey, getHTML, hideInternalStackFrames, kAPIErrorHeaderSymbol, makeErrorForHideStackFrame, parseCookies, serializeCookie, serializeSignedCookie, statusCodes, toResponse };
|
|
11
|
+
export { APIError, BetterCallError, CookieOptions, CookiePrefixOptions, Endpoint, EndpointBaseOptions, EndpointBodyMethodOptions, EndpointContext, EndpointHandler, EndpointOptions, HTTPMethod, HasRequiredKeys, InferBody, InferBodyInput, InferHeaders, InferHeadersInput, InferInputMethod, InferMethod, InferMiddlewareBody, InferMiddlewareQuery, InferParam, InferParamInput, InferParamPath, InferParamWildCard, InferQuery, InferQueryInput, InferRequest, InferRequestInput, InferUse, InputContext, IsEmptyObject, JSONResponse, MergeObject, Method, Middleware, MiddlewareContext, MiddlewareHandler, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, OpenAPIParameter, OpenAPISchemaType, Path, Prettify, RequiredKeysOf, Router, RouterConfig, StandardSchemaV1, Status, StrictEndpoint, UnionToIntersection, ValidationError, createEndpoint, createInternalContext, createMiddleware, createRouter, generator, getCookieKey, getHTML, hideInternalStackFrames, kAPIErrorHeaderSymbol, makeErrorForHideStackFrame, parseCookies, serializeCookie, serializeSignedCookie, statusCodes, toResponse };
|
package/dist/middleware.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.cjs","names":["createInternalContext","isAPIError","kAPIErrorHeaderSymbol"],"sources":["../src/middleware.ts"],"sourcesContent":["import type {\n\tInferBodyInput,\n\tInferHeaders,\n\tInferHeadersInput,\n\tInferMiddlewareBody,\n\tInferMiddlewareQuery,\n\tInferQueryInput,\n\tInferRequest,\n\tInferRequestInput,\n\tInferUse,\n\tInputContext,\n} from \"./context\";\nimport { createInternalContext } from \"./context\";\nimport type { EndpointContext, EndpointOptions } from \"./endpoint\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t *
|
|
1
|
+
{"version":3,"file":"middleware.cjs","names":["createInternalContext","isAPIError","kAPIErrorHeaderSymbol"],"sources":["../src/middleware.ts"],"sourcesContent":["import type {\n\tInferBodyInput,\n\tInferHeaders,\n\tInferHeadersInput,\n\tInferMiddlewareBody,\n\tInferMiddlewareQuery,\n\tInferQueryInput,\n\tInferRequest,\n\tInferRequestInput,\n\tInferUse,\n\tInputContext,\n} from \"./context\";\nimport { createInternalContext } from \"./context\";\nimport type { EndpointContext, EndpointOptions } from \"./endpoint\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t * Request object\n\t *\n\t * If `requireRequest` is set to true in the endpoint options this will be\n\t * required\n\t */\n\trequest: InferRequest<Options>;\n\t/**\n\t * Headers\n\t *\n\t * If `requireHeaders` is set to true in the endpoint options this will be\n\t * required\n\t */\n\theaders: InferHeaders<Options>;\n\t/**\n\t * Set header\n\t *\n\t * If it's called outside of a request it will just be ignored.\n\t */\n\tsetHeader: (key: string, value: string) => void;\n\t/**\n\t * Get header\n\t *\n\t * If it's called outside of a request it will just return null\n\t *\n\t * @param key - The key of the header\n\t * @returns\n\t */\n\tgetHeader: (key: string) => string | null;\n\t/**\n\t * JSON\n\t *\n\t * a helper function to create a JSON response with\n\t * the correct headers\n\t * and status code. If `asResponse` is set to true in\n\t * the context then\n\t * it will return a Response object instead of the\n\t * JSON object.\n\t *\n\t * @param json - The JSON object to return\n\t * @param routerResponse - The response object to\n\t * return if `asResponse` is\n\t * true in the context this will take precedence\n\t */\n\tjson: <R extends Record<string, any> | null>(\n\t\tjson: R,\n\t\trouterResponse?:\n\t\t\t| {\n\t\t\t\t\tstatus?: number;\n\t\t\t\t\theaders?: Record<string, string>;\n\t\t\t\t\tresponse?: Response;\n\t\t\t }\n\t\t\t| Response,\n\t) => Promise<R>;\n\t/**\n\t * Middleware context\n\t */\n\tcontext: Prettify<Context>;\n};\n\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\toptions: Options,\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): Middleware<\n\tOptions,\n\t<InputCtx extends MiddlewareInputContext<Options>>(\n\t\tinputContext: InputCtx,\n\t) => Promise<R>\n>;\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): Middleware<\n\tOptions,\n\t<InputCtx extends MiddlewareInputContext<Options>>(\n\t\tinputContext: InputCtx,\n\t) => Promise<R>\n>;\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n\tconst internalHandler = async (inputCtx: InputContext<any, any>) => {\n\t\tconst context = inputCtx as InputContext<any, any>;\n\t\tconst _handler =\n\t\t\ttypeof optionsOrHandler === \"function\" ? optionsOrHandler : handler;\n\t\tconst options =\n\t\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\t\tconst internalContext = await createInternalContext(context, {\n\t\t\toptions,\n\t\t\tpath: \"/\",\n\t\t});\n\n\t\tif (!_handler) {\n\t\t\tthrow new Error(\"handler must be defined\");\n\t\t}\n\t\ttry {\n\t\t\tconst response = await _handler(internalContext as any);\n\t\t\tconst headers = internalContext.responseHeaders;\n\t\t\treturn context.returnHeaders\n\t\t\t\t? {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t}\n\t\t\t\t: response;\n\t\t} catch (e) {\n\t\t\t// fixme(alex): this is workaround that set-cookie headers are not accessible when error is thrown from middleware\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tObject.defineProperty(e, kAPIErrorHeaderSymbol, {\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\tget() {\n\t\t\t\t\t\treturn internalContext.responseHeaders;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t};\n\tinternalHandler.options =\n\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\treturn internalHandler;\n}\n\nexport type MiddlewareInputContext<Options extends MiddlewareOptions> =\n\tInferBodyInput<Options> &\n\t\tInferQueryInput<Options> &\n\t\tInferRequestInput<Options> &\n\t\tInferHeadersInput<Options> & {\n\t\t\tasResponse?: boolean;\n\t\t\treturnHeaders?: boolean;\n\t\t\tuse?: MiddlewareHandler[];\n\t\t};\n\ntype MiddlewareFunction = (...args: never[]) => Promise<unknown>;\nexport type MiddlewareHandler = (\n\tinputContext: MiddlewareInputContext<MiddlewareOptions>,\n) => Promise<unknown>;\n\nexport type Middleware<\n\tOptions extends MiddlewareOptions = MiddlewareOptions,\n\tHandler extends MiddlewareFunction = MiddlewareHandler,\n> = Handler & {\n\toptions: Options;\n};\n\ncreateMiddleware.create = <\n\tE extends {\n\t\tuse?: MiddlewareHandler[];\n\t},\n>(\n\topts?: E,\n) => {\n\ttype InferredContext = InferUse<E[\"use\"]>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\toptions: Options,\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): Middleware<\n\t\tOptions,\n\t\t(inputContext: MiddlewareInputContext<Options>) => Promise<R>\n\t>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): Middleware<\n\t\tOptions,\n\t\t(inputContext: MiddlewareInputContext<Options>) => Promise<R>\n\t>;\n\tfunction fn(optionsOrHandler: any, handler?: any) {\n\t\tif (typeof optionsOrHandler === \"function\") {\n\t\t\treturn createMiddleware(\n\t\t\t\t{\n\t\t\t\t\tuse: opts?.use,\n\t\t\t\t},\n\t\t\t\toptionsOrHandler,\n\t\t\t);\n\t\t}\n\t\tif (!handler) {\n\t\t\tthrow new Error(\"Middleware handler is required\");\n\t\t}\n\t\tconst middleware = createMiddleware(\n\t\t\t{\n\t\t\t\t...optionsOrHandler,\n\t\t\t\tmethod: \"*\",\n\t\t\t\tuse: [...(opts?.use || []), ...(optionsOrHandler.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t\treturn middleware as any;\n\t}\n\treturn fn;\n};\n"],"mappings":";;;;AAsIA,SAAgB,iBAAiB,kBAAuB,SAAe;CACtE,MAAM,kBAAkB,OAAO,aAAqC;EACnE,MAAM,UAAU;EAChB,MAAM,WACL,OAAO,qBAAqB,aAAa,mBAAmB;EAG7D,MAAM,kBAAkB,MAAMA,gBAAAA,sBAAsB,SAAS;GAC5D,SAFA,OAAO,qBAAqB,aAAa,CAAC,IAAI;GAG9C,MAAM;EACP,CAAC;EAED,IAAI,CAAC,UACJ,MAAM,IAAI,MAAM,yBAAyB;EAE1C,IAAI;GACH,MAAM,WAAW,MAAM,SAAS,eAAsB;GACtD,MAAM,UAAU,gBAAgB;GAChC,OAAO,QAAQ,gBACZ;IACA;IACA;GACD,IACC;EACJ,SAAS,GAAG;GAEX,IAAIC,cAAAA,WAAW,CAAC,GACf,OAAO,eAAe,GAAGC,cAAAA,uBAAuB;IAC/C,YAAY;IACZ,cAAc;IACd,MAAM;KACL,OAAO,gBAAgB;IACxB;GACD,CAAC;GAEF,MAAM;EACP;CACD;CACA,gBAAgB,UACf,OAAO,qBAAqB,aAAa,CAAC,IAAI;CAC/C,OAAO;AACR;AAwBA,iBAAiB,UAKhB,SACI;CAeJ,SAAS,GAAG,kBAAuB,SAAe;EACjD,IAAI,OAAO,qBAAqB,YAC/B,OAAO,iBACN,EACC,KAAK,MAAM,IACZ,GACA,gBACD;EAED,IAAI,CAAC,SACJ,MAAM,IAAI,MAAM,gCAAgC;EAUjD,OARmB,iBAClB;GACC,GAAG;GACH,QAAQ;GACR,KAAK,CAAC,GAAI,MAAM,OAAO,CAAC,GAAI,GAAI,iBAAiB,OAAO,CAAC,CAAE;EAC5D,GACA,OAEe;CACjB;CACA,OAAO;AACR"}
|
package/dist/middleware.d.cts
CHANGED
|
@@ -33,18 +33,6 @@ type MiddlewareContext<Options extends MiddlewareOptions, Context = {}> = Endpoi
|
|
|
33
33
|
* and validated against the query schema if it exists
|
|
34
34
|
*/
|
|
35
35
|
query: InferMiddlewareQuery<Options>;
|
|
36
|
-
/**
|
|
37
|
-
* Params
|
|
38
|
-
*
|
|
39
|
-
* If the path is `/user/:id` and the request is `/user/1` then the
|
|
40
|
-
* params will
|
|
41
|
-
* be `{ id: "1" }` and if the path includes a wildcard like `/user/*`
|
|
42
|
-
* then the
|
|
43
|
-
* params will be `{ _: "1" }` where `_` is the wildcard key. If the
|
|
44
|
-
* wildcard
|
|
45
|
-
* is named like `/user/**:name` then the params will be `{ name: string }`
|
|
46
|
-
*/
|
|
47
|
-
params: string;
|
|
48
36
|
/**
|
|
49
37
|
* Request object
|
|
50
38
|
*
|
|
@@ -99,24 +87,26 @@ type MiddlewareContext<Options extends MiddlewareOptions, Context = {}> = Endpoi
|
|
|
99
87
|
*/
|
|
100
88
|
context: Prettify<Context>;
|
|
101
89
|
};
|
|
102
|
-
declare function createMiddleware<Options extends MiddlewareOptions, R>(options: Options, handler: (context: MiddlewareContext<Options>) => Promise<R>): <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R
|
|
103
|
-
declare function createMiddleware<Options extends MiddlewareOptions, R>(handler: (context: MiddlewareContext<Options>) => Promise<R>): <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R
|
|
90
|
+
declare function createMiddleware<Options extends MiddlewareOptions, R>(options: Options, handler: (context: MiddlewareContext<Options>) => Promise<R>): Middleware<Options, <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R>>;
|
|
91
|
+
declare function createMiddleware<Options extends MiddlewareOptions, R>(handler: (context: MiddlewareContext<Options>) => Promise<R>): Middleware<Options, <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R>>;
|
|
104
92
|
declare namespace createMiddleware {
|
|
105
93
|
var create: <E extends {
|
|
106
|
-
use?:
|
|
94
|
+
use?: MiddlewareHandler[];
|
|
107
95
|
}>(opts?: E) => {
|
|
108
|
-
<Options extends MiddlewareOptions, R>(options: Options, handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R>): (inputContext: MiddlewareInputContext<Options>) => Promise<R
|
|
109
|
-
<Options extends MiddlewareOptions, R_1>(handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R_1>): (inputContext: MiddlewareInputContext<Options>) => Promise<R_1
|
|
96
|
+
<Options extends MiddlewareOptions, R>(options: Options, handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R>): Middleware<Options, (inputContext: MiddlewareInputContext<Options>) => Promise<R>>;
|
|
97
|
+
<Options extends MiddlewareOptions, R_1>(handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R_1>): Middleware<Options, (inputContext: MiddlewareInputContext<Options>) => Promise<R_1>>;
|
|
110
98
|
};
|
|
111
99
|
}
|
|
112
100
|
type MiddlewareInputContext<Options extends MiddlewareOptions> = InferBodyInput<Options> & InferQueryInput<Options> & InferRequestInput<Options> & InferHeadersInput<Options> & {
|
|
113
101
|
asResponse?: boolean;
|
|
114
102
|
returnHeaders?: boolean;
|
|
115
|
-
use?:
|
|
103
|
+
use?: MiddlewareHandler[];
|
|
116
104
|
};
|
|
117
|
-
type
|
|
105
|
+
type MiddlewareFunction = (...args: never[]) => Promise<unknown>;
|
|
106
|
+
type MiddlewareHandler = (inputContext: MiddlewareInputContext<MiddlewareOptions>) => Promise<unknown>;
|
|
107
|
+
type Middleware<Options extends MiddlewareOptions = MiddlewareOptions, Handler extends MiddlewareFunction = MiddlewareHandler> = Handler & {
|
|
118
108
|
options: Options;
|
|
119
109
|
};
|
|
120
110
|
//#endregion
|
|
121
|
-
export { Middleware, MiddlewareContext, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, createMiddleware };
|
|
111
|
+
export { Middleware, MiddlewareContext, MiddlewareHandler, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, createMiddleware };
|
|
122
112
|
//# sourceMappingURL=middleware.d.cts.map
|
package/dist/middleware.d.mts
CHANGED
|
@@ -33,18 +33,6 @@ type MiddlewareContext<Options extends MiddlewareOptions, Context = {}> = Endpoi
|
|
|
33
33
|
* and validated against the query schema if it exists
|
|
34
34
|
*/
|
|
35
35
|
query: InferMiddlewareQuery<Options>;
|
|
36
|
-
/**
|
|
37
|
-
* Params
|
|
38
|
-
*
|
|
39
|
-
* If the path is `/user/:id` and the request is `/user/1` then the
|
|
40
|
-
* params will
|
|
41
|
-
* be `{ id: "1" }` and if the path includes a wildcard like `/user/*`
|
|
42
|
-
* then the
|
|
43
|
-
* params will be `{ _: "1" }` where `_` is the wildcard key. If the
|
|
44
|
-
* wildcard
|
|
45
|
-
* is named like `/user/**:name` then the params will be `{ name: string }`
|
|
46
|
-
*/
|
|
47
|
-
params: string;
|
|
48
36
|
/**
|
|
49
37
|
* Request object
|
|
50
38
|
*
|
|
@@ -99,24 +87,26 @@ type MiddlewareContext<Options extends MiddlewareOptions, Context = {}> = Endpoi
|
|
|
99
87
|
*/
|
|
100
88
|
context: Prettify<Context>;
|
|
101
89
|
};
|
|
102
|
-
declare function createMiddleware<Options extends MiddlewareOptions, R>(options: Options, handler: (context: MiddlewareContext<Options>) => Promise<R>): <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R
|
|
103
|
-
declare function createMiddleware<Options extends MiddlewareOptions, R>(handler: (context: MiddlewareContext<Options>) => Promise<R>): <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R
|
|
90
|
+
declare function createMiddleware<Options extends MiddlewareOptions, R>(options: Options, handler: (context: MiddlewareContext<Options>) => Promise<R>): Middleware<Options, <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R>>;
|
|
91
|
+
declare function createMiddleware<Options extends MiddlewareOptions, R>(handler: (context: MiddlewareContext<Options>) => Promise<R>): Middleware<Options, <InputCtx extends MiddlewareInputContext<Options>>(inputContext: InputCtx) => Promise<R>>;
|
|
104
92
|
declare namespace createMiddleware {
|
|
105
93
|
var create: <E extends {
|
|
106
|
-
use?:
|
|
94
|
+
use?: MiddlewareHandler[];
|
|
107
95
|
}>(opts?: E) => {
|
|
108
|
-
<Options extends MiddlewareOptions, R>(options: Options, handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R>): (inputContext: MiddlewareInputContext<Options>) => Promise<R
|
|
109
|
-
<Options extends MiddlewareOptions, R_1>(handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R_1>): (inputContext: MiddlewareInputContext<Options>) => Promise<R_1
|
|
96
|
+
<Options extends MiddlewareOptions, R>(options: Options, handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R>): Middleware<Options, (inputContext: MiddlewareInputContext<Options>) => Promise<R>>;
|
|
97
|
+
<Options extends MiddlewareOptions, R_1>(handler: (ctx: MiddlewareContext<Options, InferUse<E["use"]>>) => Promise<R_1>): Middleware<Options, (inputContext: MiddlewareInputContext<Options>) => Promise<R_1>>;
|
|
110
98
|
};
|
|
111
99
|
}
|
|
112
100
|
type MiddlewareInputContext<Options extends MiddlewareOptions> = InferBodyInput<Options> & InferQueryInput<Options> & InferRequestInput<Options> & InferHeadersInput<Options> & {
|
|
113
101
|
asResponse?: boolean;
|
|
114
102
|
returnHeaders?: boolean;
|
|
115
|
-
use?:
|
|
103
|
+
use?: MiddlewareHandler[];
|
|
116
104
|
};
|
|
117
|
-
type
|
|
105
|
+
type MiddlewareFunction = (...args: never[]) => Promise<unknown>;
|
|
106
|
+
type MiddlewareHandler = (inputContext: MiddlewareInputContext<MiddlewareOptions>) => Promise<unknown>;
|
|
107
|
+
type Middleware<Options extends MiddlewareOptions = MiddlewareOptions, Handler extends MiddlewareFunction = MiddlewareHandler> = Handler & {
|
|
118
108
|
options: Options;
|
|
119
109
|
};
|
|
120
110
|
//#endregion
|
|
121
|
-
export { Middleware, MiddlewareContext, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, createMiddleware };
|
|
111
|
+
export { Middleware, MiddlewareContext, MiddlewareHandler, MiddlewareInputContext, MiddlewareOptions, MiddlewareResponse, createMiddleware };
|
|
122
112
|
//# sourceMappingURL=middleware.d.mts.map
|
package/dist/middleware.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type {\n\tInferBodyInput,\n\tInferHeaders,\n\tInferHeadersInput,\n\tInferMiddlewareBody,\n\tInferMiddlewareQuery,\n\tInferQueryInput,\n\tInferRequest,\n\tInferRequestInput,\n\tInferUse,\n\tInputContext,\n} from \"./context\";\nimport { createInternalContext } from \"./context\";\nimport type { EndpointContext, EndpointOptions } from \"./endpoint\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t *
|
|
1
|
+
{"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type {\n\tInferBodyInput,\n\tInferHeaders,\n\tInferHeadersInput,\n\tInferMiddlewareBody,\n\tInferMiddlewareQuery,\n\tInferQueryInput,\n\tInferRequest,\n\tInferRequestInput,\n\tInferUse,\n\tInputContext,\n} from \"./context\";\nimport { createInternalContext } from \"./context\";\nimport type { EndpointContext, EndpointOptions } from \"./endpoint\";\nimport { kAPIErrorHeaderSymbol } from \"./error\";\nimport type { Prettify } from \"./helper\";\nimport { isAPIError } from \"./utils\";\n\nexport interface MiddlewareOptions extends Omit<EndpointOptions, \"method\"> {}\n\nexport type MiddlewareResponse = null | void | undefined | Record<string, any>;\n\nexport type MiddlewareContext<\n\tOptions extends MiddlewareOptions,\n\tContext = {},\n> = EndpointContext<\n\tstring,\n\tOptions & {\n\t\tmethod: \"*\";\n\t}\n> & {\n\t/**\n\t * Method\n\t *\n\t * The request method\n\t */\n\tmethod: string;\n\t/**\n\t * Path\n\t *\n\t * The path of the endpoint\n\t */\n\tpath: string;\n\t/**\n\t * Body\n\t *\n\t * The body object will be the parsed JSON from the request and validated\n\t * against the body schema if it exists\n\t */\n\tbody: InferMiddlewareBody<Options>;\n\t/**\n\t * Query\n\t *\n\t * The query object will be the parsed query string from the request\n\t * and validated against the query schema if it exists\n\t */\n\tquery: InferMiddlewareQuery<Options>;\n\t/**\n\t * Request object\n\t *\n\t * If `requireRequest` is set to true in the endpoint options this will be\n\t * required\n\t */\n\trequest: InferRequest<Options>;\n\t/**\n\t * Headers\n\t *\n\t * If `requireHeaders` is set to true in the endpoint options this will be\n\t * required\n\t */\n\theaders: InferHeaders<Options>;\n\t/**\n\t * Set header\n\t *\n\t * If it's called outside of a request it will just be ignored.\n\t */\n\tsetHeader: (key: string, value: string) => void;\n\t/**\n\t * Get header\n\t *\n\t * If it's called outside of a request it will just return null\n\t *\n\t * @param key - The key of the header\n\t * @returns\n\t */\n\tgetHeader: (key: string) => string | null;\n\t/**\n\t * JSON\n\t *\n\t * a helper function to create a JSON response with\n\t * the correct headers\n\t * and status code. If `asResponse` is set to true in\n\t * the context then\n\t * it will return a Response object instead of the\n\t * JSON object.\n\t *\n\t * @param json - The JSON object to return\n\t * @param routerResponse - The response object to\n\t * return if `asResponse` is\n\t * true in the context this will take precedence\n\t */\n\tjson: <R extends Record<string, any> | null>(\n\t\tjson: R,\n\t\trouterResponse?:\n\t\t\t| {\n\t\t\t\t\tstatus?: number;\n\t\t\t\t\theaders?: Record<string, string>;\n\t\t\t\t\tresponse?: Response;\n\t\t\t }\n\t\t\t| Response,\n\t) => Promise<R>;\n\t/**\n\t * Middleware context\n\t */\n\tcontext: Prettify<Context>;\n};\n\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\toptions: Options,\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): Middleware<\n\tOptions,\n\t<InputCtx extends MiddlewareInputContext<Options>>(\n\t\tinputContext: InputCtx,\n\t) => Promise<R>\n>;\nexport function createMiddleware<Options extends MiddlewareOptions, R>(\n\thandler: (context: MiddlewareContext<Options>) => Promise<R>,\n): Middleware<\n\tOptions,\n\t<InputCtx extends MiddlewareInputContext<Options>>(\n\t\tinputContext: InputCtx,\n\t) => Promise<R>\n>;\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n\tconst internalHandler = async (inputCtx: InputContext<any, any>) => {\n\t\tconst context = inputCtx as InputContext<any, any>;\n\t\tconst _handler =\n\t\t\ttypeof optionsOrHandler === \"function\" ? optionsOrHandler : handler;\n\t\tconst options =\n\t\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\t\tconst internalContext = await createInternalContext(context, {\n\t\t\toptions,\n\t\t\tpath: \"/\",\n\t\t});\n\n\t\tif (!_handler) {\n\t\t\tthrow new Error(\"handler must be defined\");\n\t\t}\n\t\ttry {\n\t\t\tconst response = await _handler(internalContext as any);\n\t\t\tconst headers = internalContext.responseHeaders;\n\t\t\treturn context.returnHeaders\n\t\t\t\t? {\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t}\n\t\t\t\t: response;\n\t\t} catch (e) {\n\t\t\t// fixme(alex): this is workaround that set-cookie headers are not accessible when error is thrown from middleware\n\t\t\tif (isAPIError(e)) {\n\t\t\t\tObject.defineProperty(e, kAPIErrorHeaderSymbol, {\n\t\t\t\t\tenumerable: false,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\tget() {\n\t\t\t\t\t\treturn internalContext.responseHeaders;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t};\n\tinternalHandler.options =\n\t\ttypeof optionsOrHandler === \"function\" ? {} : optionsOrHandler;\n\treturn internalHandler;\n}\n\nexport type MiddlewareInputContext<Options extends MiddlewareOptions> =\n\tInferBodyInput<Options> &\n\t\tInferQueryInput<Options> &\n\t\tInferRequestInput<Options> &\n\t\tInferHeadersInput<Options> & {\n\t\t\tasResponse?: boolean;\n\t\t\treturnHeaders?: boolean;\n\t\t\tuse?: MiddlewareHandler[];\n\t\t};\n\ntype MiddlewareFunction = (...args: never[]) => Promise<unknown>;\nexport type MiddlewareHandler = (\n\tinputContext: MiddlewareInputContext<MiddlewareOptions>,\n) => Promise<unknown>;\n\nexport type Middleware<\n\tOptions extends MiddlewareOptions = MiddlewareOptions,\n\tHandler extends MiddlewareFunction = MiddlewareHandler,\n> = Handler & {\n\toptions: Options;\n};\n\ncreateMiddleware.create = <\n\tE extends {\n\t\tuse?: MiddlewareHandler[];\n\t},\n>(\n\topts?: E,\n) => {\n\ttype InferredContext = InferUse<E[\"use\"]>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\toptions: Options,\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): Middleware<\n\t\tOptions,\n\t\t(inputContext: MiddlewareInputContext<Options>) => Promise<R>\n\t>;\n\tfunction fn<Options extends MiddlewareOptions, R>(\n\t\thandler: (ctx: MiddlewareContext<Options, InferredContext>) => Promise<R>,\n\t): Middleware<\n\t\tOptions,\n\t\t(inputContext: MiddlewareInputContext<Options>) => Promise<R>\n\t>;\n\tfunction fn(optionsOrHandler: any, handler?: any) {\n\t\tif (typeof optionsOrHandler === \"function\") {\n\t\t\treturn createMiddleware(\n\t\t\t\t{\n\t\t\t\t\tuse: opts?.use,\n\t\t\t\t},\n\t\t\t\toptionsOrHandler,\n\t\t\t);\n\t\t}\n\t\tif (!handler) {\n\t\t\tthrow new Error(\"Middleware handler is required\");\n\t\t}\n\t\tconst middleware = createMiddleware(\n\t\t\t{\n\t\t\t\t...optionsOrHandler,\n\t\t\t\tmethod: \"*\",\n\t\t\t\tuse: [...(opts?.use || []), ...(optionsOrHandler.use || [])],\n\t\t\t},\n\t\t\thandler,\n\t\t);\n\t\treturn middleware as any;\n\t}\n\treturn fn;\n};\n"],"mappings":";;;;AAsIA,SAAgB,iBAAiB,kBAAuB,SAAe;CACtE,MAAM,kBAAkB,OAAO,aAAqC;EACnE,MAAM,UAAU;EAChB,MAAM,WACL,OAAO,qBAAqB,aAAa,mBAAmB;EAG7D,MAAM,kBAAkB,MAAM,sBAAsB,SAAS;GAC5D,SAFA,OAAO,qBAAqB,aAAa,CAAC,IAAI;GAG9C,MAAM;EACP,CAAC;EAED,IAAI,CAAC,UACJ,MAAM,IAAI,MAAM,yBAAyB;EAE1C,IAAI;GACH,MAAM,WAAW,MAAM,SAAS,eAAsB;GACtD,MAAM,UAAU,gBAAgB;GAChC,OAAO,QAAQ,gBACZ;IACA;IACA;GACD,IACC;EACJ,SAAS,GAAG;GAEX,IAAI,WAAW,CAAC,GACf,OAAO,eAAe,GAAG,uBAAuB;IAC/C,YAAY;IACZ,cAAc;IACd,MAAM;KACL,OAAO,gBAAgB;IACxB;GACD,CAAC;GAEF,MAAM;EACP;CACD;CACA,gBAAgB,UACf,OAAO,qBAAqB,aAAa,CAAC,IAAI;CAC/C,OAAO;AACR;AAwBA,iBAAiB,UAKhB,SACI;CAeJ,SAAS,GAAG,kBAAuB,SAAe;EACjD,IAAI,OAAO,qBAAqB,YAC/B,OAAO,iBACN,EACC,KAAK,MAAM,IACZ,GACA,gBACD;EAED,IAAI,CAAC,SACJ,MAAM,IAAI,MAAM,gCAAgC;EAUjD,OARmB,iBAClB;GACC,GAAG;GACH,QAAQ;GACR,KAAK,CAAC,GAAI,MAAM,OAAO,CAAC,GAAI,GAAI,iBAAiB,OAAO,CAAC,CAAE;EAC5D,GACA,OAEe;CACjB;CACA,OAAO;AACR"}
|
package/dist/router.cjs
CHANGED
|
@@ -62,7 +62,7 @@ const createRouter = (endpoints, config) => {
|
|
|
62
62
|
path,
|
|
63
63
|
method: request.method,
|
|
64
64
|
headers: request.headers,
|
|
65
|
-
params: route.params ?
|
|
65
|
+
params: route.params ? { ...route.params } : {},
|
|
66
66
|
request,
|
|
67
67
|
body: handler.options.disableBody ? void 0 : await require_utils.getBody(handler.options.cloneRequest ? request.clone() : request, allowedMediaTypes),
|
|
68
68
|
query,
|
|
@@ -74,7 +74,7 @@ const createRouter = (endpoints, config) => {
|
|
|
74
74
|
if (middlewareRoutes?.length) for (const { data: middleware, params } of middlewareRoutes) {
|
|
75
75
|
const res = await middleware({
|
|
76
76
|
...context,
|
|
77
|
-
params,
|
|
77
|
+
params: params ? { ...params } : {},
|
|
78
78
|
asResponse: false
|
|
79
79
|
});
|
|
80
80
|
if (res instanceof Response) return res;
|
package/dist/router.cjs.map
CHANGED
|
@@ -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 } from \"./endpoint\";\nimport { 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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"}
|
|
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 } from \"./endpoint\";\nimport { createEndpoint } from \"./endpoint\";\nimport type { MiddlewareHandler } 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: MiddlewareHandler;\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<Endpoint>();\n\tconst middlewareRouter = createRou3Router<MiddlewareHandler>();\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);\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;\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 ? { ...route.params } : {},\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: params ? { ...params } : {},\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":";;;;;;AA4GA,MAAa,gBAIZ,WACA,WACI;CACJ,IAAI,CAAC,QAAQ,SAAS,UAAU;EAC/B,MAAM,UAAU;GACf,MAAM;GACN,GAAG,QAAQ;EACZ;EAEA,UAAU,aAAaA,iBAAAA,eACtB,QAAQ,MACR,EACC,QAAQ,MACT,GACA,OAAO,MAAM;GACZ,MAAM,SAAS,MAAMC,gBAAAA,UAAU,SAAS;GACxC,OAAO,IAAI,SAASC,gBAAAA,QAAQ,QAAQ,QAAQ,MAAM,GAAG,EACpD,SAAS,EACR,gBAAgB,YACjB,EACD,CAAC;EACF,CACD;CACD;CACA,MAAM,UAAA,GAAA,KAAA,aAAA,CAAoC;CAC1C,MAAM,oBAAA,GAAA,KAAA,aAAA,CAAuD;CAE7D,KAAK,MAAM,YAAY,OAAO,OAAO,SAAS,GAAG;EAChD,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MAClC;EAED,IAAI,SAAS,SAAS,UAAU,aAAa;EAE7C,MAAM,UAAU,MAAM,QAAQ,SAAS,SAAS,MAAM,IACnD,SAAS,QAAQ,SACjB,CAAC,SAAS,SAAS,MAAM;EAE5B,KAAK,MAAM,UAAU,SACpB,CAAA,GAAA,KAAA,SAAA,CAAS,QAAQ,QAAQ,SAAS,MAAM,QAAQ;CAElD;CAEA,IAAI,QAAQ,kBAAkB,QAC7B,KAAK,MAAM,EAAE,MAAM,gBAAgB,OAAO,kBACzC,CAAA,GAAA,KAAA,SAAA,CAAS,kBAAkB,KAAK,MAAM,UAAU;CAQlD,MAAM,WACL,QAAQ,YAAY,OAAO,aAAa,MACrC,OAAO,SAAS,QAAQ,QAAQ,EAAE,IAClC;CAEJ,MAAM,iBAAiB,OAAO,YAAqB;EAClD,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;EAC/B,MAAM,WAAW,IAAI;EAQrB,IAAI;EACJ,IAAI,UAAU;GACb,IAAI,CAAC,SAAS,WAAW,GAAG,SAAS,EAAE,GACtC,OAAO,IAAI,SAAS,MAAM;IAAE,QAAQ;IAAK,YAAY;GAAY,CAAC;GAEnE,OAAO,SAAS,MAAM,SAAS,MAAM;EACtC,OACC,OAAO;EAIR,IAAI,KAAK,WAAW,KAAK,SAAS,KAAK,IAAI,GAC1C,OAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;EAAY,CAAC;EAGnE,MAAM,SAAA,GAAA,KAAA,UAAA,CAAkB,QAAQ,QAAQ,QAAQ,IAAI;EAKpD,IAJyB,KAAK,SAAS,GAKvB,MAJc,OAAO,MAAM,MAAM,SAAS,GAAG,KAK5D,CAAC,QAAQ,qBAET,OAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;EAAY,CAAC;EAEnE,IAAI,CAAC,OAAO,MACX,OAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,YAAY;EAAY,CAAC;EAEnE,MAAM,QAA2C,CAAC;EAClD,IAAI,aAAa,SAAS,OAAO,QAAQ;GACxC,IAAI,OAAO,OACV,IAAI,MAAM,QAAQ,MAAM,IAAI,GAC3B,MAAO,IAAI,CAAc,KAAK,KAAK;QAEnC,MAAM,OAAO,CAAC,MAAM,MAAgB,KAAK;QAG1C,MAAM,OAAO;EAEf,CAAC;EAED,MAAM,UAAU,MAAM;EAEtB,IAAI;GAEH,MAAM,oBACL,QAAQ,QAAQ,UAAU,qBAC1B,QAAQ;GACT,MAAM,UAAU;IACf;IACA,QAAQ,QAAQ;IAChB,SAAS,QAAQ;IACjB,QAAQ,MAAM,SAAS,EAAE,GAAG,MAAM,OAAO,IAAI,CAAC;IACrC;IACT,MAAM,QAAQ,QAAQ,cACnB,KAAA,IACA,MAAMC,cAAAA,QACN,QAAQ,QAAQ,eAAe,QAAQ,MAAM,IAAI,SACjD,iBACD;IACF;IACA,OAAO;IACP,YAAY;IACZ,SAAS,QAAQ;GAClB;GACA,MAAM,oBAAA,GAAA,KAAA,cAAA,CAAiC,kBAAkB,KAAK,IAAI;GAClE,IAAI,kBAAkB,QACrB,KAAK,MAAM,EAAE,MAAM,YAAY,YAAY,kBAAkB;IAC5D,MAAM,MAAM,MAAO,WAAwB;KAC1C,GAAG;KACH,QAAQ,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC;KAClC,YAAY;IACb,CAAC;IAED,IAAI,eAAe,UAAU,OAAO;GACrC;GAID,OAAO,MADiB,QAAQ,OAAO;EAExC,SAAS,OAAO;GACf,IAAI,QAAQ,SACX,IAAI;IACH,MAAM,gBAAgB,MAAM,OAAO,QAAQ,OAAO,OAAO;IAEzD,IAAI,yBAAyB,UAC5B,OAAOC,oBAAAA,WAAW,aAAa;GAEjC,SAAS,OAAO;IACf,IAAIC,cAAAA,WAAW,KAAK,GACnB,OAAOD,oBAAAA,WAAW,KAAK;IAGxB,MAAM;GACP;GAGD,IAAI,QAAQ,YACX,MAAM;GAGP,IAAIC,cAAAA,WAAW,KAAK,GACnB,OAAOD,oBAAAA,WAAW,KAAK;GAGxB,QAAQ,MAAM,oBAAoB,KAAK;GACvC,OAAO,IAAI,SAAS,MAAM;IACzB,QAAQ;IACR,YAAY;GACb,CAAC;EACF;CACD;CAEA,OAAO;EACN,SAAS,OAAO,YAAqB;GACpC,MAAM,QAAQ,MAAM,QAAQ,YAAY,OAAO;GAC/C,IAAI,iBAAiB,UACpB,OAAO;GAER,MAAM,MAAME,cAAAA,UAAU,KAAK,IAAI,QAAQ;GACvC,MAAM,MAAM,MAAM,eAAe,GAAG;GACpC,MAAM,QAAQ,MAAM,QAAQ,aAAa,KAAK,GAAG;GACjD,IAAI,iBAAiB,UACpB,OAAO;GAER,OAAO;EACR;EACA;CACD;AACD"}
|
package/dist/router.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MiddlewareHandler } from "./middleware.cjs";
|
|
2
2
|
import { Endpoint } from "./endpoint.cjs";
|
|
3
3
|
//#region src/router.d.ts
|
|
4
4
|
interface RouterConfig {
|
|
@@ -6,7 +6,7 @@ interface RouterConfig {
|
|
|
6
6
|
basePath?: string;
|
|
7
7
|
routerMiddleware?: Array<{
|
|
8
8
|
path: string;
|
|
9
|
-
middleware:
|
|
9
|
+
middleware: MiddlewareHandler;
|
|
10
10
|
}>;
|
|
11
11
|
/**
|
|
12
12
|
* additional Context that needs to passed to endpoints
|
package/dist/router.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MiddlewareHandler } from "./middleware.mjs";
|
|
2
2
|
import { Endpoint } from "./endpoint.mjs";
|
|
3
3
|
//#region src/router.d.ts
|
|
4
4
|
interface RouterConfig {
|
|
@@ -6,7 +6,7 @@ interface RouterConfig {
|
|
|
6
6
|
basePath?: string;
|
|
7
7
|
routerMiddleware?: Array<{
|
|
8
8
|
path: string;
|
|
9
|
-
middleware:
|
|
9
|
+
middleware: MiddlewareHandler;
|
|
10
10
|
}>;
|
|
11
11
|
/**
|
|
12
12
|
* additional Context that needs to passed to endpoints
|
package/dist/router.mjs
CHANGED
|
@@ -62,7 +62,7 @@ const createRouter$1 = (endpoints, config) => {
|
|
|
62
62
|
path,
|
|
63
63
|
method: request.method,
|
|
64
64
|
headers: request.headers,
|
|
65
|
-
params: route.params ?
|
|
65
|
+
params: route.params ? { ...route.params } : {},
|
|
66
66
|
request,
|
|
67
67
|
body: handler.options.disableBody ? void 0 : await getBody(handler.options.cloneRequest ? request.clone() : request, allowedMediaTypes),
|
|
68
68
|
query,
|
|
@@ -74,7 +74,7 @@ const createRouter$1 = (endpoints, config) => {
|
|
|
74
74
|
if (middlewareRoutes?.length) for (const { data: middleware, params } of middlewareRoutes) {
|
|
75
75
|
const res = await middleware({
|
|
76
76
|
...context,
|
|
77
|
-
params,
|
|
77
|
+
params: params ? { ...params } : {},
|
|
78
78
|
asResponse: false
|
|
79
79
|
});
|
|
80
80
|
if (res instanceof Response) return res;
|
package/dist/router.mjs.map
CHANGED
|
@@ -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 } from \"./endpoint\";\nimport { 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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"}
|
|
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 } from \"./endpoint\";\nimport { createEndpoint } from \"./endpoint\";\nimport type { MiddlewareHandler } 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: MiddlewareHandler;\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<Endpoint>();\n\tconst middlewareRouter = createRou3Router<MiddlewareHandler>();\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);\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;\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 ? { ...route.params } : {},\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: params ? { ...params } : {},\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
|
+
"version": "1.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@better-auth/utils": "^0.5.0",
|
|
25
25
|
"@better-fetch/fetch": "^1.3.1",
|
|
26
|
-
"rou3": "^0.
|
|
26
|
+
"rou3": "^0.9.1",
|
|
27
27
|
"set-cookie-parser": "^3.1.2"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|