@mhbdev/next-safe-route 0.0.34 → 0.0.35
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/index.d.mts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{types-bLUycHgl.d.mts → types-UXG9BoMB.d.mts} +1 -1
- package/dist/{types-bLUycHgl.d.ts → types-UXG9BoMB.d.ts} +1 -1
- package/dist/valibot.d.mts +1 -1
- package/dist/valibot.d.ts +1 -1
- package/dist/yup.d.mts +1 -1
- package/dist/yup.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { S as Schema, I as Infer, V as
|
|
2
|
-
export { b as ValidationIssue } from './types-bLUycHgl.mjs';
|
|
1
|
+
import { S as Schema, I as Infer, V as ValidationIssue, a as ValidationAdapter, b as IfInstalled } from './types-UXG9BoMB.mjs';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
export { valibotAdapter } from './valibot.mjs';
|
|
5
4
|
export { yupAdapter } from './yup.mjs';
|
|
@@ -20,8 +19,9 @@ type HandlerFunction<TParams, TQuery, TBody, TContext> = (request: Request, cont
|
|
|
20
19
|
}) => Response | Promise<Response>;
|
|
21
20
|
type OriginalRouteHandler = (request: Request, context: RouteContext) => Response | Promise<Response>;
|
|
22
21
|
type HandlerServerErrorFn = (error: Error) => Response;
|
|
22
|
+
type ValidationErrorHandler = (issues: ValidationIssue[]) => Response;
|
|
23
23
|
|
|
24
|
-
type Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T>;
|
|
24
|
+
type Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T | Response>;
|
|
25
25
|
type BuilderConfig<TParams extends Schema | undefined, TQuery extends Schema | undefined, TBody extends Schema | undefined> = {
|
|
26
26
|
paramsSchema: TParams;
|
|
27
27
|
querySchema: TQuery;
|
|
@@ -31,12 +31,14 @@ declare class RouteHandlerBuilder<TParams extends Schema | undefined = undefined
|
|
|
31
31
|
private config;
|
|
32
32
|
private middlewares;
|
|
33
33
|
private handleServerError?;
|
|
34
|
+
private validationErrorHandler?;
|
|
34
35
|
private validationAdapter;
|
|
35
36
|
private baseContext;
|
|
36
|
-
constructor({ config, validationAdapter, middlewares, handleServerError, baseContext, }: {
|
|
37
|
+
constructor({ config, validationAdapter, middlewares, handleServerError, validationErrorHandler, baseContext, }: {
|
|
37
38
|
config?: BuilderConfig<TParams, TQuery, TBody>;
|
|
38
39
|
middlewares?: Middleware[];
|
|
39
40
|
handleServerError?: HandlerServerErrorFn;
|
|
41
|
+
validationErrorHandler?: ValidationErrorHandler;
|
|
40
42
|
validationAdapter?: ValidationAdapter;
|
|
41
43
|
baseContext?: TContext;
|
|
42
44
|
});
|
|
@@ -54,6 +56,7 @@ declare class RouteHandlerBuilder<TParams extends Schema | undefined = undefined
|
|
|
54
56
|
|
|
55
57
|
type CreateSafeRouteParams<TContext extends Record<string, unknown>> = {
|
|
56
58
|
handleServerError?: HandlerServerErrorFn;
|
|
59
|
+
validationErrorHandler?: ValidationErrorHandler;
|
|
57
60
|
validationAdapter?: ValidationAdapter;
|
|
58
61
|
baseContext?: TContext;
|
|
59
62
|
};
|
|
@@ -75,4 +78,4 @@ declare class ZodAdapter implements ValidationAdapter {
|
|
|
75
78
|
}
|
|
76
79
|
declare function zodAdapter(): ZodAdapter;
|
|
77
80
|
|
|
78
|
-
export { type HandlerFunction, type HandlerServerErrorFn, IfInstalled, Infer, type InferMaybe, type OriginalRouteHandler, type RouteContext, RouteHandlerBuilder, Schema, ValidationAdapter, createSafeRoute, zodAdapter };
|
|
81
|
+
export { type HandlerFunction, type HandlerServerErrorFn, IfInstalled, Infer, type InferMaybe, type OriginalRouteHandler, type RouteContext, RouteHandlerBuilder, Schema, ValidationAdapter, ValidationIssue, createSafeRoute, zodAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { S as Schema, I as Infer, V as
|
|
2
|
-
export { b as ValidationIssue } from './types-bLUycHgl.js';
|
|
1
|
+
import { S as Schema, I as Infer, V as ValidationIssue, a as ValidationAdapter, b as IfInstalled } from './types-UXG9BoMB.js';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
export { valibotAdapter } from './valibot.js';
|
|
5
4
|
export { yupAdapter } from './yup.js';
|
|
@@ -20,8 +19,9 @@ type HandlerFunction<TParams, TQuery, TBody, TContext> = (request: Request, cont
|
|
|
20
19
|
}) => Response | Promise<Response>;
|
|
21
20
|
type OriginalRouteHandler = (request: Request, context: RouteContext) => Response | Promise<Response>;
|
|
22
21
|
type HandlerServerErrorFn = (error: Error) => Response;
|
|
22
|
+
type ValidationErrorHandler = (issues: ValidationIssue[]) => Response;
|
|
23
23
|
|
|
24
|
-
type Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T>;
|
|
24
|
+
type Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T | Response>;
|
|
25
25
|
type BuilderConfig<TParams extends Schema | undefined, TQuery extends Schema | undefined, TBody extends Schema | undefined> = {
|
|
26
26
|
paramsSchema: TParams;
|
|
27
27
|
querySchema: TQuery;
|
|
@@ -31,12 +31,14 @@ declare class RouteHandlerBuilder<TParams extends Schema | undefined = undefined
|
|
|
31
31
|
private config;
|
|
32
32
|
private middlewares;
|
|
33
33
|
private handleServerError?;
|
|
34
|
+
private validationErrorHandler?;
|
|
34
35
|
private validationAdapter;
|
|
35
36
|
private baseContext;
|
|
36
|
-
constructor({ config, validationAdapter, middlewares, handleServerError, baseContext, }: {
|
|
37
|
+
constructor({ config, validationAdapter, middlewares, handleServerError, validationErrorHandler, baseContext, }: {
|
|
37
38
|
config?: BuilderConfig<TParams, TQuery, TBody>;
|
|
38
39
|
middlewares?: Middleware[];
|
|
39
40
|
handleServerError?: HandlerServerErrorFn;
|
|
41
|
+
validationErrorHandler?: ValidationErrorHandler;
|
|
40
42
|
validationAdapter?: ValidationAdapter;
|
|
41
43
|
baseContext?: TContext;
|
|
42
44
|
});
|
|
@@ -54,6 +56,7 @@ declare class RouteHandlerBuilder<TParams extends Schema | undefined = undefined
|
|
|
54
56
|
|
|
55
57
|
type CreateSafeRouteParams<TContext extends Record<string, unknown>> = {
|
|
56
58
|
handleServerError?: HandlerServerErrorFn;
|
|
59
|
+
validationErrorHandler?: ValidationErrorHandler;
|
|
57
60
|
validationAdapter?: ValidationAdapter;
|
|
58
61
|
baseContext?: TContext;
|
|
59
62
|
};
|
|
@@ -75,4 +78,4 @@ declare class ZodAdapter implements ValidationAdapter {
|
|
|
75
78
|
}
|
|
76
79
|
declare function zodAdapter(): ZodAdapter;
|
|
77
80
|
|
|
78
|
-
export { type HandlerFunction, type HandlerServerErrorFn, IfInstalled, Infer, type InferMaybe, type OriginalRouteHandler, type RouteContext, RouteHandlerBuilder, Schema, ValidationAdapter, createSafeRoute, zodAdapter };
|
|
81
|
+
export { type HandlerFunction, type HandlerServerErrorFn, IfInstalled, Infer, type InferMaybe, type OriginalRouteHandler, type RouteContext, RouteHandlerBuilder, Schema, ValidationAdapter, ValidationIssue, createSafeRoute, zodAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var C=Object.create;var c=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty;var V=(t,e)=>{for(var a in e)c(t,a,{get:e[a],enumerable:!0})},S=(t,e,a,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of A(e))!P.call(t,r)&&r!==a&&c(t,r,{get:()=>e[r],enumerable:!(n=R(e,r))||n.enumerable});return t};var x=(t,e,a)=>(a=t!=null?C(H(t)):{},S(e||!t||!t.__esModule?c(a,"default",{value:t,enumerable:!0}):a,t)),k=t=>S(c({},"__esModule",{value:!0}),t);var Q={};V(Q,{RouteHandlerBuilder:()=>l,createSafeRoute:()=>E,valibotAdapter:()=>g,yupAdapter:()=>I,zodAdapter:()=>i});module.exports=k(Q);var v=class{async validate(e,a){let n=await e.safeParseAsync(a);return n.success?{success:!0,data:n.data}:{success:!1,issues:n.error.issues.map(({message:r,path:o})=>({message:r,path:o}))}}};function i(){return new v}var d=class extends Error{constructor(e){super(e),this.name="BodyParsingError"}},l=class t{config;middlewares;handleServerError;validationErrorHandler;validationAdapter;baseContext;constructor({config:e={paramsSchema:void 0,querySchema:void 0,bodySchema:void 0},validationAdapter:a=i(),middlewares:n=[],handleServerError:r,validationErrorHandler:o,baseContext:s}){this.config=e,this.middlewares=n,this.handleServerError=r,this.validationErrorHandler=o,this.validationAdapter=a,this.baseContext=s??{}}params(e){return new t({config:{...this.config,paramsSchema:e},middlewares:this.middlewares,handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}query(e){return new t({config:{...this.config,querySchema:e},middlewares:this.middlewares,handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}body(e){return new t({config:{...this.config,bodySchema:e},middlewares:this.middlewares,handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}use(e){return new t({config:this.config,middlewares:[...this.middlewares,e],handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}handler(e){return async(a,n)=>{try{let r=n??{params:{}},o=await this.resolveParams(r.params),s=this.getQueryParams(a),u=await this.parseRequestBody(a),p=await this.validateInput(this.config.paramsSchema,o,"Invalid params");if(p.success===!1)return p.response;let m=await this.validateInput(this.config.querySchema,s,"Invalid query");if(m.success===!1)return m.response;let f=await this.validateInput(this.config.bodySchema,u,"Invalid body");if(f.success===!1)return f.response;let y={...this.baseContext};for(let b of this.middlewares){let h=await b(a);if(h instanceof Response)return h;y={...y,...h}}return e(a,{params:p.data,query:m.data,body:f.data,data:y})}catch(r){return r instanceof d?this.buildErrorResponse(r.message,void 0,400):this.handleServerError?this.handleServerError(r):this.buildErrorResponse("Internal server error",void 0,500)}}}async resolveParams(e){return await Promise.resolve(e??{})??{}}async validateInput(e,a,n){if(!e)return{success:!0,data:a??{}};let r=await this.validationAdapter.validate(e,a);return r.success===!0?{success:!0,data:r.data}:this.validationErrorHandler?{success:!1,response:this.validationErrorHandler(r.issues)}:{success:!1,response:this.buildErrorResponse(n,r.issues)}}getQueryParams(e){let a=new URL(e.url),n={},r=Array.from(new Set(a.searchParams.keys()));for(let o of r){let s=a.searchParams.getAll(o);n[o]=s.length===1?s[0]:s}return n}async parseRequestBody(e){if(!this.config.bodySchema)return{};let a=e.headers.get("content-type")??"";if(a.includes("application/json")){let n=await e.text();if(n.length===0)return{};try{return JSON.parse(n)}catch{throw new d("Invalid JSON body.")}}if(a.includes("multipart/form-data")||a.includes("application/x-www-form-urlencoded"))try{let n=await e.formData(),r={},o=Array.from(new Set(n.keys()));for(let s of o){let u=n.getAll(s);r[s]=u.length===1?u[0]:u}return r}catch{throw new d("Invalid Form Data.")}throw new d("Unsupported content type. Expected application/json, multipart/form-data, or application/x-www-form-urlencoded.")}buildErrorResponse(e,a,n=400){return new Response(JSON.stringify({message:e,issues:a}),{status:n,headers:{"content-type":"application/json"}})}};function E(t){return new l({handleServerError:t?.handleServerError,validationErrorHandler:t?.validationErrorHandler,validationAdapter:t?.validationAdapter??i(),baseContext:t?.baseContext})}async function B(){try{return await import("valibot")}catch(t){throw new Error('valibotAdapter requires optional peer dependency "valibot". Install it with `npm install valibot`.',{cause:t instanceof Error?t:void 0})}}var w=class{async validate(e,a){let n=await B(),r=await n.safeParseAsync(e,a);return r.success?{success:!0,data:r.output}:{success:!1,issues:r.issues.map(o=>({message:o.message,path:n.getDotPath(o)?.split(".")}))}}};function g(){return new w}async function M(){try{return await import("yup")}catch(t){throw new Error('yupAdapter requires optional peer dependency "yup". Install it with `npm install yup`.',{cause:t instanceof Error?t:void 0})}}var T=class{async validate(e,a){let n=await M();try{return{success:!0,data:await e.validate(a,{strict:!0})}}catch(r){if(r instanceof n.ValidationError){let{message:o,path:s}=r;return{success:!1,issues:[{message:o,path:s&&s.length>0?s.split("."):void 0}]}}throw r}}};function I(){return new T}0&&(module.exports={RouteHandlerBuilder,createSafeRoute,valibotAdapter,yupAdapter,zodAdapter});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/adapters/zod.ts","../src/routeHandlerBuilder.ts","../src/createSafeRoute.ts","../src/adapters/valibot.ts","../src/adapters/yup.ts"],"sourcesContent":["export { createSafeRoute } from './createSafeRoute';\nexport { RouteHandlerBuilder } from './routeHandlerBuilder';\nexport {\n type HandlerFunction,\n type HandlerServerErrorFn,\n type InferMaybe,\n type OriginalRouteHandler,\n type RouteContext,\n} from './types';\nexport {\n type IfInstalled,\n type Infer,\n type Schema,\n type ValidationAdapter,\n type ValidationIssue,\n} from './adapters/types';\nexport { zodAdapter } from './adapters/zod';\nexport { valibotAdapter } from './adapters/valibot';\nexport { yupAdapter } from './adapters/yup';\n","// Code based on https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { z } from 'zod';\n\nimport type { IfInstalled, Infer, ValidationAdapter } from './types';\n\nclass ZodAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<z.ZodTypeAny>>(schema: S, data: unknown) {\n const result = await schema.safeParseAsync(data);\n\n if (result.success) {\n return {\n success: true,\n data: result.data as Infer<S>,\n } as const;\n }\n\n return {\n success: false,\n issues: result.error.issues.map(({ message, path }) => ({ message, path })),\n } as const;\n }\n}\n\nexport function zodAdapter() {\n return new ZodAdapter();\n}\n","import { Schema, ValidationAdapter, ValidationIssue } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport { HandlerFunction, HandlerServerErrorFn, InferMaybe, OriginalRouteHandler, RouteContext } from './types';\n\ntype Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T>;\n\ntype BuilderConfig<\n TParams extends Schema | undefined,\n TQuery extends Schema | undefined,\n TBody extends Schema | undefined,\n> = {\n paramsSchema: TParams;\n querySchema: TQuery;\n bodySchema: TBody;\n};\n\nclass BodyParsingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BodyParsingError';\n }\n}\n\nexport class RouteHandlerBuilder<\n TParams extends Schema | undefined = undefined,\n TQuery extends Schema | undefined = undefined,\n TBody extends Schema | undefined = undefined,\n TContext extends Record<string, unknown> = Record<string, unknown>,\n> {\n private config: BuilderConfig<TParams, TQuery, TBody>;\n private middlewares: Middleware[];\n private handleServerError?: HandlerServerErrorFn;\n private validationAdapter: ValidationAdapter;\n private baseContext: TContext;\n\n constructor({\n config = {\n paramsSchema: undefined as TParams,\n querySchema: undefined as TQuery,\n bodySchema: undefined as TBody,\n },\n validationAdapter = zodAdapter(),\n middlewares = [],\n handleServerError,\n baseContext,\n }: {\n config?: BuilderConfig<TParams, TQuery, TBody>;\n middlewares?: Middleware[];\n handleServerError?: HandlerServerErrorFn;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n }) {\n this.config = config;\n this.middlewares = middlewares;\n this.handleServerError = handleServerError;\n this.validationAdapter = validationAdapter;\n this.baseContext = (baseContext ?? {}) as TContext;\n }\n\n params<T extends Schema>(schema: T): RouteHandlerBuilder<T, TQuery, TBody, TContext> {\n return new RouteHandlerBuilder<T, TQuery, TBody, TContext>({\n config: { ...this.config, paramsSchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n query<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, T, TBody, TContext> {\n return new RouteHandlerBuilder<TParams, T, TBody, TContext>({\n config: { ...this.config, querySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n body<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, TQuery, T, TContext> {\n return new RouteHandlerBuilder<TParams, TQuery, T, TContext>({\n config: { ...this.config, bodySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n use<TReturnType extends Record<string, unknown>>(middleware: Middleware<TReturnType>) {\n return new RouteHandlerBuilder<TParams, TQuery, TBody, TContext & TReturnType>({\n config: this.config,\n middlewares: [...this.middlewares, middleware],\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext as unknown as TContext & TReturnType,\n });\n }\n\n handler(\n handler: HandlerFunction<InferMaybe<TParams>, InferMaybe<TQuery>, InferMaybe<TBody>, TContext>,\n ): OriginalRouteHandler {\n return async (request, context): Promise<Response> => {\n try {\n const routeContext = context ?? ({ params: {} } as RouteContext);\n const paramsInput = await this.resolveParams(routeContext.params);\n const queryInput = this.getQueryParams(request);\n const bodyInput = await this.parseRequestBody(request);\n\n const paramsValidation = await this.validateInput(this.config.paramsSchema, paramsInput, 'Invalid params');\n if (paramsValidation.success === false) {\n return paramsValidation.response;\n }\n\n const queryValidation = await this.validateInput(this.config.querySchema, queryInput, 'Invalid query');\n if (queryValidation.success === false) {\n return queryValidation.response;\n }\n\n const bodyValidation = await this.validateInput(this.config.bodySchema, bodyInput, 'Invalid body');\n if (bodyValidation.success === false) {\n return bodyValidation.response;\n }\n\n let middlewareContext: TContext = { ...this.baseContext };\n for (const middleware of this.middlewares) {\n const result = await middleware(request);\n middlewareContext = { ...middlewareContext, ...result };\n }\n\n return handler(request, {\n params: paramsValidation.data,\n query: queryValidation.data,\n body: bodyValidation.data,\n data: middlewareContext,\n });\n } catch (error) {\n if (error instanceof BodyParsingError) {\n return this.buildErrorResponse(error.message, undefined, 400);\n }\n\n if (this.handleServerError) {\n return this.handleServerError(error as Error);\n }\n\n return this.buildErrorResponse('Internal server error', undefined, 500);\n }\n };\n }\n\n private async resolveParams(params: RouteContext['params'] | undefined) {\n const resolvedParams = await Promise.resolve(params ?? {});\n return (resolvedParams ?? {}) as Record<string, unknown>;\n }\n\n private async validateInput<S extends Schema | undefined>(\n schema: S,\n data: unknown,\n errorMessage: string,\n ): Promise<{ success: true; data: InferMaybe<S> } | { success: false; response: Response }> {\n if (!schema) {\n return { success: true, data: (data ?? {}) as InferMaybe<S> };\n }\n\n const result = await this.validationAdapter.validate(schema, data);\n if (result.success === true) {\n return { success: true, data: result.data as InferMaybe<S> };\n }\n\n return { success: false, response: this.buildErrorResponse(errorMessage, result.issues) };\n }\n\n private getQueryParams(request: Request) {\n const url = new URL(request.url);\n return Object.fromEntries(url.searchParams.entries());\n }\n\n private async parseRequestBody(request: Request): Promise<unknown> {\n if (!this.config.bodySchema) {\n return {};\n }\n\n const contentType = request.headers.get('content-type') ?? '';\n const rawBody = await request.text();\n\n if (rawBody.length === 0) {\n return {};\n }\n\n if (!contentType.includes('application/json')) {\n throw new BodyParsingError('Unsupported content type. Expected application/json.');\n }\n\n try {\n return JSON.parse(rawBody);\n } catch (error) {\n throw new BodyParsingError('Invalid JSON body.');\n }\n }\n\n private buildErrorResponse(message: string, issues?: ValidationIssue[], status = 400) {\n return new Response(JSON.stringify({ message, issues }), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n }\n}\n","import { ValidationAdapter } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport { RouteHandlerBuilder } from './routeHandlerBuilder';\nimport { HandlerServerErrorFn } from './types';\n\ntype CreateSafeRouteParams<TContext extends Record<string, unknown>> = {\n handleServerError?: HandlerServerErrorFn;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n};\n\nexport function createSafeRoute<TContext extends Record<string, unknown> = Record<string, unknown>>(\n params?: CreateSafeRouteParams<TContext>,\n) {\n return new RouteHandlerBuilder<undefined, undefined, undefined, TContext>({\n handleServerError: params?.handleServerError,\n validationAdapter: params?.validationAdapter ?? zodAdapter(),\n baseContext: params?.baseContext,\n });\n}\n","// Code based on https://github.com/decs/typeschema/blob/main/packages/valibot/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { GenericSchema, GenericSchemaAsync } from 'valibot';\n\nimport type { IfInstalled, Infer, ValidationAdapter } from './types';\n\ntype ValibotModule = typeof import('valibot');\n\nasync function loadValibot(): Promise<ValibotModule> {\n try {\n return await import('valibot');\n } catch (error) {\n throw new Error(\n 'valibotAdapter requires optional peer dependency \"valibot\". Install it with `npm install valibot`.',\n { cause: error instanceof Error ? error : undefined },\n );\n }\n}\n\nclass ValibotAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<GenericSchema | GenericSchemaAsync>>(schema: S, data: unknown) {\n const valibot = await loadValibot();\n const result = await valibot.safeParseAsync(schema, data);\n\n if (result.success) {\n return {\n success: true,\n data: result.output as Infer<S>,\n } as const;\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message,\n path: valibot.getDotPath(issue)?.split('.'),\n })),\n } as const;\n }\n}\n\nexport function valibotAdapter() {\n return new ValibotAdapter();\n}\n","// Code based on https://github.com/decs/typeschema/blob/main/packages/yup/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { Schema as YupSchema } from 'yup';\n\nimport type { IfInstalled, Infer, ValidationAdapter, ValidationIssue } from './types';\n\ntype YupModule = typeof import('yup');\n\nasync function loadYup(): Promise<YupModule> {\n try {\n return await import('yup');\n } catch (error) {\n throw new Error('yupAdapter requires optional peer dependency \"yup\". Install it with `npm install yup`.', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n\nclass YupAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<YupSchema>>(schema: S, data: unknown) {\n const yup = await loadYup();\n\n try {\n const result = await schema.validate(data, { strict: true });\n\n return {\n success: true,\n data: result as Infer<S>,\n } as const;\n } catch (e) {\n if (e instanceof yup.ValidationError) {\n const { message, path } = e;\n\n return {\n success: false,\n issues: [\n {\n message,\n path: path && path.length > 0 ? path.split('.') : undefined,\n },\n ] as ValidationIssue[],\n } as const;\n }\n\n throw e;\n }\n }\n}\n\nexport function yupAdapter() {\n return new YupAdapter();\n}\n"],"mappings":"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","names":["src_exports","__export","RouteHandlerBuilder","createSafeRoute","valibotAdapter","yupAdapter","zodAdapter","__toCommonJS","ZodAdapter","schema","data","result","message","path","zodAdapter","BodyParsingError","message","RouteHandlerBuilder","_RouteHandlerBuilder","config","validationAdapter","zodAdapter","middlewares","handleServerError","baseContext","schema","middleware","handler","request","context","routeContext","paramsInput","queryInput","bodyInput","paramsValidation","queryValidation","bodyValidation","middlewareContext","result","error","params","data","errorMessage","url","contentType","rawBody","issues","status","createSafeRoute","params","RouteHandlerBuilder","zodAdapter","loadValibot","error","ValibotAdapter","schema","data","valibot","result","issue","valibotAdapter","loadYup","error","YupAdapter","schema","data","yup","e","message","path","yupAdapter"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/adapters/zod.ts","../src/routeHandlerBuilder.ts","../src/createSafeRoute.ts","../src/adapters/valibot.ts","../src/adapters/yup.ts"],"sourcesContent":["export { createSafeRoute } from './createSafeRoute';\nexport { RouteHandlerBuilder } from './routeHandlerBuilder';\nexport {\n type HandlerFunction,\n type HandlerServerErrorFn,\n type InferMaybe,\n type OriginalRouteHandler,\n type RouteContext,\n} from './types';\nexport {\n type IfInstalled,\n type Infer,\n type Schema,\n type ValidationAdapter,\n type ValidationIssue,\n} from './adapters/types';\nexport { zodAdapter } from './adapters/zod';\nexport { valibotAdapter } from './adapters/valibot';\nexport { yupAdapter } from './adapters/yup';\n","// Code based on https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { z } from 'zod';\n\nimport type { IfInstalled, Infer, ValidationAdapter } from './types';\n\nclass ZodAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<z.ZodTypeAny>>(schema: S, data: unknown) {\n const result = await schema.safeParseAsync(data);\n\n if (result.success) {\n return {\n success: true,\n data: result.data as Infer<S>,\n } as const;\n }\n\n return {\n success: false,\n issues: result.error.issues.map(({ message, path }) => ({ message, path })),\n } as const;\n }\n}\n\nexport function zodAdapter() {\n return new ZodAdapter();\n}\n","import { Schema, ValidationAdapter, ValidationIssue } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport {\n HandlerFunction,\n HandlerServerErrorFn,\n InferMaybe,\n OriginalRouteHandler,\n RouteContext,\n ValidationErrorHandler,\n} from './types';\n\ntype Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T | Response>;\n\ntype BuilderConfig<\n TParams extends Schema | undefined,\n TQuery extends Schema | undefined,\n TBody extends Schema | undefined,\n> = {\n paramsSchema: TParams;\n querySchema: TQuery;\n bodySchema: TBody;\n};\n\nclass BodyParsingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BodyParsingError';\n }\n}\n\nexport class RouteHandlerBuilder<\n TParams extends Schema | undefined = undefined,\n TQuery extends Schema | undefined = undefined,\n TBody extends Schema | undefined = undefined,\n TContext extends Record<string, unknown> = Record<string, unknown>,\n> {\n private config: BuilderConfig<TParams, TQuery, TBody>;\n private middlewares: Middleware[];\n private handleServerError?: HandlerServerErrorFn;\n private validationErrorHandler?: ValidationErrorHandler;\n private validationAdapter: ValidationAdapter;\n private baseContext: TContext;\n\n constructor({\n config = {\n paramsSchema: undefined as TParams,\n querySchema: undefined as TQuery,\n bodySchema: undefined as TBody,\n },\n validationAdapter = zodAdapter(),\n middlewares = [],\n handleServerError,\n validationErrorHandler,\n baseContext,\n }: {\n config?: BuilderConfig<TParams, TQuery, TBody>;\n middlewares?: Middleware[];\n handleServerError?: HandlerServerErrorFn;\n validationErrorHandler?: ValidationErrorHandler;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n }) {\n this.config = config;\n this.middlewares = middlewares;\n this.handleServerError = handleServerError;\n this.validationErrorHandler = validationErrorHandler;\n this.validationAdapter = validationAdapter;\n this.baseContext = (baseContext ?? {}) as TContext;\n }\n\n params<T extends Schema>(schema: T): RouteHandlerBuilder<T, TQuery, TBody, TContext> {\n return new RouteHandlerBuilder<T, TQuery, TBody, TContext>({\n config: { ...this.config, paramsSchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n query<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, T, TBody, TContext> {\n return new RouteHandlerBuilder<TParams, T, TBody, TContext>({\n config: { ...this.config, querySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n body<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, TQuery, T, TContext> {\n return new RouteHandlerBuilder<TParams, TQuery, T, TContext>({\n config: { ...this.config, bodySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n use<TReturnType extends Record<string, unknown>>(middleware: Middleware<TReturnType>) {\n return new RouteHandlerBuilder<TParams, TQuery, TBody, TContext & TReturnType>({\n config: this.config,\n middlewares: [...this.middlewares, middleware],\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext as unknown as TContext & TReturnType,\n });\n }\n\n handler(\n handler: HandlerFunction<InferMaybe<TParams>, InferMaybe<TQuery>, InferMaybe<TBody>, TContext>,\n ): OriginalRouteHandler {\n return async (request, context): Promise<Response> => {\n try {\n const routeContext = context ?? ({ params: {} } as RouteContext);\n const paramsInput = await this.resolveParams(routeContext.params);\n const queryInput = this.getQueryParams(request);\n const bodyInput = await this.parseRequestBody(request);\n\n const paramsValidation = await this.validateInput(this.config.paramsSchema, paramsInput, 'Invalid params');\n if (paramsValidation.success === false) {\n return paramsValidation.response;\n }\n\n const queryValidation = await this.validateInput(this.config.querySchema, queryInput, 'Invalid query');\n if (queryValidation.success === false) {\n return queryValidation.response;\n }\n\n const bodyValidation = await this.validateInput(this.config.bodySchema, bodyInput, 'Invalid body');\n if (bodyValidation.success === false) {\n return bodyValidation.response;\n }\n\n let middlewareContext: TContext = { ...this.baseContext };\n for (const middleware of this.middlewares) {\n const result = await middleware(request);\n if (result instanceof Response) {\n return result;\n }\n middlewareContext = { ...middlewareContext, ...(result as TContext) };\n }\n\n return handler(request, {\n params: paramsValidation.data,\n query: queryValidation.data,\n body: bodyValidation.data,\n data: middlewareContext,\n });\n } catch (error) {\n if (error instanceof BodyParsingError) {\n return this.buildErrorResponse(error.message, undefined, 400);\n }\n\n if (this.handleServerError) {\n return this.handleServerError(error as Error);\n }\n\n return this.buildErrorResponse('Internal server error', undefined, 500);\n }\n };\n }\n\n private async resolveParams(params: RouteContext['params'] | undefined) {\n const resolvedParams = await Promise.resolve(params ?? {});\n return (resolvedParams ?? {}) as Record<string, unknown>;\n }\n\n private async validateInput<S extends Schema | undefined>(\n schema: S,\n data: unknown,\n errorMessage: string,\n ): Promise<{ success: true; data: InferMaybe<S> } | { success: false; response: Response }> {\n if (!schema) {\n return { success: true, data: (data ?? {}) as InferMaybe<S> };\n }\n\n const result = await this.validationAdapter.validate(schema, data);\n if (result.success === true) {\n return { success: true, data: result.data as InferMaybe<S> };\n }\n\n if (this.validationErrorHandler) {\n return { success: false, response: this.validationErrorHandler(result.issues) };\n }\n\n return { success: false, response: this.buildErrorResponse(errorMessage, result.issues) };\n }\n\n private getQueryParams(request: Request) {\n const url = new URL(request.url);\n const params: Record<string, unknown> = {};\n const keys = Array.from(new Set(url.searchParams.keys()));\n\n for (const key of keys) {\n const values = url.searchParams.getAll(key);\n params[key] = values.length === 1 ? values[0] : values;\n }\n\n return params;\n }\n\n private async parseRequestBody(request: Request): Promise<unknown> {\n if (!this.config.bodySchema) {\n return {};\n }\n\n const contentType = request.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json')) {\n const rawBody = await request.text();\n\n if (rawBody.length === 0) {\n return {};\n }\n\n try {\n return JSON.parse(rawBody);\n } catch (error) {\n throw new BodyParsingError('Invalid JSON body.');\n }\n }\n\n if (contentType.includes('multipart/form-data') || contentType.includes('application/x-www-form-urlencoded')) {\n try {\n const formData = await request.formData();\n const data: Record<string, unknown> = {};\n const keys = Array.from(new Set(formData.keys()));\n\n for (const key of keys) {\n const values = formData.getAll(key);\n data[key] = values.length === 1 ? values[0] : values;\n }\n\n return data;\n } catch (error) {\n throw new BodyParsingError('Invalid Form Data.');\n }\n }\n\n throw new BodyParsingError(\n 'Unsupported content type. Expected application/json, multipart/form-data, or application/x-www-form-urlencoded.',\n );\n }\n\n private buildErrorResponse(message: string, issues?: ValidationIssue[], status = 400) {\n return new Response(JSON.stringify({ message, issues }), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n }\n}\n","import { ValidationAdapter } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport { RouteHandlerBuilder } from './routeHandlerBuilder';\nimport { HandlerServerErrorFn, ValidationErrorHandler } from './types';\n\ntype CreateSafeRouteParams<TContext extends Record<string, unknown>> = {\n handleServerError?: HandlerServerErrorFn;\n validationErrorHandler?: ValidationErrorHandler;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n};\n\nexport function createSafeRoute<TContext extends Record<string, unknown> = Record<string, unknown>>(\n params?: CreateSafeRouteParams<TContext>,\n) {\n return new RouteHandlerBuilder<undefined, undefined, undefined, TContext>({\n handleServerError: params?.handleServerError,\n validationErrorHandler: params?.validationErrorHandler,\n validationAdapter: params?.validationAdapter ?? zodAdapter(),\n baseContext: params?.baseContext,\n });\n}\n","// Code based on https://github.com/decs/typeschema/blob/main/packages/valibot/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { GenericSchema, GenericSchemaAsync } from 'valibot';\n\nimport type { IfInstalled, Infer, ValidationAdapter } from './types';\n\ntype ValibotModule = typeof import('valibot');\n\nasync function loadValibot(): Promise<ValibotModule> {\n try {\n return await import('valibot');\n } catch (error) {\n throw new Error(\n 'valibotAdapter requires optional peer dependency \"valibot\". Install it with `npm install valibot`.',\n { cause: error instanceof Error ? error : undefined },\n );\n }\n}\n\nclass ValibotAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<GenericSchema | GenericSchemaAsync>>(schema: S, data: unknown) {\n const valibot = await loadValibot();\n const result = await valibot.safeParseAsync(schema, data);\n\n if (result.success) {\n return {\n success: true,\n data: result.output as Infer<S>,\n } as const;\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message,\n path: valibot.getDotPath(issue)?.split('.'),\n })),\n } as const;\n }\n}\n\nexport function valibotAdapter() {\n return new ValibotAdapter();\n}\n","// Code based on https://github.com/decs/typeschema/blob/main/packages/yup/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { Schema as YupSchema } from 'yup';\n\nimport type { IfInstalled, Infer, ValidationAdapter, ValidationIssue } from './types';\n\ntype YupModule = typeof import('yup');\n\nasync function loadYup(): Promise<YupModule> {\n try {\n return await import('yup');\n } catch (error) {\n throw new Error('yupAdapter requires optional peer dependency \"yup\". Install it with `npm install yup`.', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n\nclass YupAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<YupSchema>>(schema: S, data: unknown) {\n const yup = await loadYup();\n\n try {\n const result = await schema.validate(data, { strict: true });\n\n return {\n success: true,\n data: result as Infer<S>,\n } as const;\n } catch (e) {\n if (e instanceof yup.ValidationError) {\n const { message, path } = e;\n\n return {\n success: false,\n issues: [\n {\n message,\n path: path && path.length > 0 ? path.split('.') : undefined,\n },\n ] as ValidationIssue[],\n } as const;\n }\n\n throw e;\n }\n }\n}\n\nexport function yupAdapter() {\n return new YupAdapter();\n}\n"],"mappings":"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","names":["src_exports","__export","RouteHandlerBuilder","createSafeRoute","valibotAdapter","yupAdapter","zodAdapter","__toCommonJS","ZodAdapter","schema","data","result","message","path","zodAdapter","BodyParsingError","message","RouteHandlerBuilder","_RouteHandlerBuilder","config","validationAdapter","zodAdapter","middlewares","handleServerError","validationErrorHandler","baseContext","schema","middleware","handler","request","context","routeContext","paramsInput","queryInput","bodyInput","paramsValidation","queryValidation","bodyValidation","middlewareContext","result","error","params","data","errorMessage","url","keys","key","values","contentType","rawBody","formData","issues","status","createSafeRoute","params","RouteHandlerBuilder","zodAdapter","loadValibot","error","ValibotAdapter","schema","data","valibot","result","issue","valibotAdapter","loadYup","error","YupAdapter","schema","data","yup","e","message","path","yupAdapter"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as
|
|
1
|
+
import{a as T}from"./chunk-SOIROJAG.mjs";import{a as x}from"./chunk-N3L74QA5.mjs";var y=class{async validate(e,r){let t=await e.safeParseAsync(r);return t.success?{success:!0,data:t.data}:{success:!1,issues:t.error.issues.map(({message:a,path:o})=>({message:a,path:o}))}}};function l(){return new y}var i=class extends Error{constructor(e){super(e),this.name="BodyParsingError"}},u=class n{config;middlewares;handleServerError;validationErrorHandler;validationAdapter;baseContext;constructor({config:e={paramsSchema:void 0,querySchema:void 0,bodySchema:void 0},validationAdapter:r=l(),middlewares:t=[],handleServerError:a,validationErrorHandler:o,baseContext:s}){this.config=e,this.middlewares=t,this.handleServerError=a,this.validationErrorHandler=o,this.validationAdapter=r,this.baseContext=s??{}}params(e){return new n({config:{...this.config,paramsSchema:e},middlewares:this.middlewares,handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}query(e){return new n({config:{...this.config,querySchema:e},middlewares:this.middlewares,handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}body(e){return new n({config:{...this.config,bodySchema:e},middlewares:this.middlewares,handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}use(e){return new n({config:this.config,middlewares:[...this.middlewares,e],handleServerError:this.handleServerError,validationErrorHandler:this.validationErrorHandler,validationAdapter:this.validationAdapter,baseContext:this.baseContext})}handler(e){return async(r,t)=>{try{let a=t??{params:{}},o=await this.resolveParams(a.params),s=this.getQueryParams(r),d=await this.parseRequestBody(r),c=await this.validateInput(this.config.paramsSchema,o,"Invalid params");if(c.success===!1)return c.response;let p=await this.validateInput(this.config.querySchema,s,"Invalid query");if(p.success===!1)return p.response;let m=await this.validateInput(this.config.bodySchema,d,"Invalid body");if(m.success===!1)return m.response;let f={...this.baseContext};for(let v of this.middlewares){let h=await v(r);if(h instanceof Response)return h;f={...f,...h}}return e(r,{params:c.data,query:p.data,body:m.data,data:f})}catch(a){return a instanceof i?this.buildErrorResponse(a.message,void 0,400):this.handleServerError?this.handleServerError(a):this.buildErrorResponse("Internal server error",void 0,500)}}}async resolveParams(e){return await Promise.resolve(e??{})??{}}async validateInput(e,r,t){if(!e)return{success:!0,data:r??{}};let a=await this.validationAdapter.validate(e,r);return a.success===!0?{success:!0,data:a.data}:this.validationErrorHandler?{success:!1,response:this.validationErrorHandler(a.issues)}:{success:!1,response:this.buildErrorResponse(t,a.issues)}}getQueryParams(e){let r=new URL(e.url),t={},a=Array.from(new Set(r.searchParams.keys()));for(let o of a){let s=r.searchParams.getAll(o);t[o]=s.length===1?s[0]:s}return t}async parseRequestBody(e){if(!this.config.bodySchema)return{};let r=e.headers.get("content-type")??"";if(r.includes("application/json")){let t=await e.text();if(t.length===0)return{};try{return JSON.parse(t)}catch{throw new i("Invalid JSON body.")}}if(r.includes("multipart/form-data")||r.includes("application/x-www-form-urlencoded"))try{let t=await e.formData(),a={},o=Array.from(new Set(t.keys()));for(let s of o){let d=t.getAll(s);a[s]=d.length===1?d[0]:d}return a}catch{throw new i("Invalid Form Data.")}throw new i("Unsupported content type. Expected application/json, multipart/form-data, or application/x-www-form-urlencoded.")}buildErrorResponse(e,r,t=400){return new Response(JSON.stringify({message:e,issues:r}),{status:t,headers:{"content-type":"application/json"}})}};function w(n){return new u({handleServerError:n?.handleServerError,validationErrorHandler:n?.validationErrorHandler,validationAdapter:n?.validationAdapter??l(),baseContext:n?.baseContext})}export{u as RouteHandlerBuilder,w as createSafeRoute,T as valibotAdapter,x as yupAdapter,l as zodAdapter};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/zod.ts","../src/routeHandlerBuilder.ts","../src/createSafeRoute.ts"],"sourcesContent":["// Code based on https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { z } from 'zod';\n\nimport type { IfInstalled, Infer, ValidationAdapter } from './types';\n\nclass ZodAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<z.ZodTypeAny>>(schema: S, data: unknown) {\n const result = await schema.safeParseAsync(data);\n\n if (result.success) {\n return {\n success: true,\n data: result.data as Infer<S>,\n } as const;\n }\n\n return {\n success: false,\n issues: result.error.issues.map(({ message, path }) => ({ message, path })),\n } as const;\n }\n}\n\nexport function zodAdapter() {\n return new ZodAdapter();\n}\n","import { Schema, ValidationAdapter, ValidationIssue } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport { HandlerFunction, HandlerServerErrorFn, InferMaybe, OriginalRouteHandler, RouteContext } from './types';\n\ntype Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T>;\n\ntype BuilderConfig<\n TParams extends Schema | undefined,\n TQuery extends Schema | undefined,\n TBody extends Schema | undefined,\n> = {\n paramsSchema: TParams;\n querySchema: TQuery;\n bodySchema: TBody;\n};\n\nclass BodyParsingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BodyParsingError';\n }\n}\n\nexport class RouteHandlerBuilder<\n TParams extends Schema | undefined = undefined,\n TQuery extends Schema | undefined = undefined,\n TBody extends Schema | undefined = undefined,\n TContext extends Record<string, unknown> = Record<string, unknown>,\n> {\n private config: BuilderConfig<TParams, TQuery, TBody>;\n private middlewares: Middleware[];\n private handleServerError?: HandlerServerErrorFn;\n private validationAdapter: ValidationAdapter;\n private baseContext: TContext;\n\n constructor({\n config = {\n paramsSchema: undefined as TParams,\n querySchema: undefined as TQuery,\n bodySchema: undefined as TBody,\n },\n validationAdapter = zodAdapter(),\n middlewares = [],\n handleServerError,\n baseContext,\n }: {\n config?: BuilderConfig<TParams, TQuery, TBody>;\n middlewares?: Middleware[];\n handleServerError?: HandlerServerErrorFn;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n }) {\n this.config = config;\n this.middlewares = middlewares;\n this.handleServerError = handleServerError;\n this.validationAdapter = validationAdapter;\n this.baseContext = (baseContext ?? {}) as TContext;\n }\n\n params<T extends Schema>(schema: T): RouteHandlerBuilder<T, TQuery, TBody, TContext> {\n return new RouteHandlerBuilder<T, TQuery, TBody, TContext>({\n config: { ...this.config, paramsSchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n query<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, T, TBody, TContext> {\n return new RouteHandlerBuilder<TParams, T, TBody, TContext>({\n config: { ...this.config, querySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n body<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, TQuery, T, TContext> {\n return new RouteHandlerBuilder<TParams, TQuery, T, TContext>({\n config: { ...this.config, bodySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n use<TReturnType extends Record<string, unknown>>(middleware: Middleware<TReturnType>) {\n return new RouteHandlerBuilder<TParams, TQuery, TBody, TContext & TReturnType>({\n config: this.config,\n middlewares: [...this.middlewares, middleware],\n handleServerError: this.handleServerError,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext as unknown as TContext & TReturnType,\n });\n }\n\n handler(\n handler: HandlerFunction<InferMaybe<TParams>, InferMaybe<TQuery>, InferMaybe<TBody>, TContext>,\n ): OriginalRouteHandler {\n return async (request, context): Promise<Response> => {\n try {\n const routeContext = context ?? ({ params: {} } as RouteContext);\n const paramsInput = await this.resolveParams(routeContext.params);\n const queryInput = this.getQueryParams(request);\n const bodyInput = await this.parseRequestBody(request);\n\n const paramsValidation = await this.validateInput(this.config.paramsSchema, paramsInput, 'Invalid params');\n if (paramsValidation.success === false) {\n return paramsValidation.response;\n }\n\n const queryValidation = await this.validateInput(this.config.querySchema, queryInput, 'Invalid query');\n if (queryValidation.success === false) {\n return queryValidation.response;\n }\n\n const bodyValidation = await this.validateInput(this.config.bodySchema, bodyInput, 'Invalid body');\n if (bodyValidation.success === false) {\n return bodyValidation.response;\n }\n\n let middlewareContext: TContext = { ...this.baseContext };\n for (const middleware of this.middlewares) {\n const result = await middleware(request);\n middlewareContext = { ...middlewareContext, ...result };\n }\n\n return handler(request, {\n params: paramsValidation.data,\n query: queryValidation.data,\n body: bodyValidation.data,\n data: middlewareContext,\n });\n } catch (error) {\n if (error instanceof BodyParsingError) {\n return this.buildErrorResponse(error.message, undefined, 400);\n }\n\n if (this.handleServerError) {\n return this.handleServerError(error as Error);\n }\n\n return this.buildErrorResponse('Internal server error', undefined, 500);\n }\n };\n }\n\n private async resolveParams(params: RouteContext['params'] | undefined) {\n const resolvedParams = await Promise.resolve(params ?? {});\n return (resolvedParams ?? {}) as Record<string, unknown>;\n }\n\n private async validateInput<S extends Schema | undefined>(\n schema: S,\n data: unknown,\n errorMessage: string,\n ): Promise<{ success: true; data: InferMaybe<S> } | { success: false; response: Response }> {\n if (!schema) {\n return { success: true, data: (data ?? {}) as InferMaybe<S> };\n }\n\n const result = await this.validationAdapter.validate(schema, data);\n if (result.success === true) {\n return { success: true, data: result.data as InferMaybe<S> };\n }\n\n return { success: false, response: this.buildErrorResponse(errorMessage, result.issues) };\n }\n\n private getQueryParams(request: Request) {\n const url = new URL(request.url);\n return Object.fromEntries(url.searchParams.entries());\n }\n\n private async parseRequestBody(request: Request): Promise<unknown> {\n if (!this.config.bodySchema) {\n return {};\n }\n\n const contentType = request.headers.get('content-type') ?? '';\n const rawBody = await request.text();\n\n if (rawBody.length === 0) {\n return {};\n }\n\n if (!contentType.includes('application/json')) {\n throw new BodyParsingError('Unsupported content type. Expected application/json.');\n }\n\n try {\n return JSON.parse(rawBody);\n } catch (error) {\n throw new BodyParsingError('Invalid JSON body.');\n }\n }\n\n private buildErrorResponse(message: string, issues?: ValidationIssue[], status = 400) {\n return new Response(JSON.stringify({ message, issues }), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n }\n}\n","import { ValidationAdapter } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport { RouteHandlerBuilder } from './routeHandlerBuilder';\nimport { HandlerServerErrorFn } from './types';\n\ntype CreateSafeRouteParams<TContext extends Record<string, unknown>> = {\n handleServerError?: HandlerServerErrorFn;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n};\n\nexport function createSafeRoute<TContext extends Record<string, unknown> = Record<string, unknown>>(\n params?: CreateSafeRouteParams<TContext>,\n) {\n return new RouteHandlerBuilder<undefined, undefined, undefined, TContext>({\n handleServerError: params?.handleServerError,\n validationAdapter: params?.validationAdapter ?? zodAdapter(),\n baseContext: params?.baseContext,\n });\n}\n"],"mappings":"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","names":["ZodAdapter","schema","data","result","message","path","zodAdapter","BodyParsingError","message","RouteHandlerBuilder","_RouteHandlerBuilder","config","validationAdapter","zodAdapter","middlewares","handleServerError","baseContext","schema","middleware","handler","request","context","routeContext","paramsInput","queryInput","bodyInput","paramsValidation","queryValidation","bodyValidation","middlewareContext","result","error","params","data","errorMessage","url","contentType","rawBody","issues","status","createSafeRoute","params","RouteHandlerBuilder","zodAdapter"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapters/zod.ts","../src/routeHandlerBuilder.ts","../src/createSafeRoute.ts"],"sourcesContent":["// Code based on https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts\n// MIT License\n// Copyright (c) 2023 André Costa\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nimport type { z } from 'zod';\n\nimport type { IfInstalled, Infer, ValidationAdapter } from './types';\n\nclass ZodAdapter implements ValidationAdapter {\n async validate<S extends IfInstalled<z.ZodTypeAny>>(schema: S, data: unknown) {\n const result = await schema.safeParseAsync(data);\n\n if (result.success) {\n return {\n success: true,\n data: result.data as Infer<S>,\n } as const;\n }\n\n return {\n success: false,\n issues: result.error.issues.map(({ message, path }) => ({ message, path })),\n } as const;\n }\n}\n\nexport function zodAdapter() {\n return new ZodAdapter();\n}\n","import { Schema, ValidationAdapter, ValidationIssue } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport {\n HandlerFunction,\n HandlerServerErrorFn,\n InferMaybe,\n OriginalRouteHandler,\n RouteContext,\n ValidationErrorHandler,\n} from './types';\n\ntype Middleware<T = Record<string, unknown>> = (request: Request) => Promise<T | Response>;\n\ntype BuilderConfig<\n TParams extends Schema | undefined,\n TQuery extends Schema | undefined,\n TBody extends Schema | undefined,\n> = {\n paramsSchema: TParams;\n querySchema: TQuery;\n bodySchema: TBody;\n};\n\nclass BodyParsingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BodyParsingError';\n }\n}\n\nexport class RouteHandlerBuilder<\n TParams extends Schema | undefined = undefined,\n TQuery extends Schema | undefined = undefined,\n TBody extends Schema | undefined = undefined,\n TContext extends Record<string, unknown> = Record<string, unknown>,\n> {\n private config: BuilderConfig<TParams, TQuery, TBody>;\n private middlewares: Middleware[];\n private handleServerError?: HandlerServerErrorFn;\n private validationErrorHandler?: ValidationErrorHandler;\n private validationAdapter: ValidationAdapter;\n private baseContext: TContext;\n\n constructor({\n config = {\n paramsSchema: undefined as TParams,\n querySchema: undefined as TQuery,\n bodySchema: undefined as TBody,\n },\n validationAdapter = zodAdapter(),\n middlewares = [],\n handleServerError,\n validationErrorHandler,\n baseContext,\n }: {\n config?: BuilderConfig<TParams, TQuery, TBody>;\n middlewares?: Middleware[];\n handleServerError?: HandlerServerErrorFn;\n validationErrorHandler?: ValidationErrorHandler;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n }) {\n this.config = config;\n this.middlewares = middlewares;\n this.handleServerError = handleServerError;\n this.validationErrorHandler = validationErrorHandler;\n this.validationAdapter = validationAdapter;\n this.baseContext = (baseContext ?? {}) as TContext;\n }\n\n params<T extends Schema>(schema: T): RouteHandlerBuilder<T, TQuery, TBody, TContext> {\n return new RouteHandlerBuilder<T, TQuery, TBody, TContext>({\n config: { ...this.config, paramsSchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n query<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, T, TBody, TContext> {\n return new RouteHandlerBuilder<TParams, T, TBody, TContext>({\n config: { ...this.config, querySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n body<T extends Schema>(schema: T): RouteHandlerBuilder<TParams, TQuery, T, TContext> {\n return new RouteHandlerBuilder<TParams, TQuery, T, TContext>({\n config: { ...this.config, bodySchema: schema },\n middlewares: this.middlewares,\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext,\n });\n }\n\n use<TReturnType extends Record<string, unknown>>(middleware: Middleware<TReturnType>) {\n return new RouteHandlerBuilder<TParams, TQuery, TBody, TContext & TReturnType>({\n config: this.config,\n middlewares: [...this.middlewares, middleware],\n handleServerError: this.handleServerError,\n validationErrorHandler: this.validationErrorHandler,\n validationAdapter: this.validationAdapter,\n baseContext: this.baseContext as unknown as TContext & TReturnType,\n });\n }\n\n handler(\n handler: HandlerFunction<InferMaybe<TParams>, InferMaybe<TQuery>, InferMaybe<TBody>, TContext>,\n ): OriginalRouteHandler {\n return async (request, context): Promise<Response> => {\n try {\n const routeContext = context ?? ({ params: {} } as RouteContext);\n const paramsInput = await this.resolveParams(routeContext.params);\n const queryInput = this.getQueryParams(request);\n const bodyInput = await this.parseRequestBody(request);\n\n const paramsValidation = await this.validateInput(this.config.paramsSchema, paramsInput, 'Invalid params');\n if (paramsValidation.success === false) {\n return paramsValidation.response;\n }\n\n const queryValidation = await this.validateInput(this.config.querySchema, queryInput, 'Invalid query');\n if (queryValidation.success === false) {\n return queryValidation.response;\n }\n\n const bodyValidation = await this.validateInput(this.config.bodySchema, bodyInput, 'Invalid body');\n if (bodyValidation.success === false) {\n return bodyValidation.response;\n }\n\n let middlewareContext: TContext = { ...this.baseContext };\n for (const middleware of this.middlewares) {\n const result = await middleware(request);\n if (result instanceof Response) {\n return result;\n }\n middlewareContext = { ...middlewareContext, ...(result as TContext) };\n }\n\n return handler(request, {\n params: paramsValidation.data,\n query: queryValidation.data,\n body: bodyValidation.data,\n data: middlewareContext,\n });\n } catch (error) {\n if (error instanceof BodyParsingError) {\n return this.buildErrorResponse(error.message, undefined, 400);\n }\n\n if (this.handleServerError) {\n return this.handleServerError(error as Error);\n }\n\n return this.buildErrorResponse('Internal server error', undefined, 500);\n }\n };\n }\n\n private async resolveParams(params: RouteContext['params'] | undefined) {\n const resolvedParams = await Promise.resolve(params ?? {});\n return (resolvedParams ?? {}) as Record<string, unknown>;\n }\n\n private async validateInput<S extends Schema | undefined>(\n schema: S,\n data: unknown,\n errorMessage: string,\n ): Promise<{ success: true; data: InferMaybe<S> } | { success: false; response: Response }> {\n if (!schema) {\n return { success: true, data: (data ?? {}) as InferMaybe<S> };\n }\n\n const result = await this.validationAdapter.validate(schema, data);\n if (result.success === true) {\n return { success: true, data: result.data as InferMaybe<S> };\n }\n\n if (this.validationErrorHandler) {\n return { success: false, response: this.validationErrorHandler(result.issues) };\n }\n\n return { success: false, response: this.buildErrorResponse(errorMessage, result.issues) };\n }\n\n private getQueryParams(request: Request) {\n const url = new URL(request.url);\n const params: Record<string, unknown> = {};\n const keys = Array.from(new Set(url.searchParams.keys()));\n\n for (const key of keys) {\n const values = url.searchParams.getAll(key);\n params[key] = values.length === 1 ? values[0] : values;\n }\n\n return params;\n }\n\n private async parseRequestBody(request: Request): Promise<unknown> {\n if (!this.config.bodySchema) {\n return {};\n }\n\n const contentType = request.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json')) {\n const rawBody = await request.text();\n\n if (rawBody.length === 0) {\n return {};\n }\n\n try {\n return JSON.parse(rawBody);\n } catch (error) {\n throw new BodyParsingError('Invalid JSON body.');\n }\n }\n\n if (contentType.includes('multipart/form-data') || contentType.includes('application/x-www-form-urlencoded')) {\n try {\n const formData = await request.formData();\n const data: Record<string, unknown> = {};\n const keys = Array.from(new Set(formData.keys()));\n\n for (const key of keys) {\n const values = formData.getAll(key);\n data[key] = values.length === 1 ? values[0] : values;\n }\n\n return data;\n } catch (error) {\n throw new BodyParsingError('Invalid Form Data.');\n }\n }\n\n throw new BodyParsingError(\n 'Unsupported content type. Expected application/json, multipart/form-data, or application/x-www-form-urlencoded.',\n );\n }\n\n private buildErrorResponse(message: string, issues?: ValidationIssue[], status = 400) {\n return new Response(JSON.stringify({ message, issues }), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n }\n}\n","import { ValidationAdapter } from './adapters/types';\nimport { zodAdapter } from './adapters/zod';\nimport { RouteHandlerBuilder } from './routeHandlerBuilder';\nimport { HandlerServerErrorFn, ValidationErrorHandler } from './types';\n\ntype CreateSafeRouteParams<TContext extends Record<string, unknown>> = {\n handleServerError?: HandlerServerErrorFn;\n validationErrorHandler?: ValidationErrorHandler;\n validationAdapter?: ValidationAdapter;\n baseContext?: TContext;\n};\n\nexport function createSafeRoute<TContext extends Record<string, unknown> = Record<string, unknown>>(\n params?: CreateSafeRouteParams<TContext>,\n) {\n return new RouteHandlerBuilder<undefined, undefined, undefined, TContext>({\n handleServerError: params?.handleServerError,\n validationErrorHandler: params?.validationErrorHandler,\n validationAdapter: params?.validationAdapter ?? zodAdapter(),\n baseContext: params?.baseContext,\n });\n}\n"],"mappings":"kFAsBA,IAAMA,EAAN,KAA8C,CAC5C,MAAM,SAA8CC,EAAWC,EAAe,CAC5E,IAAMC,EAAS,MAAMF,EAAO,eAAeC,CAAI,EAE/C,OAAIC,EAAO,QACF,CACL,QAAS,GACT,KAAMA,EAAO,IACf,EAGK,CACL,QAAS,GACT,OAAQA,EAAO,MAAM,OAAO,IAAI,CAAC,CAAE,QAAAC,EAAS,KAAAC,CAAK,KAAO,CAAE,QAAAD,EAAS,KAAAC,CAAK,EAAE,CAC5E,CACF,CACF,EAEO,SAASC,GAAa,CAC3B,OAAO,IAAIN,CACb,CCnBA,IAAMO,EAAN,cAA+B,KAAM,CACnC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,kBACd,CACF,EAEaC,EAAN,MAAMC,CAKX,CACQ,OACA,YACA,kBACA,uBACA,kBACA,YAER,YAAY,CACV,OAAAC,EAAS,CACP,aAAc,OACd,YAAa,OACb,WAAY,MACd,EACA,kBAAAC,EAAoBC,EAAW,EAC/B,YAAAC,EAAc,CAAC,EACf,kBAAAC,EACA,uBAAAC,EACA,YAAAC,CACF,EAOG,CACD,KAAK,OAASN,EACd,KAAK,YAAcG,EACnB,KAAK,kBAAoBC,EACzB,KAAK,uBAAyBC,EAC9B,KAAK,kBAAoBJ,EACzB,KAAK,YAAeK,GAAe,CAAC,CACtC,CAEA,OAAyBC,EAA4D,CACnF,OAAO,IAAIR,EAAgD,CACzD,OAAQ,CAAE,GAAG,KAAK,OAAQ,aAAcQ,CAAO,EAC/C,YAAa,KAAK,YAClB,kBAAmB,KAAK,kBACxB,uBAAwB,KAAK,uBAC7B,kBAAmB,KAAK,kBACxB,YAAa,KAAK,WACpB,CAAC,CACH,CAEA,MAAwBA,EAA6D,CACnF,OAAO,IAAIR,EAAiD,CAC1D,OAAQ,CAAE,GAAG,KAAK,OAAQ,YAAaQ,CAAO,EAC9C,YAAa,KAAK,YAClB,kBAAmB,KAAK,kBACxB,uBAAwB,KAAK,uBAC7B,kBAAmB,KAAK,kBACxB,YAAa,KAAK,WACpB,CAAC,CACH,CAEA,KAAuBA,EAA8D,CACnF,OAAO,IAAIR,EAAkD,CAC3D,OAAQ,CAAE,GAAG,KAAK,OAAQ,WAAYQ,CAAO,EAC7C,YAAa,KAAK,YAClB,kBAAmB,KAAK,kBACxB,uBAAwB,KAAK,uBAC7B,kBAAmB,KAAK,kBACxB,YAAa,KAAK,WACpB,CAAC,CACH,CAEA,IAAiDC,EAAqC,CACpF,OAAO,IAAIT,EAAoE,CAC7E,OAAQ,KAAK,OACb,YAAa,CAAC,GAAG,KAAK,YAAaS,CAAU,EAC7C,kBAAmB,KAAK,kBACxB,uBAAwB,KAAK,uBAC7B,kBAAmB,KAAK,kBACxB,YAAa,KAAK,WACpB,CAAC,CACH,CAEA,QACEC,EACsB,CACtB,MAAO,OAAOC,EAASC,IAA+B,CACpD,GAAI,CACF,IAAMC,EAAeD,GAAY,CAAE,OAAQ,CAAC,CAAE,EACxCE,EAAc,MAAM,KAAK,cAAcD,EAAa,MAAM,EAC1DE,EAAa,KAAK,eAAeJ,CAAO,EACxCK,EAAY,MAAM,KAAK,iBAAiBL,CAAO,EAE/CM,EAAmB,MAAM,KAAK,cAAc,KAAK,OAAO,aAAcH,EAAa,gBAAgB,EACzG,GAAIG,EAAiB,UAAY,GAC/B,OAAOA,EAAiB,SAG1B,IAAMC,EAAkB,MAAM,KAAK,cAAc,KAAK,OAAO,YAAaH,EAAY,eAAe,EACrG,GAAIG,EAAgB,UAAY,GAC9B,OAAOA,EAAgB,SAGzB,IAAMC,EAAiB,MAAM,KAAK,cAAc,KAAK,OAAO,WAAYH,EAAW,cAAc,EACjG,GAAIG,EAAe,UAAY,GAC7B,OAAOA,EAAe,SAGxB,IAAIC,EAA8B,CAAE,GAAG,KAAK,WAAY,EACxD,QAAWX,KAAc,KAAK,YAAa,CACzC,IAAMY,EAAS,MAAMZ,EAAWE,CAAO,EACvC,GAAIU,aAAkB,SACpB,OAAOA,EAETD,EAAoB,CAAE,GAAGA,EAAmB,GAAIC,CAAoB,CACtE,CAEA,OAAOX,EAAQC,EAAS,CACtB,OAAQM,EAAiB,KACzB,MAAOC,EAAgB,KACvB,KAAMC,EAAe,KACrB,KAAMC,CACR,CAAC,CACH,OAASE,EAAO,CACd,OAAIA,aAAiBzB,EACZ,KAAK,mBAAmByB,EAAM,QAAS,OAAW,GAAG,EAG1D,KAAK,kBACA,KAAK,kBAAkBA,CAAc,EAGvC,KAAK,mBAAmB,wBAAyB,OAAW,GAAG,CACxE,CACF,CACF,CAEA,MAAc,cAAcC,EAA4C,CAEtE,OADuB,MAAM,QAAQ,QAAQA,GAAU,CAAC,CAAC,GAC/B,CAAC,CAC7B,CAEA,MAAc,cACZf,EACAgB,EACAC,EAC0F,CAC1F,GAAI,CAACjB,EACH,MAAO,CAAE,QAAS,GAAM,KAAOgB,GAAQ,CAAC,CAAoB,EAG9D,IAAMH,EAAS,MAAM,KAAK,kBAAkB,SAASb,EAAQgB,CAAI,EACjE,OAAIH,EAAO,UAAY,GACd,CAAE,QAAS,GAAM,KAAMA,EAAO,IAAsB,EAGzD,KAAK,uBACA,CAAE,QAAS,GAAO,SAAU,KAAK,uBAAuBA,EAAO,MAAM,CAAE,EAGzE,CAAE,QAAS,GAAO,SAAU,KAAK,mBAAmBI,EAAcJ,EAAO,MAAM,CAAE,CAC1F,CAEQ,eAAeV,EAAkB,CACvC,IAAMe,EAAM,IAAI,IAAIf,EAAQ,GAAG,EACzBY,EAAkC,CAAC,EACnCI,EAAO,MAAM,KAAK,IAAI,IAAID,EAAI,aAAa,KAAK,CAAC,CAAC,EAExD,QAAWE,KAAOD,EAAM,CACtB,IAAME,EAASH,EAAI,aAAa,OAAOE,CAAG,EAC1CL,EAAOK,CAAG,EAAIC,EAAO,SAAW,EAAIA,EAAO,CAAC,EAAIA,CAClD,CAEA,OAAON,CACT,CAEA,MAAc,iBAAiBZ,EAAoC,CACjE,GAAI,CAAC,KAAK,OAAO,WACf,MAAO,CAAC,EAGV,IAAMmB,EAAcnB,EAAQ,QAAQ,IAAI,cAAc,GAAK,GAE3D,GAAImB,EAAY,SAAS,kBAAkB,EAAG,CAC5C,IAAMC,EAAU,MAAMpB,EAAQ,KAAK,EAEnC,GAAIoB,EAAQ,SAAW,EACrB,MAAO,CAAC,EAGV,GAAI,CACF,OAAO,KAAK,MAAMA,CAAO,CAC3B,MAAgB,CACd,MAAM,IAAIlC,EAAiB,oBAAoB,CACjD,CACF,CAEA,GAAIiC,EAAY,SAAS,qBAAqB,GAAKA,EAAY,SAAS,mCAAmC,EACzG,GAAI,CACF,IAAME,EAAW,MAAMrB,EAAQ,SAAS,EAClCa,EAAgC,CAAC,EACjCG,EAAO,MAAM,KAAK,IAAI,IAAIK,EAAS,KAAK,CAAC,CAAC,EAEhD,QAAWJ,KAAOD,EAAM,CACtB,IAAME,EAASG,EAAS,OAAOJ,CAAG,EAClCJ,EAAKI,CAAG,EAAIC,EAAO,SAAW,EAAIA,EAAO,CAAC,EAAIA,CAChD,CAEA,OAAOL,CACT,MAAgB,CACd,MAAM,IAAI3B,EAAiB,oBAAoB,CACjD,CAGF,MAAM,IAAIA,EACR,iHACF,CACF,CAEQ,mBAAmBC,EAAiBmC,EAA4BC,EAAS,IAAK,CACpF,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,QAAApC,EAAS,OAAAmC,CAAO,CAAC,EAAG,CACvD,OAAAC,EACA,QAAS,CAAE,eAAgB,kBAAmB,CAChD,CAAC,CACH,CACF,ECpPO,SAASC,EACdC,EACA,CACA,OAAO,IAAIC,EAA+D,CACxE,kBAAmBD,GAAQ,kBAC3B,uBAAwBA,GAAQ,uBAChC,kBAAmBA,GAAQ,mBAAqBE,EAAW,EAC3D,YAAaF,GAAQ,WACvB,CAAC,CACH","names":["ZodAdapter","schema","data","result","message","path","zodAdapter","BodyParsingError","message","RouteHandlerBuilder","_RouteHandlerBuilder","config","validationAdapter","zodAdapter","middlewares","handleServerError","validationErrorHandler","baseContext","schema","middleware","handler","request","context","routeContext","paramsInput","queryInput","bodyInput","paramsValidation","queryValidation","bodyValidation","middlewareContext","result","error","params","data","errorMessage","url","keys","key","values","contentType","rawBody","formData","issues","status","createSafeRoute","params","RouteHandlerBuilder","zodAdapter"]}
|
package/dist/valibot.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GenericSchema, GenericSchemaAsync } from 'valibot';
|
|
2
|
-
import {
|
|
2
|
+
import { a as ValidationAdapter, b as IfInstalled, I as Infer } from './types-UXG9BoMB.mjs';
|
|
3
3
|
import '@sinclair/typebox';
|
|
4
4
|
import 'yup';
|
|
5
5
|
import 'zod';
|
package/dist/valibot.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GenericSchema, GenericSchemaAsync } from 'valibot';
|
|
2
|
-
import {
|
|
2
|
+
import { a as ValidationAdapter, b as IfInstalled, I as Infer } from './types-UXG9BoMB.js';
|
|
3
3
|
import '@sinclair/typebox';
|
|
4
4
|
import 'yup';
|
|
5
5
|
import 'zod';
|
package/dist/yup.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from 'yup';
|
|
2
|
-
import {
|
|
2
|
+
import { a as ValidationAdapter, b as IfInstalled, I as Infer, V as ValidationIssue } from './types-UXG9BoMB.mjs';
|
|
3
3
|
import '@sinclair/typebox';
|
|
4
4
|
import 'valibot';
|
|
5
5
|
import 'zod';
|
package/dist/yup.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from 'yup';
|
|
2
|
-
import {
|
|
2
|
+
import { a as ValidationAdapter, b as IfInstalled, I as Infer, V as ValidationIssue } from './types-UXG9BoMB.js';
|
|
3
3
|
import '@sinclair/typebox';
|
|
4
4
|
import 'valibot';
|
|
5
5
|
import 'zod';
|