better-call 0.0.5-beta.2 → 0.0.5-beta.3

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/README.md CHANGED
@@ -4,7 +4,6 @@ Better call is a tiny web framework for creating endpoints that can be invoked a
4
4
 
5
5
  Built for typescript and it comes with a very high performance router based on [rou3](https://github.com/unjs/rou3).
6
6
 
7
-
8
7
  > ⚠️ This project early in development and not ready for production use. But feel free to try it out and give feedback.
9
8
 
10
9
  ## Install
@@ -15,7 +14,7 @@ pnpm i better-call
15
14
 
16
15
  ## Usage
17
16
 
18
- The building blocks for better-call are endpoints. You can create an endpoint by calling `createEndpoint` and passing it a path, inputs that can be validated with zod (body, query) and a function that will be invoked when the endpoint is called.
17
+ The building blocks for better-call are endpoints. You can create an endpoint by calling `createEndpoint` and passing it a path, [options](#endpointoptions) and a function that will be invoked when the endpoint is called.
19
18
 
20
19
  ```ts
21
20
  import { createEndpoint, createRouter } from "better-call"
@@ -58,36 +57,45 @@ Bun.serve({
58
57
 
59
58
  ### Middleware
60
59
 
61
- You can create middleware by calling `createMiddleware` and passing it a function that will be invoked before the endpoint is called.
60
+ Endpoints can use middleware by passing the `use` option to the endpoint. To create a middleware, you can call `createMiddleware` and pass it a function or an options object and a handler function.
62
61
 
63
- If you return a context object from the middleware, it will be merged with the context object on the endpoint.
62
+ If you return a context object from the middleware, it will be available in the endpoint context.
64
63
 
65
64
  ```ts
66
- import { createMiddleware, createEndpoint } from "better-call"
67
-
68
- const createProtectedEndpoint = createMiddleware(async (ctx) => {
69
- if(ctx.headers.get("Authorization") !== "Bearer 123") {
70
- throw new Error("Unauthorized")
71
- }
65
+ const middleware = createMiddleware(async (ctx) => {
72
66
  return {
73
- context: {
74
- session: {
75
- id: "123",
76
- }
77
- }
67
+ name: "hello"
78
68
  }
79
69
  })
70
+ const endpoint = createEndpoint("/", {
71
+ method: "GET",
72
+ use: [middleware],
73
+ }, async (ctx) => {
74
+ //this will be the context object returned by the middleware with the name property
75
+ ctx.context
76
+ })
77
+ ```
80
78
 
81
- const getUser = createProtectedEndpoint("/:id", {
82
- method: "GET"
83
- },
84
- async (ctx) => {
85
- const user = await getUserFromDatabase(ctx.params.id)
86
- // ctx.session is the session object
79
+ You can also pass an options object to the middleware and a handler function.
80
+
81
+ ```ts
82
+ const middleware = createMiddleware({
83
+ body: z.object({
84
+ name: z.string()
85
+ })
86
+ }, async (ctx) => {
87
87
  return {
88
- user: ctx.session.user
88
+ name: "hello"
89
89
  }
90
90
  })
91
+
92
+ const endpoint = createEndpoint("/", {
93
+ method: "GET",
94
+ use: [middleware],
95
+ }, async (ctx) => {
96
+ //the body will also contain the middleware body
97
+ ctx.body
98
+ })
91
99
  ```
92
100
 
93
101
  ### Router
@@ -102,8 +110,32 @@ const router = createRouter([
102
110
  createItem
103
111
  ])
104
112
  ```
113
+
105
114
  Behind the scenes, the router uses [rou3](https://github.com/unjs/rou3) to match the endpoints and invoke the correct endpoint. You can look at the [rou3 documentation](https://github.com/unjs/rou3) for more information.
106
115
 
116
+ #### Router Options
117
+
118
+ **routerMiddleware**: A router middleware is similar to an endpoint middleware but it's applied to any path that matches the route. It's like a traditional middleware you specify a path matcher and a handler function and it will be called before the endpoint.
119
+
120
+ ```ts
121
+ const router = createRouter([
122
+ createItem
123
+ ], {
124
+ routerMiddleware: [
125
+ {
126
+ path: "/api/**",
127
+ handler: middleware
128
+ }
129
+ ]
130
+ })
131
+ ```
132
+
133
+ **basePath**: The base path for the router. All paths will be relative to this path.
134
+
135
+ **onError**: The router will call this function if an error occurs in the middleware or the endpoint.
136
+
137
+ **throwError**: If true, the router will throw an error if an error occurs in the middleware or the endpoint.
138
+
107
139
 
108
140
  ### Returning non 200 responses
109
141
 
@@ -168,15 +200,7 @@ const createItem = createEndpoint("/item", {
168
200
  })
169
201
  ```
170
202
 
171
- ## API
172
-
173
- ### `createEndpoint`
174
-
175
- ```ts
176
- createEndpoint(path: string, options: EndpointOptions, fn: EndpointFunction): Endpoint
177
- ```
178
-
179
- Creates an endpoint. The `path` is the path that will be used to match the endpoint. The `options` are the options that will be used to validate the request. The `fn` is the function that will be invoked when the endpoint is called.
203
+ ### Endpoint Parameters
180
204
 
181
205
  ### `path`
182
206
 
@@ -205,6 +229,7 @@ type EndpointOptions = {
205
229
  params?: ZodSchema
206
230
  requireHeaders?: boolean
207
231
  requireRequest?: boolean
232
+ use?: Endpoint[]
208
233
  }
209
234
  ```
210
235
 
@@ -220,6 +245,7 @@ type EndpointOptions = {
220
245
 
221
246
  - **requireRequest**: if true, the endpoint will throw an error if the request doesn't have a request object. And even when you call the endpoint as a function, it will require a request to be passed in the context.
222
247
 
248
+ - **use**: the use option accepts an array of endpoints and will be called before the endpoint is called.
223
249
 
224
250
  ### `EndpointFunction`
225
251
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var h=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var T=(t,e,n)=>e in t?h(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var O=(t,e)=>{for(var n in e)h(t,n,{get:e[n],enumerable:!0})},I=(t,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of q(e))!b.call(t,r)&&r!==n&&h(t,r,{get:()=>e[r],enumerable:!(a=C(e,r))||a.enumerable});return t};var M=t=>I(h({},"__esModule",{value:!0}),t);var m=(t,e,n)=>T(t,typeof e!="symbol"?e+"":e,n);var k={};O(k,{APIError:()=>u,createEndpoint:()=>S,createMiddleware:()=>B,createRouter:()=>H,getBody:()=>x,shouldSerialize:()=>R,statusCode:()=>P});module.exports=M(k);var E=require("zod");var u=class extends Error{constructor(n,a){super(`API Error: ${n} ${a?.message??""}`,{cause:a});m(this,"status");m(this,"body");this.status=n,this.body=a??{},this.stack="",this.name="BetterCallAPIError"}};function S(t,e,n){let a=new Headers,r=async(...s)=>{let o={setHeader(d,i){a.set(d,i)},setCookie(d,i){a.append("Set-Cookie",`${d}=${i}`)},getCookie(d){return s[0]?.headers?.get("cookie")?.split(";").find(l=>l.startsWith(`${d}=`))?.split("=")[1]},...s[0]||{}};try{o.body=e.body?e.body.parse(o.body):void 0,o.query=e.query?e.query.parse(o.query):void 0,o.params=e.params?e.params.parse(o.params):void 0}catch(d){throw d instanceof E.ZodError?new u("Bad Request",{message:d.message,details:d.errors}):d}if(e.requireHeaders&&!o.headers)throw new u("Bad Request",{message:"Headers are required"});if(e.requireRequest&&!o.request)throw new u("Bad Request",{message:"Request is required"});return await n(o)};return r.path=t,r.options=e,r.method=e.method,r.headers=a,r.middleware=void 0,r}var f=require("rou3");async function x(t){let e=t.headers.get("content-type")||"";if(t.body){if(e.includes("application/json"))return await t.json();if(e.includes("application/x-www-form-urlencoded")){let n=await t.formData(),a={};return n.forEach((r,s)=>{a[s]=r.toString()}),a}if(e.includes("multipart/form-data")){let n=await t.formData(),a={};return n.forEach((r,s)=>{a[s]=r}),a}return e.includes("text/plain")?await t.text():e.includes("application/octet-stream")?await t.arrayBuffer():e.includes("application/pdf")||e.includes("image/")||e.includes("video/")?await t.blob():e.includes("application/stream")||t.body instanceof ReadableStream?t.body:await t.text()}}function R(t){return typeof t=="object"&&t!==null&&!(t instanceof Blob)&&!(t instanceof FormData)}var P={OK:200,Created:201,Accepted:202,"No Content":204,"Multiple Choices":300,"Moved Permanently":301,Found:302,"See Other":303,"Not Modified":304,"Temporary Redirect":307,"Bad Request":400,Unauthorized:401,"Payment Required":402,Forbidden:403,"Not Found":404,"Method Not Allowed":405,"Not Acceptable":406,"Proxy Authentication Required":407,"Request Timeout":408,Conflict:409,Gone:410,"Length Required":411,"Precondition Failed":412,"Payload Too Large":413,"URI Too Long":414,"Unsupported Media Type":415,"Range Not Satisfiable":416,"Expectation Failed":417,"I'm a teapot":418,"Misdirected Request":421,"Unprocessable Entity":422,Locked:423,"Failed Dependency":424,"Too Early":425,"Upgrade Required":426,"Precondition Required":428,"Too Many Requests":429,"Request Header Fields Too Large":431,"Unavailable For Legal Reasons":451,"Internal Server Error":500,"Not Implemented":501,"Bad Gateway":502,"Service Unavailable":503,"Gateway Timeout":504,"HTTP Version Not Supported":505,"Variant Also Negotiates":506,"Insufficient Storage":507,"Loop Detected":508,"Not Extended":510,"Network Authentication Required":511};var H=(t,e)=>{let n=(0,f.createRouter)();for(let r of t)if(Array.isArray(r.options?.method))for(let s of r.options.method)(0,f.addRoute)(n,s,r.path,r);else(0,f.addRoute)(n,r.options.method,r.path,r);return{handler:async r=>{let s=new URL(r.url),o=s.pathname;e?.basePath&&(o=o.split(e.basePath)[1]);let c=r.method,d=(0,f.findRoute)(n,c,o),i=d?.data,l=await x(r),g=r.headers;if(!i)return new Response(null,{status:404,statusText:"Not Found"});try{let p={};i.middleware&&(p=await i.middleware({path:o,method:c,headers:g,params:s.searchParams,request:r.clone(),body:l})||{});let y=await i({path:o,method:c,headers:g,params:d?.params,request:r,body:l,...p?.context});if(y instanceof Response)return y;let w=R(y)?JSON.stringify(y):y;return new Response(w,{headers:i.headers})}catch(p){if(e?.onError){let y=await e.onError(p);if(y instanceof Response)return y}if(p instanceof u)return new Response(p.body?JSON.stringify(p.body):null,{status:P[p.status],statusText:p.status,headers:{"Content-Type":"application/json"}});if(e?.throwError)throw p;return new Response(null,{status:500,statusText:"Internal Server Error"})}}}};var B=t=>(e,n,a)=>{let r=async(...s)=>await a(s[0]||{});return r.path=e,r.options=n,r.middleware=t,r};0&&(module.exports={APIError,createEndpoint,createMiddleware,createRouter,getBody,shouldSerialize,statusCode});
1
+ "use strict";var m=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var U=(t,e,n)=>e in t?m(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var L=(t,e)=>{for(var n in e)m(t,n,{get:e[n],enumerable:!0})},M=(t,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let d of S(e))!b.call(t,d)&&d!==n&&m(t,d,{get:()=>e[d],enumerable:!(a=g(e,d))||a.enumerable});return t};var H=t=>M(m({},"__esModule",{value:!0}),t);var O=(t,e,n)=>U(t,typeof e!="symbol"?e+"":e,n);var F={};L(F,{APIError:()=>c,createEndpoint:()=>l,createMiddleware:()=>C,createRouter:()=>B,getBody:()=>y,shouldSerialize:()=>h,statusCode:()=>T});module.exports=H(F);var D=require("zod");var c=class extends Error{constructor(n,a){super(`API Error: ${n} ${a?.message??""}`,{cause:a});O(this,"status");O(this,"body");this.status=n,this.body=a??{},this.stack="",this.name="BetterCallAPIError"}};function l(t,e,n){let a=new Headers,d=async(...o)=>{let r={setHeader(s,i){a.set(s,i)},setCookie(s,i){a.append("Set-Cookie",`${s}=${i}`)},getCookie(s){return o[0]?.headers?.get("cookie")?.split(";").find(u=>u.startsWith(`${s}=`))?.split("=")[1]},...o[0]||{},context:{}};if(e.use?.length)for(let s of e.use){let i=await s(r),u=i.options?.body?i.options.body.parse(r.body):void 0;i&&(r={...r,body:u?{...u,...r.body}:r.body,context:{...r.context||{},...i}})}try{let s=e.body?e.body.parse(r.body):r.body;r={...r,body:s?{...s,...r.body}:r.body},r.query=e.query?e.query.parse(r.query):r.query,r.params=e.params?e.params.parse(r.params):r.params}catch(s){throw s instanceof D.ZodError?new c("BAD_REQUEST",{message:s.message,details:s.errors}):s}if(e.requireHeaders&&!r.headers)throw new c("BAD_REQUEST",{message:"Headers are required"});if(e.requireRequest&&!r.request)throw new c("BAD_REQUEST",{message:"Request is required"});return await n(r)};return d.path=t,d.options=e,d.method=e.method,d.headers=a,d}var E=require("rou3");async function y(t){let e=t.headers.get("content-type")||"";if(t.body){if(e.includes("application/json"))return await t.json();if(e.includes("application/x-www-form-urlencoded")){let n=await t.formData(),a={};return n.forEach((d,o)=>{a[o]=d.toString()}),a}if(e.includes("multipart/form-data")){let n=await t.formData(),a={};return n.forEach((d,o)=>{a[o]=d}),a}return e.includes("text/plain")?await t.text():e.includes("application/octet-stream")?await t.arrayBuffer():e.includes("application/pdf")||e.includes("image/")||e.includes("video/")?await t.blob():e.includes("application/stream")||t.body instanceof ReadableStream?t.body:await t.text()}}function h(t){return typeof t=="object"&&t!==null&&!(t instanceof Blob)&&!(t instanceof FormData)}var T={OK:200,CREATED:201,ACCEPTED:202,NO_CONTENT:204,MULTIPLE_CHOICES:300,MOVED_PERMANENTLY:301,FOUND:302,SEE_OTHER:303,NOT_MODIFIED:304,TEMPORARY_REDIRECT:307,BAD_REQUEST:400,UNAUTHORIZED:401,PAYMENT_REQUIRED:402,FORBIDDEN:403,NOT_FOUND:404,METHOD_NOT_ALLOWED:405,NOT_ACCEPTABLE:406,PROXY_AUTHENTICATION_REQUIRED:407,REQUEST_TIMEOUT:408,CONFLICT:409,GONE:410,LENGTH_REQUIRED:411,PRECONDITION_FAILED:412,PAYLOAD_TOO_LARGE:413,URI_TOO_LONG:414,UNSUPPORTED_MEDIA_TYPE:415,RANGE_NOT_SATISFIABLE:416,EXPECTATION_FAILED:417,"I'M_A_TEAPOT":418,MISDIRECTED_REQUEST:421,UNPROCESSABLE_ENTITY:422,LOCKED:423,FAILED_DEPENDENCY:424,TOO_EARLY:425,UPGRADE_REQUIRED:426,PRECONDITION_REQUIRED:428,TOO_MANY_REQUESTS:429,REQUEST_HEADER_FIELDS_TOO_LARGE:431,UNAVAILABLE_FOR_LEGAL_REASONS:451,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504,HTTP_VERSION_NOT_SUPPORTED:505,VARIANT_ALSO_NEGOTIATES:506,INSUFFICIENT_STORAGE:507,LOOP_DETECTED:508,NOT_EXTENDED:510,NETWORK_AUTHENTICATION_REQUIRED:511};var B=(t,e)=>{let n=(0,E.createRouter)();for(let o of t)if(Array.isArray(o.options?.method))for(let r of o.options.method)(0,E.addRoute)(n,r,o.path,o);else(0,E.addRoute)(n,o.options.method,o.path,o);let a=(0,E.createRouter)();for(let o of e?.routerMiddleware||[]){let r=Array.isArray(o.method)?o.method:[o.method];for(let R of r)(0,E.addRoute)(a,R,o.path,o.handler)}return{handler:async o=>{let r=new URL(o.url),R=r.pathname;e?.basePath&&(R=R.split(e.basePath)[1]);let s=o.method,i=(0,E.findRoute)(n,s,R),u=i?.data,_=await y(o),I=o.headers,w=Object.fromEntries(r.searchParams),N=(0,E.findRoute)(a,s,R)?.data;if(!u)return new Response(null,{status:404,statusText:"Not Found"});try{let p={};if(N){let x=await N({path:R,method:s,headers:I,params:i?.params,request:o,body:_,query:w});x&&(p={...x,...p})}let f=await u({path:R,method:s,headers:I,params:i?.params,request:o,body:_,query:w,...p});if(f instanceof Response)return f;let P=h(f)?JSON.stringify(f):f;return new Response(P,{headers:u.headers})}catch(p){if(e?.onError){let f=await e.onError(p);if(f instanceof Response)return f}if(p instanceof c)return new Response(p.body?JSON.stringify(p.body):null,{status:T[p.status],statusText:p.status,headers:{"Content-Type":"application/json"}});if(e?.throwError)throw p;return new Response(null,{status:500,statusText:"Internal Server Error"})}}}};var A=require("zod");function C(t,e){if(typeof t=="function")return l("*",{method:"*"},t);if(!e)throw new Error("Middleware handler is required");return l("*",{...t,method:"*"},e)}var z=C({body:A.z.object({name:A.z.string()})},async t=>{});0&&(module.exports={APIError,createEndpoint,createMiddleware,createRouter,getBody,shouldSerialize,statusCode});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/endpoint.ts","../src/better-call-error.ts","../src/router.ts","../src/utils.ts","../src/middleware.ts"],"sourcesContent":["export * from \"./endpoint\"\nexport * from \"./router\"\nexport * from \"./middleware\"\nexport * from \"./better-call-error\"\nexport * from \"./utils\"","import { z, ZodError, type ZodOptional, type ZodSchema } from \"zod\"\nimport type { Middleware } from \"./middleware\"\nimport { APIError } from \"./better-call-error\";\n\n\nexport type RequiredKeysOf<BaseType extends object> = Exclude<{\n [Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]>\n ? Key\n : never\n}[keyof BaseType], undefined>;\n\nexport type HasRequiredKeys<BaseType extends object> = RequiredKeysOf<BaseType> extends never ? false : true;\n\ntype Method = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"*\"\n\nexport interface EndpointOptions {\n method: Method | Method[]\n body?: ZodSchema\n query?: ZodSchema\n params?: ZodSchema<any>\n /**\n * If true headers will be required to be passed in the context\n */\n requireHeaders?: boolean\n /**\n * If true request object will be required\n */\n requireRequest?: boolean\n}\n\ntype InferParamPath<Path> =\n Path extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof InferParamPath<Rest>]: string }\n : Path extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Path extends `${infer _Start}/${infer Rest}`\n ? InferParamPath<Rest>\n : undefined;\n\ntype InferParamWildCard<Path> = Path extends `${infer _Start}/*:${infer Param}/${infer Rest}` | `${infer _Start}/**:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof InferParamPath<Rest>]: string }\n : Path extends `${infer _Start}/*`\n ? { [K in \"_\"]: string }\n : Path extends `${infer _Start}/${infer Rest}`\n ? InferParamPath<Rest>\n : undefined;\n\n\nexport type Prettify<T> = {\n [key in keyof T]: T[key];\n} & {};\n\ntype ContextTools = {\n setHeader: (key: string, value: string) => void\n setCookie: (key: string, value: string) => void\n getCookie: (key: string) => string | undefined\n}\n\nexport type Context<Path extends string, Opts extends EndpointOptions, Extra extends Record<string, any> = {}> = InferBody<Opts[\"body\"]> & InferParam<Path> & InferMethod<Opts['method']> & InferHeaders<Opts[\"requireHeaders\"]> & InferRequest<Opts[\"requireRequest\"]> & InferQuery<Opts[\"query\"]> & Extra\n\ntype InferMethod<M extends Method | Method[]> = M extends Array<Method> ? {\n method: M[number]\n} : {\n method?: M\n}\n\ntype InferHeaders<HeaderReq> = HeaderReq extends true ? {\n headers: Headers\n} : {\n headers?: Headers\n}\n\ntype InferRequest<RequestReq> = RequestReq extends true ? {\n request: Request\n} : {\n request?: Request\n}\n\n\ntype InferBody<Body> = Body extends ZodSchema ? Body extends ZodOptional<any> ? {\n body?: z.infer<Body>\n} : {\n body: z.infer<Body>\n} : {\n body?: undefined\n}\n\ntype InferQuery<Query> = Query extends ZodSchema ? Query extends ZodOptional<any> ? {\n query?: z.infer<Query>\n} : {\n query: z.infer<Query>\n} : {\n query?: undefined\n}\n\ntype InferParam<Path extends string, ParamPath extends InferParamPath<Path> = InferParamPath<Path>, WildCard extends InferParamWildCard<Path> = InferParamWildCard<Path>> = ParamPath extends undefined ? WildCard extends undefined ? {\n params?: undefined\n} : {\n params: WildCard\n} : {\n params: ParamPath & (WildCard extends undefined ? {} : WildCard)\n}\n\n\nexport type EndpointResponse = Record<string, any> | string | boolean | number | void | undefined\n\nexport type Handler<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse, Extra extends Record<string, any> = Record<string, any>> = (ctx: Context<Path, Opts, Extra>) => Promise<R>\n\nexport interface EndpointConfig {\n /**\n * Throw when the response isn't in 200 range\n */\n throwOnError?: boolean\n}\n\nexport function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, ContextTools>) {\n const responseHeader = new Headers()\n type Ctx = Context<Path, Opts>\n const handle = async (...ctx: HasRequiredKeys<Ctx> extends true ? [Ctx] : [Ctx?]) => {\n const internalCtx = ({\n setHeader(key, value) {\n responseHeader.set(key, value)\n },\n setCookie(key, value) {\n responseHeader.append(\"Set-Cookie\", `${key}=${value}`)\n },\n getCookie(key) {\n const header = ctx[0]?.headers\n return header?.get(\"cookie\")?.split(\";\").find(cookie => cookie.startsWith(`${key}=`))?.split(\"=\")[1]\n },\n ...(ctx[0] || {})\n }) as (Ctx & ContextTools)\n try {\n internalCtx.body = options.body ? options.body.parse(internalCtx.body) : undefined\n internalCtx.query = options.query ? options.query.parse(internalCtx.query) : undefined\n internalCtx.params = options.params ? options.params.parse(internalCtx.params) : undefined\n } catch (e) {\n if (e instanceof ZodError) {\n throw new APIError(\"Bad Request\", {\n message: e.message,\n details: e.errors\n })\n }\n throw e\n }\n if (options.requireHeaders && !internalCtx.headers) {\n throw new APIError(\"Bad Request\", {\n message: \"Headers are required\"\n })\n }\n if (options.requireRequest && !internalCtx.request) {\n throw new APIError(\"Bad Request\", {\n message: \"Request is required\"\n })\n }\n const res = await handler(internalCtx)\n return res as ReturnType<Handler<Path, Opts, R>>\n }\n handle.path = path\n handle.options = options\n handle.method = options.method\n handle.headers = responseHeader\n //for type inference\n handle.middleware = undefined as (Middleware<any> | undefined)\n\n return handle\n}\n\nexport type Endpoint = {\n path: string\n options: EndpointOptions\n middleware?: Middleware<any>\n headers?: Headers\n} & ((ctx: any) => Promise<any>)","import type { statusCode } from \"./utils\"\n\ntype Status = keyof typeof statusCode\n\nexport class APIError extends Error {\n status: Status\n body: Record<string, any>\n constructor(\n status: Status,\n body?: Record<string, any>,\n ) {\n super(\n `API Error: ${status} ${body?.message ?? \"\"}`,\n {\n cause: body,\n }\n )\n this.status = status\n this.body = body ?? {}\n this.stack = \"\";\n this.name = \"BetterCallAPIError\"\n }\n}","import type { Endpoint } from \"./endpoint\";\nimport {\n createRouter as createRou3Router,\n addRoute,\n findRoute,\n} from \"rou3\";\nimport { getBody, shouldSerialize, statusCode } from \"./utils\";\nimport { APIError } from \"./better-call-error\";\n\ninterface RouterConfig {\n /**\n * Throw error if error occurred other than APIError\n */\n throwError?: boolean\n /**\n * Handle error\n */\n onError?: (e: unknown) => void | Promise<void> | Response | Promise<Response>\n /**\n * Base path for the router\n */\n basePath?: string\n}\n\nexport const createRouter = <E extends Endpoint, Config extends RouterConfig>(endpoints: E[], config?: Config) => {\n const router = createRou3Router()\n for (const endpoint of endpoints) {\n if (Array.isArray(endpoint.options?.method)) {\n for (const method of endpoint.options.method) {\n addRoute(router, method, endpoint.path, endpoint)\n }\n } else {\n addRoute(router, endpoint.options.method, endpoint.path, endpoint)\n }\n }\n\n const handler = async (request: Request) => {\n const url = new URL(request.url);\n let path = url.pathname\n if (config?.basePath) {\n path = path.split(config.basePath)[1]\n }\n const method = request.method;\n const route = findRoute(router, method, path)\n const handler = route?.data as Endpoint\n const body = await getBody(request)\n const headers = request.headers\n\n //handler 404\n if (!handler) {\n return new Response(null, {\n status: 404,\n statusText: \"Not Found\"\n })\n }\n try {\n let middleware: Record<string, any> = {}\n if (handler.middleware) {\n middleware = await handler.middleware({\n path: path,\n method: method,\n headers,\n params: url.searchParams,\n request: request.clone(),\n body: body,\n }) || {}\n }\n const handlerRes = await handler({\n path: path,\n method: method as \"GET\",\n headers,\n params: route?.params as any,\n request: request,\n body: body,\n ...middleware?.context,\n })\n if (handlerRes instanceof Response) {\n return handlerRes\n }\n const resBody = shouldSerialize(handlerRes) ? JSON.stringify(handlerRes) : handlerRes\n return new Response(resBody as any, {\n headers: handler.headers\n })\n } catch (e) {\n if (config?.onError) {\n const onErrorRes = await config.onError(e)\n if (onErrorRes instanceof Response) {\n return onErrorRes\n }\n }\n if (e instanceof APIError) {\n return new Response(e.body ? JSON.stringify(e.body) : null, {\n status: statusCode[e.status],\n statusText: e.status,\n headers: {\n \"Content-Type\": \"application/json\",\n }\n })\n }\n if (config?.throwError) {\n throw e\n }\n return new Response(null, {\n status: 500,\n statusText: \"Internal Server Error\"\n })\n }\n }\n return { handler }\n}","export async function getBody(request: Request) {\n const contentType = request.headers.get('content-type') || '';\n\n if (!request.body) {\n return undefined\n }\n\n if (contentType.includes('application/json')) {\n return await request.json();\n }\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n const result: Record<string, string> = {};\n formData.forEach((value, key) => {\n result[key] = value.toString();\n });\n return result;\n }\n\n if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n const result: Record<string, any> = {};\n formData.forEach((value, key) => {\n result[key] = value;\n });\n return result;\n }\n\n if (contentType.includes('text/plain')) {\n return await request.text();\n }\n\n if (contentType.includes('application/octet-stream')) {\n return await request.arrayBuffer();\n }\n\n if (contentType.includes('application/pdf') || contentType.includes('image/') || contentType.includes('video/')) {\n const blob = await request.blob();\n return blob;\n }\n\n if (contentType.includes('application/stream') || request.body instanceof ReadableStream) {\n return request.body;\n }\n\n return await request.text();\n}\n\n\nexport function shouldSerialize(body: any) {\n return typeof body === \"object\" && body !== null && !(body instanceof Blob) && !(body instanceof FormData)\n}\n\nexport const statusCode = {\n \"OK\": 200,\n \"Created\": 201,\n \"Accepted\": 202,\n \"No Content\": 204,\n \"Multiple Choices\": 300,\n \"Moved Permanently\": 301,\n \"Found\": 302,\n \"See Other\": 303,\n \"Not Modified\": 304,\n \"Temporary Redirect\": 307,\n \"Bad Request\": 400,\n \"Unauthorized\": 401,\n \"Payment Required\": 402,\n \"Forbidden\": 403,\n \"Not Found\": 404,\n \"Method Not Allowed\": 405,\n \"Not Acceptable\": 406,\n \"Proxy Authentication Required\": 407,\n \"Request Timeout\": 408,\n \"Conflict\": 409,\n \"Gone\": 410,\n \"Length Required\": 411,\n \"Precondition Failed\": 412,\n \"Payload Too Large\": 413,\n \"URI Too Long\": 414,\n \"Unsupported Media Type\": 415,\n \"Range Not Satisfiable\": 416,\n \"Expectation Failed\": 417,\n \"I'm a teapot\": 418,\n \"Misdirected Request\": 421,\n \"Unprocessable Entity\": 422,\n \"Locked\": 423,\n \"Failed Dependency\": 424,\n \"Too Early\": 425,\n \"Upgrade Required\": 426,\n \"Precondition Required\": 428,\n \"Too Many Requests\": 429,\n \"Request Header Fields Too Large\": 431,\n \"Unavailable For Legal Reasons\": 451,\n \"Internal Server Error\": 500,\n \"Not Implemented\": 501,\n \"Bad Gateway\": 502,\n \"Service Unavailable\": 503,\n \"Gateway Timeout\": 504,\n \"HTTP Version Not Supported\": 505,\n \"Variant Also Negotiates\": 506,\n \"Insufficient Storage\": 507,\n \"Loop Detected\": 508,\n \"Not Extended\": 510,\n \"Network Authentication Required\": 511,\n}","import type { HasRequiredKeys } from \"type-fest\"\nimport { type Context, type EndpointOptions, type EndpointResponse, type Handler } from \"./endpoint\"\n\n\nexport type Middleware<E extends Record<string, string>> = (ctx: Context<string, EndpointOptions, E>) => Promise<{\n context: E\n} | void>\n\nexport const createMiddleware = <E extends Record<string, any>, M extends Middleware<E>>(middleware: M) => {\n type MiddlewareContext = Awaited<ReturnType<M>> extends {\n context: infer C\n } ? C extends Record<string, any> ? C : {} : {}\n return <Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, MiddlewareContext>) => {\n type Ctx = Context<Path, Opts, MiddlewareContext>\n const handle = async (...ctx: HasRequiredKeys<Ctx> extends true ? [Ctx] : [Ctx?]) => {\n const res = await handler((ctx[0] || {}) as Ctx)\n return res as ReturnType<Handler<Path, Opts, R>>\n }\n handle.path = path\n handle.options = options\n handle.middleware = middleware\n return handle\n }\n}"],"mappings":"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","names":["src_exports","__export","APIError","createEndpoint","createMiddleware","createRouter","getBody","shouldSerialize","statusCode","__toCommonJS","import_zod","APIError","status","body","__publicField","createEndpoint","path","options","handler","responseHeader","handle","ctx","internalCtx","key","value","cookie","e","APIError","import_rou3","getBody","request","contentType","formData","result","value","key","shouldSerialize","body","statusCode","createRouter","endpoints","config","router","createRou3Router","endpoint","method","request","url","path","route","handler","body","getBody","headers","middleware","handlerRes","resBody","shouldSerialize","e","onErrorRes","APIError","statusCode","createMiddleware","middleware","path","options","handler","handle","ctx"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/endpoint.ts","../src/better-call-error.ts","../src/router.ts","../src/utils.ts","../src/middleware.ts"],"sourcesContent":["export * from \"./endpoint\"\nexport * from \"./router\"\nexport * from \"./middleware\"\nexport * from \"./better-call-error\"\nexport * from \"./utils\"","import { z, ZodError, type ZodOptional, type ZodSchema } from \"zod\"\nimport type { Middleware } from \"./middleware\"\nimport { APIError } from \"./better-call-error\";\nimport type { HasRequiredKeys, UnionToIntersection } from \"./helper\";\nimport type { Context, ContextTools, Endpoint, EndpointOptions, EndpointResponse, Handler } from \"./types\";\n\n\nexport interface EndpointConfig {\n /**\n * Throw when the response isn't in 200 range\n */\n throwOnError?: boolean\n}\n\nexport function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R>) {\n const responseHeader = new Headers()\n type Ctx = Context<Path, Opts>\n const handle = async (...ctx: HasRequiredKeys<Ctx> extends true ? [Ctx] : [Ctx?]) => {\n let internalCtx = ({\n setHeader(key: string, value: string) {\n responseHeader.set(key, value)\n },\n setCookie(key: string, value: string) {\n responseHeader.append(\"Set-Cookie\", `${key}=${value}`)\n },\n getCookie(key: string) {\n const header = ctx[0]?.headers\n return header?.get(\"cookie\")?.split(\";\").find(cookie => cookie.startsWith(`${key}=`))?.split(\"=\")[1]\n },\n ...(ctx[0] || {}),\n context: {}\n })\n if (options.use?.length) {\n for (const middleware of options.use) {\n const res = await middleware(internalCtx) as Endpoint\n const body = res.options?.body ? res.options.body.parse(internalCtx.body) : undefined\n if (res) {\n internalCtx = {\n ...internalCtx,\n body: body ? {\n ...body,\n ...internalCtx.body\n } : internalCtx.body,\n context: {\n ...internalCtx.context || {},\n ...res\n }\n }\n }\n }\n }\n try {\n const body = options.body ? options.body.parse(internalCtx.body) : internalCtx.body\n internalCtx = {\n ...internalCtx,\n body: body ? {\n ...body,\n ...internalCtx.body\n } : internalCtx.body,\n }\n internalCtx.query = options.query ? options.query.parse(internalCtx.query) : internalCtx.query\n internalCtx.params = options.params ? options.params.parse(internalCtx.params) : internalCtx.params\n } catch (e) {\n if (e instanceof ZodError) {\n throw new APIError(\"BAD_REQUEST\", {\n message: e.message,\n details: e.errors\n })\n }\n throw e\n }\n if (options.requireHeaders && !internalCtx.headers) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Headers are required\"\n })\n }\n if (options.requireRequest && !internalCtx.request) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Request is required\"\n })\n }\n //@ts-expect-error\n const res = await handler(internalCtx)\n return res as ReturnType<Handler<Path, Opts, R>>\n }\n handle.path = path\n handle.options = options\n handle.method = options.method\n handle.headers = responseHeader\n return handle\n}","import type { statusCode } from \"./utils\"\n\ntype Status = keyof typeof statusCode\n\nexport class APIError extends Error {\n status: Status\n body: Record<string, any>\n constructor(\n status: Status,\n body?: Record<string, any>,\n ) {\n super(\n `API Error: ${status} ${body?.message ?? \"\"}`,\n {\n cause: body,\n }\n )\n this.status = status\n this.body = body ?? {}\n this.stack = \"\";\n this.name = \"BetterCallAPIError\"\n }\n}","import {\n createRouter as createRou3Router,\n addRoute,\n findRoute,\n} from \"rou3\";\nimport { getBody, shouldSerialize, statusCode } from \"./utils\";\nimport { APIError } from \"./better-call-error\";\nimport type { Middleware, MiddlewareHandler } from \"./middleware\";\nimport type { Endpoint, Method } from \"./types\";\n\ninterface RouterConfig {\n /**\n * Throw error if error occurred other than APIError\n */\n throwError?: boolean\n /**\n * Handle error\n */\n onError?: (e: unknown) => void | Promise<void> | Response | Promise<Response>\n /**\n * Base path for the router\n */\n basePath?: string\n /**\n * Middlewares for the router\n */\n routerMiddleware?: ({\n /**\n * The path to match for the middleware to be called\n */\n path: string,\n /**\n * The method to match for the middleware to be called\n * \n * @default \"*\"\n */\n method?: Method | Method[],\n /**\n * The middleware handler\n */\n handler: Endpoint\n })[]\n}\n\nexport const createRouter = <E extends Endpoint, Config extends RouterConfig>(endpoints: E[], config?: Config) => {\n const router = createRou3Router()\n for (const endpoint of endpoints) {\n if (Array.isArray(endpoint.options?.method)) {\n for (const method of endpoint.options.method) {\n addRoute(router, method, endpoint.path, endpoint)\n }\n } else {\n addRoute(router, endpoint.options.method, endpoint.path, endpoint)\n }\n }\n\n const middlewareRouter = createRou3Router()\n for (const route of (config?.routerMiddleware || [])) {\n const methods = Array.isArray(route.method) ? route.method : [route.method]\n for (const method of methods) {\n addRoute(middlewareRouter, method, route.path, route.handler)\n }\n }\n\n const handler = async (request: Request) => {\n const url = new URL(request.url);\n let path = url.pathname\n if (config?.basePath) {\n path = path.split(config.basePath)[1]\n }\n const method = request.method;\n const route = findRoute(router, method, path)\n const handler = route?.data as Endpoint\n const body = await getBody(request)\n const headers = request.headers\n const query = Object.fromEntries(url.searchParams)\n const middleware = findRoute(middlewareRouter, method, path)?.data as Endpoint | undefined\n\n //handler 404\n if (!handler) {\n return new Response(null, {\n status: 404,\n statusText: \"Not Found\"\n })\n }\n try {\n let middlewareContext: Record<string, any> = {}\n if (middleware) {\n const res = await middleware({\n path: path,\n method: method as \"GET\",\n headers,\n params: route?.params as any,\n request: request,\n body: body,\n query\n })\n if (res) {\n middlewareContext = {\n ...res,\n ...middlewareContext\n }\n }\n }\n const handlerRes = await handler({\n path: path,\n method: method as \"GET\",\n headers,\n params: route?.params as any,\n request: request,\n body: body,\n query,\n ...middlewareContext,\n })\n if (handlerRes instanceof Response) {\n return handlerRes\n }\n const resBody = shouldSerialize(handlerRes) ? JSON.stringify(handlerRes) : handlerRes\n return new Response(resBody as any, {\n headers: handler.headers\n })\n } catch (e) {\n if (config?.onError) {\n const onErrorRes = await config.onError(e)\n if (onErrorRes instanceof Response) {\n return onErrorRes\n }\n }\n if (e instanceof APIError) {\n return new Response(e.body ? JSON.stringify(e.body) : null, {\n status: statusCode[e.status],\n statusText: e.status,\n headers: {\n \"Content-Type\": \"application/json\",\n }\n })\n }\n if (config?.throwError) {\n throw e\n }\n return new Response(null, {\n status: 500,\n statusText: \"Internal Server Error\"\n })\n }\n }\n return {\n handler\n }\n}","export async function getBody(request: Request) {\n const contentType = request.headers.get('content-type') || '';\n\n if (!request.body) {\n return undefined\n }\n\n if (contentType.includes('application/json')) {\n return await request.json();\n }\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n const result: Record<string, string> = {};\n formData.forEach((value, key) => {\n result[key] = value.toString();\n });\n return result;\n }\n\n if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n const result: Record<string, any> = {};\n formData.forEach((value, key) => {\n result[key] = value;\n });\n return result;\n }\n\n if (contentType.includes('text/plain')) {\n return await request.text();\n }\n\n if (contentType.includes('application/octet-stream')) {\n return await request.arrayBuffer();\n }\n\n if (contentType.includes('application/pdf') || contentType.includes('image/') || contentType.includes('video/')) {\n const blob = await request.blob();\n return blob;\n }\n\n if (contentType.includes('application/stream') || request.body instanceof ReadableStream) {\n return request.body;\n }\n\n return await request.text();\n}\n\n\nexport function shouldSerialize(body: any) {\n return typeof body === \"object\" && body !== null && !(body instanceof Blob) && !(body instanceof FormData)\n}\n\nexport const statusCode = {\n \"OK\": 200,\n \"CREATED\": 201,\n \"ACCEPTED\": 202,\n \"NO_CONTENT\": 204,\n \"MULTIPLE_CHOICES\": 300,\n \"MOVED_PERMANENTLY\": 301,\n \"FOUND\": 302,\n \"SEE_OTHER\": 303,\n \"NOT_MODIFIED\": 304,\n \"TEMPORARY_REDIRECT\": 307,\n \"BAD_REQUEST\": 400,\n \"UNAUTHORIZED\": 401,\n \"PAYMENT_REQUIRED\": 402,\n \"FORBIDDEN\": 403,\n \"NOT_FOUND\": 404,\n \"METHOD_NOT_ALLOWED\": 405,\n \"NOT_ACCEPTABLE\": 406,\n \"PROXY_AUTHENTICATION_REQUIRED\": 407,\n \"REQUEST_TIMEOUT\": 408,\n \"CONFLICT\": 409,\n \"GONE\": 410,\n \"LENGTH_REQUIRED\": 411,\n \"PRECONDITION_FAILED\": 412,\n \"PAYLOAD_TOO_LARGE\": 413,\n \"URI_TOO_LONG\": 414,\n \"UNSUPPORTED_MEDIA_TYPE\": 415,\n \"RANGE_NOT_SATISFIABLE\": 416,\n \"EXPECTATION_FAILED\": 417,\n \"I'M_A_TEAPOT\": 418,\n \"MISDIRECTED_REQUEST\": 421,\n \"UNPROCESSABLE_ENTITY\": 422,\n \"LOCKED\": 423,\n \"FAILED_DEPENDENCY\": 424,\n \"TOO_EARLY\": 425,\n \"UPGRADE_REQUIRED\": 426,\n \"PRECONDITION_REQUIRED\": 428,\n \"TOO_MANY_REQUESTS\": 429,\n \"REQUEST_HEADER_FIELDS_TOO_LARGE\": 431,\n \"UNAVAILABLE_FOR_LEGAL_REASONS\": 451,\n \"INTERNAL_SERVER_ERROR\": 500,\n \"NOT_IMPLEMENTED\": 501,\n \"BAD_GATEWAY\": 502,\n \"SERVICE_UNAVAILABLE\": 503,\n \"GATEWAY_TIMEOUT\": 504,\n \"HTTP_VERSION_NOT_SUPPORTED\": 505,\n \"VARIANT_ALSO_NEGOTIATES\": 506,\n \"INSUFFICIENT_STORAGE\": 507,\n \"LOOP_DETECTED\": 508,\n \"NOT_EXTENDED\": 510,\n \"NETWORK_AUTHENTICATION_REQUIRED\": 511,\n}\n","import { z } from \"zod\"\nimport type { ContextTools, Endpoint, EndpointOptions, EndpointResponse, Handler, InferBody, InferHeaders, InferRequest, Prettify } from \"./types\"\nimport { createEndpoint } from \"./endpoint\"\n\nexport type MiddlewareHandler<Opts extends EndpointOptions, R extends EndpointResponse> = (ctx: Prettify<InferBody<Opts> & InferRequest<Opts> & InferHeaders<Opts> & {\n params?: Record<string, string>,\n query?: Record<string, string>,\n} & ContextTools>) => Promise<R>\n\nexport function createMiddleware<Opts extends EndpointOptions, R extends EndpointResponse>(optionsOrHandler: MiddlewareHandler<Opts, R>): Endpoint<Handler<string, Opts, R>, Opts>\nexport function createMiddleware<Opts extends Omit<EndpointOptions, \"method\">, R extends EndpointResponse>(optionsOrHandler: Opts, handler: MiddlewareHandler<Opts & {\n method: \"*\"\n}, R>): Endpoint<Handler<string, Opts & {\n method: \"*\"\n}, R>, Opts & {\n method: \"*\"\n}>\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n if (typeof optionsOrHandler === \"function\") {\n return createEndpoint(\"*\", {\n method: \"*\"\n }, optionsOrHandler)\n }\n if (!handler) {\n throw new Error(\"Middleware handler is required\")\n }\n const endpoint = createEndpoint(\"*\", {\n ...optionsOrHandler,\n method: \"*\"\n }, handler)\n return endpoint as any\n}\n\nexport type Middleware<Opts extends EndpointOptions, R> = (opts: Opts, handler: (ctx: {\n body?: InferBody<Opts>,\n params?: Record<string, string>,\n query?: Record<string, string>\n}) => Promise<R>) => Endpoint\n\nconst m1 = createMiddleware({\n body: z.object({\n name: z.string()\n }),\n}, async (ctx) => {\n ctx\n})"],"mappings":"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","names":["src_exports","__export","APIError","createEndpoint","createMiddleware","createRouter","getBody","shouldSerialize","statusCode","__toCommonJS","import_zod","APIError","status","body","__publicField","createEndpoint","path","options","handler","responseHeader","handle","ctx","internalCtx","key","value","cookie","middleware","res","body","e","APIError","import_rou3","getBody","request","contentType","formData","result","value","key","shouldSerialize","body","statusCode","createRouter","endpoints","config","router","createRou3Router","endpoint","method","middlewareRouter","route","methods","request","url","path","handler","body","getBody","headers","query","middleware","middlewareContext","res","handlerRes","resBody","shouldSerialize","e","onErrorRes","APIError","statusCode","import_zod","createMiddleware","optionsOrHandler","handler","createEndpoint","m1","ctx"]}
package/dist/index.d.cts CHANGED
@@ -1,29 +1,11 @@
1
1
  import { ZodSchema, ZodOptional, z } from 'zod';
2
- import { HasRequiredKeys as HasRequiredKeys$1 } from 'type-fest';
3
-
4
- type Middleware<E extends Record<string, string>> = (ctx: Context<string, EndpointOptions, E>) => Promise<{
5
- context: E;
6
- } | void>;
7
- declare const createMiddleware: <E extends Record<string, any>, M extends Middleware<E>>(middleware: M) => <Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, Awaited<ReturnType<M>> extends {
8
- context: infer C;
9
- } ? C extends Record<string, any> ? C : {} : {}>) => {
10
- (...ctx: HasRequiredKeys$1<Context<Path, Opts, Awaited<ReturnType<M>> extends {
11
- context: infer C;
12
- } ? C extends Record<string, any> ? C : {} : {}>> extends true ? [Context<Path, Opts, Awaited<ReturnType<M>> extends {
13
- context: infer C;
14
- } ? C extends Record<string, any> ? C : {} : {}>] : [Context<Path, Opts, Awaited<ReturnType<M>> extends {
15
- context: infer C;
16
- } ? C extends Record<string, any> ? C : {} : {}>?]): Promise<R>;
17
- path: Path;
18
- options: Opts;
19
- middleware: M;
20
- };
21
2
 
3
+ type UnionToIntersection<Union> = (Union extends unknown ? (distributedUnion: Union) => void : never) extends ((mergedIntersection: infer Intersection) => void) ? Intersection & Union : never;
22
4
  type RequiredKeysOf<BaseType extends object> = Exclude<{
23
5
  [Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]> ? Key : never;
24
6
  }[keyof BaseType], undefined>;
25
7
  type HasRequiredKeys<BaseType extends object> = RequiredKeysOf<BaseType> extends never ? false : true;
26
- type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "*";
8
+
27
9
  interface EndpointOptions {
28
10
  method: Method | Method[];
29
11
  body?: ZodSchema;
@@ -37,7 +19,16 @@ interface EndpointOptions {
37
19
  * If true request object will be required
38
20
  */
39
21
  requireRequest?: boolean;
22
+ /**
23
+ * List of endpoints that will be called before this endpoint
24
+ */
25
+ use?: Endpoint[];
40
26
  }
27
+ type Endpoint<Handler extends (ctx: any) => Promise<any> = (ctx: any) => Promise<any>, Option extends EndpointOptions = EndpointOptions> = {
28
+ path: string;
29
+ options: Option;
30
+ headers?: Headers;
31
+ } & (Handler);
41
32
  type InferParamPath<Path> = Path extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
42
33
  [K in Param | keyof InferParamPath<Rest>]: string;
43
34
  } : Path extends `${infer _Start}:${infer Param}` ? {
@@ -52,33 +43,57 @@ type Prettify<T> = {
52
43
  [key in keyof T]: T[key];
53
44
  } & {};
54
45
  type ContextTools = {
46
+ /**
47
+ * Set header
48
+ *
49
+ * If it's called outside of a request it will just be ignored.
50
+ */
55
51
  setHeader: (key: string, value: string) => void;
52
+ /**
53
+ * cookie setter.
54
+ *
55
+ * If it's called outside of a request it will just be ignored.
56
+ */
56
57
  setCookie: (key: string, value: string) => void;
58
+ /**
59
+ * Get cookie value
60
+ *
61
+ * If it's called outside of a request it will just be ignored.
62
+ */
57
63
  getCookie: (key: string) => string | undefined;
58
64
  };
59
- type Context<Path extends string, Opts extends EndpointOptions, Extra extends Record<string, any> = {}> = InferBody<Opts["body"]> & InferParam<Path> & InferMethod<Opts['method']> & InferHeaders<Opts["requireHeaders"]> & InferRequest<Opts["requireRequest"]> & InferQuery<Opts["query"]> & Extra;
65
+ type Context<Path extends string, Opts extends EndpointOptions> = InferBody<Opts> & InferParam<Path> & InferMethod<Opts['method']> & InferHeaders<Opts> & InferRequest<Opts> & InferQuery<Opts["query"]>;
66
+ type InferUse<Opts extends EndpointOptions> = Opts["use"] extends Endpoint[] ? {
67
+ context: UnionToIntersection<Awaited<ReturnType<Opts["use"][number]>>>;
68
+ } : {};
69
+ type InferUseOptions<Opts extends EndpointOptions> = Opts["use"] extends Array<infer U> ? UnionToIntersection<U extends Endpoint ? U['options'] : {
70
+ body?: {};
71
+ requireRequest?: boolean;
72
+ requireHeaders?: boolean;
73
+ }> : {
74
+ body?: {};
75
+ requireRequest?: boolean;
76
+ requireHeaders?: boolean;
77
+ };
60
78
  type InferMethod<M extends Method | Method[]> = M extends Array<Method> ? {
61
79
  method: M[number];
62
80
  } : {
63
81
  method?: M;
64
82
  };
65
- type InferHeaders<HeaderReq> = HeaderReq extends true ? {
83
+ type InferHeaders<Opt extends EndpointOptions, HeaderReq = Opt["requireHeaders"]> = HeaderReq extends true ? {
84
+ headers: Headers;
85
+ } : InferUseOptions<Opt>['requireHeaders'] extends true ? {
66
86
  headers: Headers;
67
87
  } : {
68
88
  headers?: Headers;
69
89
  };
70
- type InferRequest<RequestReq> = RequestReq extends true ? {
90
+ type InferRequest<Opt extends EndpointOptions, RequestReq = Opt["requireRequest"]> = RequestReq extends true ? {
91
+ request: Request;
92
+ } : InferUseOptions<Opt>['requireRequest'] extends true ? {
71
93
  request: Request;
72
94
  } : {
73
95
  request?: Request;
74
96
  };
75
- type InferBody<Body> = Body extends ZodSchema ? Body extends ZodOptional<any> ? {
76
- body?: z.infer<Body>;
77
- } : {
78
- body: z.infer<Body>;
79
- } : {
80
- body?: undefined;
81
- };
82
97
  type InferQuery<Query> = Query extends ZodSchema ? Query extends ZodOptional<any> ? {
83
98
  query?: z.infer<Query>;
84
99
  } : {
@@ -87,34 +102,36 @@ type InferQuery<Query> = Query extends ZodSchema ? Query extends ZodOptional<any
87
102
  query?: undefined;
88
103
  };
89
104
  type InferParam<Path extends string, ParamPath extends InferParamPath<Path> = InferParamPath<Path>, WildCard extends InferParamWildCard<Path> = InferParamWildCard<Path>> = ParamPath extends undefined ? WildCard extends undefined ? {
90
- params?: undefined;
105
+ params?: Record<string, string>;
91
106
  } : {
92
107
  params: WildCard;
93
108
  } : {
94
- params: ParamPath & (WildCard extends undefined ? {} : WildCard);
109
+ params: Prettify<ParamPath & (WildCard extends undefined ? {} : WildCard)>;
95
110
  };
96
111
  type EndpointResponse = Record<string, any> | string | boolean | number | void | undefined;
97
- type Handler<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse, Extra extends Record<string, any> = Record<string, any>> = (ctx: Context<Path, Opts, Extra>) => Promise<R>;
112
+ type Handler<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse> = (ctx: Prettify<Context<Path, Opts> & InferUse<Opts> & ContextTools>) => Promise<R>;
113
+ type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "*";
114
+ type InferBody<Opts extends EndpointOptions, Body extends ZodSchema | undefined = (Opts["body"] & (undefined extends InferUseOptions<Opts>['body'] ? {} : InferUseOptions<Opts>['body']))> = Body extends ZodSchema ? Body extends ZodOptional<any> ? {
115
+ body?: Prettify<z.infer<Body>>;
116
+ } : {
117
+ body: Prettify<z.infer<Body>>;
118
+ } : {
119
+ body?: undefined;
120
+ };
121
+
98
122
  interface EndpointConfig {
99
123
  /**
100
124
  * Throw when the response isn't in 200 range
101
125
  */
102
126
  throwOnError?: boolean;
103
127
  }
104
- declare function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, ContextTools>): {
105
- (...ctx: HasRequiredKeys<Context<Path, Opts, {}>> extends true ? [Context<Path, Opts, {}>] : [Context<Path, Opts, {}>?]): Promise<R>;
128
+ declare function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R>): {
129
+ (...ctx: HasRequiredKeys<Context<Path, Opts>> extends true ? [Context<Path, Opts>] : [Context<Path, Opts>?]): Promise<R>;
106
130
  path: Path;
107
131
  options: Opts;
108
132
  method: Method | Method[];
109
133
  headers: Headers;
110
- middleware: Middleware<any> | undefined;
111
134
  };
112
- type Endpoint = {
113
- path: string;
114
- options: EndpointOptions;
115
- middleware?: Middleware<any>;
116
- headers?: Headers;
117
- } & ((ctx: any) => Promise<any>);
118
135
 
119
136
  interface RouterConfig {
120
137
  /**
@@ -129,64 +146,101 @@ interface RouterConfig {
129
146
  * Base path for the router
130
147
  */
131
148
  basePath?: string;
149
+ /**
150
+ * Middlewares for the router
151
+ */
152
+ routerMiddleware?: ({
153
+ /**
154
+ * The path to match for the middleware to be called
155
+ */
156
+ path: string;
157
+ /**
158
+ * The method to match for the middleware to be called
159
+ *
160
+ * @default "*"
161
+ */
162
+ method?: Method | Method[];
163
+ /**
164
+ * The middleware handler
165
+ */
166
+ handler: Endpoint;
167
+ })[];
132
168
  }
133
169
  declare const createRouter: <E extends Endpoint, Config extends RouterConfig>(endpoints: E[], config?: Config) => {
134
170
  handler: (request: Request) => Promise<Response>;
135
171
  };
136
172
 
173
+ type MiddlewareHandler<Opts extends EndpointOptions, R extends EndpointResponse> = (ctx: Prettify<InferBody<Opts> & InferRequest<Opts> & InferHeaders<Opts> & {
174
+ params?: Record<string, string>;
175
+ query?: Record<string, string>;
176
+ } & ContextTools>) => Promise<R>;
177
+ declare function createMiddleware<Opts extends EndpointOptions, R extends EndpointResponse>(optionsOrHandler: MiddlewareHandler<Opts, R>): Endpoint<Handler<string, Opts, R>, Opts>;
178
+ declare function createMiddleware<Opts extends Omit<EndpointOptions, "method">, R extends EndpointResponse>(optionsOrHandler: Opts, handler: MiddlewareHandler<Opts & {
179
+ method: "*";
180
+ }, R>): Endpoint<Handler<string, Opts & {
181
+ method: "*";
182
+ }, R>, Opts & {
183
+ method: "*";
184
+ }>;
185
+ type Middleware<Opts extends EndpointOptions, R> = (opts: Opts, handler: (ctx: {
186
+ body?: InferBody<Opts>;
187
+ params?: Record<string, string>;
188
+ query?: Record<string, string>;
189
+ }) => Promise<R>) => Endpoint;
190
+
137
191
  declare function getBody(request: Request): Promise<any>;
138
192
  declare function shouldSerialize(body: any): boolean;
139
193
  declare const statusCode: {
140
194
  OK: number;
141
- Created: number;
142
- Accepted: number;
143
- "No Content": number;
144
- "Multiple Choices": number;
145
- "Moved Permanently": number;
146
- Found: number;
147
- "See Other": number;
148
- "Not Modified": number;
149
- "Temporary Redirect": number;
150
- "Bad Request": number;
151
- Unauthorized: number;
152
- "Payment Required": number;
153
- Forbidden: number;
154
- "Not Found": number;
155
- "Method Not Allowed": number;
156
- "Not Acceptable": number;
157
- "Proxy Authentication Required": number;
158
- "Request Timeout": number;
159
- Conflict: number;
160
- Gone: number;
161
- "Length Required": number;
162
- "Precondition Failed": number;
163
- "Payload Too Large": number;
164
- "URI Too Long": number;
165
- "Unsupported Media Type": number;
166
- "Range Not Satisfiable": number;
167
- "Expectation Failed": number;
168
- "I'm a teapot": number;
169
- "Misdirected Request": number;
170
- "Unprocessable Entity": number;
171
- Locked: number;
172
- "Failed Dependency": number;
173
- "Too Early": number;
174
- "Upgrade Required": number;
175
- "Precondition Required": number;
176
- "Too Many Requests": number;
177
- "Request Header Fields Too Large": number;
178
- "Unavailable For Legal Reasons": number;
179
- "Internal Server Error": number;
180
- "Not Implemented": number;
181
- "Bad Gateway": number;
182
- "Service Unavailable": number;
183
- "Gateway Timeout": number;
184
- "HTTP Version Not Supported": number;
185
- "Variant Also Negotiates": number;
186
- "Insufficient Storage": number;
187
- "Loop Detected": number;
188
- "Not Extended": number;
189
- "Network Authentication Required": number;
195
+ CREATED: number;
196
+ ACCEPTED: number;
197
+ NO_CONTENT: number;
198
+ MULTIPLE_CHOICES: number;
199
+ MOVED_PERMANENTLY: number;
200
+ FOUND: number;
201
+ SEE_OTHER: number;
202
+ NOT_MODIFIED: number;
203
+ TEMPORARY_REDIRECT: number;
204
+ BAD_REQUEST: number;
205
+ UNAUTHORIZED: number;
206
+ PAYMENT_REQUIRED: number;
207
+ FORBIDDEN: number;
208
+ NOT_FOUND: number;
209
+ METHOD_NOT_ALLOWED: number;
210
+ NOT_ACCEPTABLE: number;
211
+ PROXY_AUTHENTICATION_REQUIRED: number;
212
+ REQUEST_TIMEOUT: number;
213
+ CONFLICT: number;
214
+ GONE: number;
215
+ LENGTH_REQUIRED: number;
216
+ PRECONDITION_FAILED: number;
217
+ PAYLOAD_TOO_LARGE: number;
218
+ URI_TOO_LONG: number;
219
+ UNSUPPORTED_MEDIA_TYPE: number;
220
+ RANGE_NOT_SATISFIABLE: number;
221
+ EXPECTATION_FAILED: number;
222
+ "I'M_A_TEAPOT": number;
223
+ MISDIRECTED_REQUEST: number;
224
+ UNPROCESSABLE_ENTITY: number;
225
+ LOCKED: number;
226
+ FAILED_DEPENDENCY: number;
227
+ TOO_EARLY: number;
228
+ UPGRADE_REQUIRED: number;
229
+ PRECONDITION_REQUIRED: number;
230
+ TOO_MANY_REQUESTS: number;
231
+ REQUEST_HEADER_FIELDS_TOO_LARGE: number;
232
+ UNAVAILABLE_FOR_LEGAL_REASONS: number;
233
+ INTERNAL_SERVER_ERROR: number;
234
+ NOT_IMPLEMENTED: number;
235
+ BAD_GATEWAY: number;
236
+ SERVICE_UNAVAILABLE: number;
237
+ GATEWAY_TIMEOUT: number;
238
+ HTTP_VERSION_NOT_SUPPORTED: number;
239
+ VARIANT_ALSO_NEGOTIATES: number;
240
+ INSUFFICIENT_STORAGE: number;
241
+ LOOP_DETECTED: number;
242
+ NOT_EXTENDED: number;
243
+ NETWORK_AUTHENTICATION_REQUIRED: number;
190
244
  };
191
245
 
192
246
  type Status = keyof typeof statusCode;
@@ -196,4 +250,4 @@ declare class APIError extends Error {
196
250
  constructor(status: Status, body?: Record<string, any>);
197
251
  }
198
252
 
199
- export { APIError, type Context, type Endpoint, type EndpointConfig, type EndpointOptions, type EndpointResponse, type Handler, type HasRequiredKeys, type Middleware, type Prettify, type RequiredKeysOf, createEndpoint, createMiddleware, createRouter, getBody, shouldSerialize, statusCode };
253
+ export { APIError, type EndpointConfig, type Middleware, type MiddlewareHandler, createEndpoint, createMiddleware, createRouter, getBody, shouldSerialize, statusCode };
package/dist/index.d.ts CHANGED
@@ -1,29 +1,11 @@
1
1
  import { ZodSchema, ZodOptional, z } from 'zod';
2
- import { HasRequiredKeys as HasRequiredKeys$1 } from 'type-fest';
3
-
4
- type Middleware<E extends Record<string, string>> = (ctx: Context<string, EndpointOptions, E>) => Promise<{
5
- context: E;
6
- } | void>;
7
- declare const createMiddleware: <E extends Record<string, any>, M extends Middleware<E>>(middleware: M) => <Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, Awaited<ReturnType<M>> extends {
8
- context: infer C;
9
- } ? C extends Record<string, any> ? C : {} : {}>) => {
10
- (...ctx: HasRequiredKeys$1<Context<Path, Opts, Awaited<ReturnType<M>> extends {
11
- context: infer C;
12
- } ? C extends Record<string, any> ? C : {} : {}>> extends true ? [Context<Path, Opts, Awaited<ReturnType<M>> extends {
13
- context: infer C;
14
- } ? C extends Record<string, any> ? C : {} : {}>] : [Context<Path, Opts, Awaited<ReturnType<M>> extends {
15
- context: infer C;
16
- } ? C extends Record<string, any> ? C : {} : {}>?]): Promise<R>;
17
- path: Path;
18
- options: Opts;
19
- middleware: M;
20
- };
21
2
 
3
+ type UnionToIntersection<Union> = (Union extends unknown ? (distributedUnion: Union) => void : never) extends ((mergedIntersection: infer Intersection) => void) ? Intersection & Union : never;
22
4
  type RequiredKeysOf<BaseType extends object> = Exclude<{
23
5
  [Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]> ? Key : never;
24
6
  }[keyof BaseType], undefined>;
25
7
  type HasRequiredKeys<BaseType extends object> = RequiredKeysOf<BaseType> extends never ? false : true;
26
- type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "*";
8
+
27
9
  interface EndpointOptions {
28
10
  method: Method | Method[];
29
11
  body?: ZodSchema;
@@ -37,7 +19,16 @@ interface EndpointOptions {
37
19
  * If true request object will be required
38
20
  */
39
21
  requireRequest?: boolean;
22
+ /**
23
+ * List of endpoints that will be called before this endpoint
24
+ */
25
+ use?: Endpoint[];
40
26
  }
27
+ type Endpoint<Handler extends (ctx: any) => Promise<any> = (ctx: any) => Promise<any>, Option extends EndpointOptions = EndpointOptions> = {
28
+ path: string;
29
+ options: Option;
30
+ headers?: Headers;
31
+ } & (Handler);
41
32
  type InferParamPath<Path> = Path extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
42
33
  [K in Param | keyof InferParamPath<Rest>]: string;
43
34
  } : Path extends `${infer _Start}:${infer Param}` ? {
@@ -52,33 +43,57 @@ type Prettify<T> = {
52
43
  [key in keyof T]: T[key];
53
44
  } & {};
54
45
  type ContextTools = {
46
+ /**
47
+ * Set header
48
+ *
49
+ * If it's called outside of a request it will just be ignored.
50
+ */
55
51
  setHeader: (key: string, value: string) => void;
52
+ /**
53
+ * cookie setter.
54
+ *
55
+ * If it's called outside of a request it will just be ignored.
56
+ */
56
57
  setCookie: (key: string, value: string) => void;
58
+ /**
59
+ * Get cookie value
60
+ *
61
+ * If it's called outside of a request it will just be ignored.
62
+ */
57
63
  getCookie: (key: string) => string | undefined;
58
64
  };
59
- type Context<Path extends string, Opts extends EndpointOptions, Extra extends Record<string, any> = {}> = InferBody<Opts["body"]> & InferParam<Path> & InferMethod<Opts['method']> & InferHeaders<Opts["requireHeaders"]> & InferRequest<Opts["requireRequest"]> & InferQuery<Opts["query"]> & Extra;
65
+ type Context<Path extends string, Opts extends EndpointOptions> = InferBody<Opts> & InferParam<Path> & InferMethod<Opts['method']> & InferHeaders<Opts> & InferRequest<Opts> & InferQuery<Opts["query"]>;
66
+ type InferUse<Opts extends EndpointOptions> = Opts["use"] extends Endpoint[] ? {
67
+ context: UnionToIntersection<Awaited<ReturnType<Opts["use"][number]>>>;
68
+ } : {};
69
+ type InferUseOptions<Opts extends EndpointOptions> = Opts["use"] extends Array<infer U> ? UnionToIntersection<U extends Endpoint ? U['options'] : {
70
+ body?: {};
71
+ requireRequest?: boolean;
72
+ requireHeaders?: boolean;
73
+ }> : {
74
+ body?: {};
75
+ requireRequest?: boolean;
76
+ requireHeaders?: boolean;
77
+ };
60
78
  type InferMethod<M extends Method | Method[]> = M extends Array<Method> ? {
61
79
  method: M[number];
62
80
  } : {
63
81
  method?: M;
64
82
  };
65
- type InferHeaders<HeaderReq> = HeaderReq extends true ? {
83
+ type InferHeaders<Opt extends EndpointOptions, HeaderReq = Opt["requireHeaders"]> = HeaderReq extends true ? {
84
+ headers: Headers;
85
+ } : InferUseOptions<Opt>['requireHeaders'] extends true ? {
66
86
  headers: Headers;
67
87
  } : {
68
88
  headers?: Headers;
69
89
  };
70
- type InferRequest<RequestReq> = RequestReq extends true ? {
90
+ type InferRequest<Opt extends EndpointOptions, RequestReq = Opt["requireRequest"]> = RequestReq extends true ? {
91
+ request: Request;
92
+ } : InferUseOptions<Opt>['requireRequest'] extends true ? {
71
93
  request: Request;
72
94
  } : {
73
95
  request?: Request;
74
96
  };
75
- type InferBody<Body> = Body extends ZodSchema ? Body extends ZodOptional<any> ? {
76
- body?: z.infer<Body>;
77
- } : {
78
- body: z.infer<Body>;
79
- } : {
80
- body?: undefined;
81
- };
82
97
  type InferQuery<Query> = Query extends ZodSchema ? Query extends ZodOptional<any> ? {
83
98
  query?: z.infer<Query>;
84
99
  } : {
@@ -87,34 +102,36 @@ type InferQuery<Query> = Query extends ZodSchema ? Query extends ZodOptional<any
87
102
  query?: undefined;
88
103
  };
89
104
  type InferParam<Path extends string, ParamPath extends InferParamPath<Path> = InferParamPath<Path>, WildCard extends InferParamWildCard<Path> = InferParamWildCard<Path>> = ParamPath extends undefined ? WildCard extends undefined ? {
90
- params?: undefined;
105
+ params?: Record<string, string>;
91
106
  } : {
92
107
  params: WildCard;
93
108
  } : {
94
- params: ParamPath & (WildCard extends undefined ? {} : WildCard);
109
+ params: Prettify<ParamPath & (WildCard extends undefined ? {} : WildCard)>;
95
110
  };
96
111
  type EndpointResponse = Record<string, any> | string | boolean | number | void | undefined;
97
- type Handler<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse, Extra extends Record<string, any> = Record<string, any>> = (ctx: Context<Path, Opts, Extra>) => Promise<R>;
112
+ type Handler<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse> = (ctx: Prettify<Context<Path, Opts> & InferUse<Opts> & ContextTools>) => Promise<R>;
113
+ type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "*";
114
+ type InferBody<Opts extends EndpointOptions, Body extends ZodSchema | undefined = (Opts["body"] & (undefined extends InferUseOptions<Opts>['body'] ? {} : InferUseOptions<Opts>['body']))> = Body extends ZodSchema ? Body extends ZodOptional<any> ? {
115
+ body?: Prettify<z.infer<Body>>;
116
+ } : {
117
+ body: Prettify<z.infer<Body>>;
118
+ } : {
119
+ body?: undefined;
120
+ };
121
+
98
122
  interface EndpointConfig {
99
123
  /**
100
124
  * Throw when the response isn't in 200 range
101
125
  */
102
126
  throwOnError?: boolean;
103
127
  }
104
- declare function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, ContextTools>): {
105
- (...ctx: HasRequiredKeys<Context<Path, Opts, {}>> extends true ? [Context<Path, Opts, {}>] : [Context<Path, Opts, {}>?]): Promise<R>;
128
+ declare function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R>): {
129
+ (...ctx: HasRequiredKeys<Context<Path, Opts>> extends true ? [Context<Path, Opts>] : [Context<Path, Opts>?]): Promise<R>;
106
130
  path: Path;
107
131
  options: Opts;
108
132
  method: Method | Method[];
109
133
  headers: Headers;
110
- middleware: Middleware<any> | undefined;
111
134
  };
112
- type Endpoint = {
113
- path: string;
114
- options: EndpointOptions;
115
- middleware?: Middleware<any>;
116
- headers?: Headers;
117
- } & ((ctx: any) => Promise<any>);
118
135
 
119
136
  interface RouterConfig {
120
137
  /**
@@ -129,64 +146,101 @@ interface RouterConfig {
129
146
  * Base path for the router
130
147
  */
131
148
  basePath?: string;
149
+ /**
150
+ * Middlewares for the router
151
+ */
152
+ routerMiddleware?: ({
153
+ /**
154
+ * The path to match for the middleware to be called
155
+ */
156
+ path: string;
157
+ /**
158
+ * The method to match for the middleware to be called
159
+ *
160
+ * @default "*"
161
+ */
162
+ method?: Method | Method[];
163
+ /**
164
+ * The middleware handler
165
+ */
166
+ handler: Endpoint;
167
+ })[];
132
168
  }
133
169
  declare const createRouter: <E extends Endpoint, Config extends RouterConfig>(endpoints: E[], config?: Config) => {
134
170
  handler: (request: Request) => Promise<Response>;
135
171
  };
136
172
 
173
+ type MiddlewareHandler<Opts extends EndpointOptions, R extends EndpointResponse> = (ctx: Prettify<InferBody<Opts> & InferRequest<Opts> & InferHeaders<Opts> & {
174
+ params?: Record<string, string>;
175
+ query?: Record<string, string>;
176
+ } & ContextTools>) => Promise<R>;
177
+ declare function createMiddleware<Opts extends EndpointOptions, R extends EndpointResponse>(optionsOrHandler: MiddlewareHandler<Opts, R>): Endpoint<Handler<string, Opts, R>, Opts>;
178
+ declare function createMiddleware<Opts extends Omit<EndpointOptions, "method">, R extends EndpointResponse>(optionsOrHandler: Opts, handler: MiddlewareHandler<Opts & {
179
+ method: "*";
180
+ }, R>): Endpoint<Handler<string, Opts & {
181
+ method: "*";
182
+ }, R>, Opts & {
183
+ method: "*";
184
+ }>;
185
+ type Middleware<Opts extends EndpointOptions, R> = (opts: Opts, handler: (ctx: {
186
+ body?: InferBody<Opts>;
187
+ params?: Record<string, string>;
188
+ query?: Record<string, string>;
189
+ }) => Promise<R>) => Endpoint;
190
+
137
191
  declare function getBody(request: Request): Promise<any>;
138
192
  declare function shouldSerialize(body: any): boolean;
139
193
  declare const statusCode: {
140
194
  OK: number;
141
- Created: number;
142
- Accepted: number;
143
- "No Content": number;
144
- "Multiple Choices": number;
145
- "Moved Permanently": number;
146
- Found: number;
147
- "See Other": number;
148
- "Not Modified": number;
149
- "Temporary Redirect": number;
150
- "Bad Request": number;
151
- Unauthorized: number;
152
- "Payment Required": number;
153
- Forbidden: number;
154
- "Not Found": number;
155
- "Method Not Allowed": number;
156
- "Not Acceptable": number;
157
- "Proxy Authentication Required": number;
158
- "Request Timeout": number;
159
- Conflict: number;
160
- Gone: number;
161
- "Length Required": number;
162
- "Precondition Failed": number;
163
- "Payload Too Large": number;
164
- "URI Too Long": number;
165
- "Unsupported Media Type": number;
166
- "Range Not Satisfiable": number;
167
- "Expectation Failed": number;
168
- "I'm a teapot": number;
169
- "Misdirected Request": number;
170
- "Unprocessable Entity": number;
171
- Locked: number;
172
- "Failed Dependency": number;
173
- "Too Early": number;
174
- "Upgrade Required": number;
175
- "Precondition Required": number;
176
- "Too Many Requests": number;
177
- "Request Header Fields Too Large": number;
178
- "Unavailable For Legal Reasons": number;
179
- "Internal Server Error": number;
180
- "Not Implemented": number;
181
- "Bad Gateway": number;
182
- "Service Unavailable": number;
183
- "Gateway Timeout": number;
184
- "HTTP Version Not Supported": number;
185
- "Variant Also Negotiates": number;
186
- "Insufficient Storage": number;
187
- "Loop Detected": number;
188
- "Not Extended": number;
189
- "Network Authentication Required": number;
195
+ CREATED: number;
196
+ ACCEPTED: number;
197
+ NO_CONTENT: number;
198
+ MULTIPLE_CHOICES: number;
199
+ MOVED_PERMANENTLY: number;
200
+ FOUND: number;
201
+ SEE_OTHER: number;
202
+ NOT_MODIFIED: number;
203
+ TEMPORARY_REDIRECT: number;
204
+ BAD_REQUEST: number;
205
+ UNAUTHORIZED: number;
206
+ PAYMENT_REQUIRED: number;
207
+ FORBIDDEN: number;
208
+ NOT_FOUND: number;
209
+ METHOD_NOT_ALLOWED: number;
210
+ NOT_ACCEPTABLE: number;
211
+ PROXY_AUTHENTICATION_REQUIRED: number;
212
+ REQUEST_TIMEOUT: number;
213
+ CONFLICT: number;
214
+ GONE: number;
215
+ LENGTH_REQUIRED: number;
216
+ PRECONDITION_FAILED: number;
217
+ PAYLOAD_TOO_LARGE: number;
218
+ URI_TOO_LONG: number;
219
+ UNSUPPORTED_MEDIA_TYPE: number;
220
+ RANGE_NOT_SATISFIABLE: number;
221
+ EXPECTATION_FAILED: number;
222
+ "I'M_A_TEAPOT": number;
223
+ MISDIRECTED_REQUEST: number;
224
+ UNPROCESSABLE_ENTITY: number;
225
+ LOCKED: number;
226
+ FAILED_DEPENDENCY: number;
227
+ TOO_EARLY: number;
228
+ UPGRADE_REQUIRED: number;
229
+ PRECONDITION_REQUIRED: number;
230
+ TOO_MANY_REQUESTS: number;
231
+ REQUEST_HEADER_FIELDS_TOO_LARGE: number;
232
+ UNAVAILABLE_FOR_LEGAL_REASONS: number;
233
+ INTERNAL_SERVER_ERROR: number;
234
+ NOT_IMPLEMENTED: number;
235
+ BAD_GATEWAY: number;
236
+ SERVICE_UNAVAILABLE: number;
237
+ GATEWAY_TIMEOUT: number;
238
+ HTTP_VERSION_NOT_SUPPORTED: number;
239
+ VARIANT_ALSO_NEGOTIATES: number;
240
+ INSUFFICIENT_STORAGE: number;
241
+ LOOP_DETECTED: number;
242
+ NOT_EXTENDED: number;
243
+ NETWORK_AUTHENTICATION_REQUIRED: number;
190
244
  };
191
245
 
192
246
  type Status = keyof typeof statusCode;
@@ -196,4 +250,4 @@ declare class APIError extends Error {
196
250
  constructor(status: Status, body?: Record<string, any>);
197
251
  }
198
252
 
199
- export { APIError, type Context, type Endpoint, type EndpointConfig, type EndpointOptions, type EndpointResponse, type Handler, type HasRequiredKeys, type Middleware, type Prettify, type RequiredKeysOf, createEndpoint, createMiddleware, createRouter, getBody, shouldSerialize, statusCode };
253
+ export { APIError, type EndpointConfig, type Middleware, type MiddlewareHandler, createEndpoint, createMiddleware, createRouter, getBody, shouldSerialize, statusCode };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var E=Object.defineProperty;var w=(r,e,n)=>e in r?E(r,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):r[e]=n;var l=(r,e,n)=>w(r,typeof e!="symbol"?e+"":e,n);import{ZodError as C}from"zod";var y=class extends Error{constructor(n,a){super(`API Error: ${n} ${a?.message??""}`,{cause:a});l(this,"status");l(this,"body");this.status=n,this.body=a??{},this.stack="",this.name="BetterCallAPIError"}};function B(r,e,n){let a=new Headers,t=async(...s)=>{let o={setHeader(d,i){a.set(d,i)},setCookie(d,i){a.append("Set-Cookie",`${d}=${i}`)},getCookie(d){return s[0]?.headers?.get("cookie")?.split(";").find(c=>c.startsWith(`${d}=`))?.split("=")[1]},...s[0]||{}};try{o.body=e.body?e.body.parse(o.body):void 0,o.query=e.query?e.query.parse(o.query):void 0,o.params=e.params?e.params.parse(o.params):void 0}catch(d){throw d instanceof C?new y("Bad Request",{message:d.message,details:d.errors}):d}if(e.requireHeaders&&!o.headers)throw new y("Bad Request",{message:"Headers are required"});if(e.requireRequest&&!o.request)throw new y("Bad Request",{message:"Request is required"});return await n(o)};return t.path=r,t.options=e,t.method=e.method,t.headers=a,t.middleware=void 0,t}import{createRouter as q,addRoute as P,findRoute as b}from"rou3";async function m(r){let e=r.headers.get("content-type")||"";if(r.body){if(e.includes("application/json"))return await r.json();if(e.includes("application/x-www-form-urlencoded")){let n=await r.formData(),a={};return n.forEach((t,s)=>{a[s]=t.toString()}),a}if(e.includes("multipart/form-data")){let n=await r.formData(),a={};return n.forEach((t,s)=>{a[s]=t}),a}return e.includes("text/plain")?await r.text():e.includes("application/octet-stream")?await r.arrayBuffer():e.includes("application/pdf")||e.includes("image/")||e.includes("video/")?await r.blob():e.includes("application/stream")||r.body instanceof ReadableStream?r.body:await r.text()}}function x(r){return typeof r=="object"&&r!==null&&!(r instanceof Blob)&&!(r instanceof FormData)}var R={OK:200,Created:201,Accepted:202,"No Content":204,"Multiple Choices":300,"Moved Permanently":301,Found:302,"See Other":303,"Not Modified":304,"Temporary Redirect":307,"Bad Request":400,Unauthorized:401,"Payment Required":402,Forbidden:403,"Not Found":404,"Method Not Allowed":405,"Not Acceptable":406,"Proxy Authentication Required":407,"Request Timeout":408,Conflict:409,Gone:410,"Length Required":411,"Precondition Failed":412,"Payload Too Large":413,"URI Too Long":414,"Unsupported Media Type":415,"Range Not Satisfiable":416,"Expectation Failed":417,"I'm a teapot":418,"Misdirected Request":421,"Unprocessable Entity":422,Locked:423,"Failed Dependency":424,"Too Early":425,"Upgrade Required":426,"Precondition Required":428,"Too Many Requests":429,"Request Header Fields Too Large":431,"Unavailable For Legal Reasons":451,"Internal Server Error":500,"Not Implemented":501,"Bad Gateway":502,"Service Unavailable":503,"Gateway Timeout":504,"HTTP Version Not Supported":505,"Variant Also Negotiates":506,"Insufficient Storage":507,"Loop Detected":508,"Not Extended":510,"Network Authentication Required":511};var N=(r,e)=>{let n=q();for(let t of r)if(Array.isArray(t.options?.method))for(let s of t.options.method)P(n,s,t.path,t);else P(n,t.options.method,t.path,t);return{handler:async t=>{let s=new URL(t.url),o=s.pathname;e?.basePath&&(o=o.split(e.basePath)[1]);let f=t.method,d=b(n,f,o),i=d?.data,c=await m(t),h=t.headers;if(!i)return new Response(null,{status:404,statusText:"Not Found"});try{let p={};i.middleware&&(p=await i.middleware({path:o,method:f,headers:h,params:s.searchParams,request:t.clone(),body:c})||{});let u=await i({path:o,method:f,headers:h,params:d?.params,request:t,body:c,...p?.context});if(u instanceof Response)return u;let g=x(u)?JSON.stringify(u):u;return new Response(g,{headers:i.headers})}catch(p){if(e?.onError){let u=await e.onError(p);if(u instanceof Response)return u}if(p instanceof y)return new Response(p.body?JSON.stringify(p.body):null,{status:R[p.status],statusText:p.status,headers:{"Content-Type":"application/json"}});if(e?.throwError)throw p;return new Response(null,{status:500,statusText:"Internal Server Error"})}}}};var L=r=>(e,n,a)=>{let t=async(...s)=>await a(s[0]||{});return t.path=e,t.options=n,t.middleware=r,t};export{y as APIError,B as createEndpoint,L as createMiddleware,N as createRouter,m as getBody,x as shouldSerialize,R as statusCode};
1
+ var P=Object.defineProperty;var g=(t,e,s)=>e in t?P(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var f=(t,e,s)=>g(t,typeof e!="symbol"?e+"":e,s);import{ZodError as S}from"zod";var u=class extends Error{constructor(s,a){super(`API Error: ${s} ${a?.message??""}`,{cause:a});f(this,"status");f(this,"body");this.status=s,this.body=a??{},this.stack="",this.name="BetterCallAPIError"}};function m(t,e,s){let a=new Headers,p=async(...o)=>{let r={setHeader(n,d){a.set(n,d)},setCookie(n,d){a.append("Set-Cookie",`${n}=${d}`)},getCookie(n){return o[0]?.headers?.get("cookie")?.split(";").find(R=>R.startsWith(`${n}=`))?.split("=")[1]},...o[0]||{},context:{}};if(e.use?.length)for(let n of e.use){let d=await n(r),R=d.options?.body?d.options.body.parse(r.body):void 0;d&&(r={...r,body:R?{...R,...r.body}:r.body,context:{...r.context||{},...d}})}try{let n=e.body?e.body.parse(r.body):r.body;r={...r,body:n?{...n,...r.body}:r.body},r.query=e.query?e.query.parse(r.query):r.query,r.params=e.params?e.params.parse(r.params):r.params}catch(n){throw n instanceof S?new u("BAD_REQUEST",{message:n.message,details:n.errors}):n}if(e.requireHeaders&&!r.headers)throw new u("BAD_REQUEST",{message:"Headers are required"});if(e.requireRequest&&!r.request)throw new u("BAD_REQUEST",{message:"Request is required"});return await s(r)};return p.path=t,p.options=e,p.method=e.method,p.headers=a,p}import{createRouter as N,addRoute as l,findRoute as x}from"rou3";async function _(t){let e=t.headers.get("content-type")||"";if(t.body){if(e.includes("application/json"))return await t.json();if(e.includes("application/x-www-form-urlencoded")){let s=await t.formData(),a={};return s.forEach((p,o)=>{a[o]=p.toString()}),a}if(e.includes("multipart/form-data")){let s=await t.formData(),a={};return s.forEach((p,o)=>{a[o]=p}),a}return e.includes("text/plain")?await t.text():e.includes("application/octet-stream")?await t.arrayBuffer():e.includes("application/pdf")||e.includes("image/")||e.includes("video/")?await t.blob():e.includes("application/stream")||t.body instanceof ReadableStream?t.body:await t.text()}}function I(t){return typeof t=="object"&&t!==null&&!(t instanceof Blob)&&!(t instanceof FormData)}var w={OK:200,CREATED:201,ACCEPTED:202,NO_CONTENT:204,MULTIPLE_CHOICES:300,MOVED_PERMANENTLY:301,FOUND:302,SEE_OTHER:303,NOT_MODIFIED:304,TEMPORARY_REDIRECT:307,BAD_REQUEST:400,UNAUTHORIZED:401,PAYMENT_REQUIRED:402,FORBIDDEN:403,NOT_FOUND:404,METHOD_NOT_ALLOWED:405,NOT_ACCEPTABLE:406,PROXY_AUTHENTICATION_REQUIRED:407,REQUEST_TIMEOUT:408,CONFLICT:409,GONE:410,LENGTH_REQUIRED:411,PRECONDITION_FAILED:412,PAYLOAD_TOO_LARGE:413,URI_TOO_LONG:414,UNSUPPORTED_MEDIA_TYPE:415,RANGE_NOT_SATISFIABLE:416,EXPECTATION_FAILED:417,"I'M_A_TEAPOT":418,MISDIRECTED_REQUEST:421,UNPROCESSABLE_ENTITY:422,LOCKED:423,FAILED_DEPENDENCY:424,TOO_EARLY:425,UPGRADE_REQUIRED:426,PRECONDITION_REQUIRED:428,TOO_MANY_REQUESTS:429,REQUEST_HEADER_FIELDS_TOO_LARGE:431,UNAVAILABLE_FOR_LEGAL_REASONS:451,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504,HTTP_VERSION_NOT_SUPPORTED:505,VARIANT_ALSO_NEGOTIATES:506,INSUFFICIENT_STORAGE:507,LOOP_DETECTED:508,NOT_EXTENDED:510,NETWORK_AUTHENTICATION_REQUIRED:511};var V=(t,e)=>{let s=N();for(let o of t)if(Array.isArray(o.options?.method))for(let r of o.options.method)l(s,r,o.path,o);else l(s,o.options.method,o.path,o);let a=N();for(let o of e?.routerMiddleware||[]){let r=Array.isArray(o.method)?o.method:[o.method];for(let E of r)l(a,E,o.path,o.handler)}return{handler:async o=>{let r=new URL(o.url),E=r.pathname;e?.basePath&&(E=E.split(e.basePath)[1]);let n=o.method,d=x(s,n,E),R=d?.data,O=await _(o),y=o.headers,h=Object.fromEntries(r.searchParams),T=x(a,n,E)?.data;if(!R)return new Response(null,{status:404,statusText:"Not Found"});try{let i={};if(T){let A=await T({path:E,method:n,headers:y,params:d?.params,request:o,body:O,query:h});A&&(i={...A,...i})}let c=await R({path:E,method:n,headers:y,params:d?.params,request:o,body:O,query:h,...i});if(c instanceof Response)return c;let C=I(c)?JSON.stringify(c):c;return new Response(C,{headers:R.headers})}catch(i){if(e?.onError){let c=await e.onError(i);if(c instanceof Response)return c}if(i instanceof u)return new Response(i.body?JSON.stringify(i.body):null,{status:w[i.status],statusText:i.status,headers:{"Content-Type":"application/json"}});if(e?.throwError)throw i;return new Response(null,{status:500,statusText:"Internal Server Error"})}}}};import{z as D}from"zod";function b(t,e){if(typeof t=="function")return m("*",{method:"*"},t);if(!e)throw new Error("Middleware handler is required");return m("*",{...t,method:"*"},e)}var $=b({body:D.object({name:D.string()})},async t=>{});export{u as APIError,m as createEndpoint,b as createMiddleware,V as createRouter,_ as getBody,I as shouldSerialize,w as statusCode};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/endpoint.ts","../src/better-call-error.ts","../src/router.ts","../src/utils.ts","../src/middleware.ts"],"sourcesContent":["import { z, ZodError, type ZodOptional, type ZodSchema } from \"zod\"\nimport type { Middleware } from \"./middleware\"\nimport { APIError } from \"./better-call-error\";\n\n\nexport type RequiredKeysOf<BaseType extends object> = Exclude<{\n [Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]>\n ? Key\n : never\n}[keyof BaseType], undefined>;\n\nexport type HasRequiredKeys<BaseType extends object> = RequiredKeysOf<BaseType> extends never ? false : true;\n\ntype Method = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"*\"\n\nexport interface EndpointOptions {\n method: Method | Method[]\n body?: ZodSchema\n query?: ZodSchema\n params?: ZodSchema<any>\n /**\n * If true headers will be required to be passed in the context\n */\n requireHeaders?: boolean\n /**\n * If true request object will be required\n */\n requireRequest?: boolean\n}\n\ntype InferParamPath<Path> =\n Path extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof InferParamPath<Rest>]: string }\n : Path extends `${infer _Start}:${infer Param}`\n ? { [K in Param]: string }\n : Path extends `${infer _Start}/${infer Rest}`\n ? InferParamPath<Rest>\n : undefined;\n\ntype InferParamWildCard<Path> = Path extends `${infer _Start}/*:${infer Param}/${infer Rest}` | `${infer _Start}/**:${infer Param}/${infer Rest}`\n ? { [K in Param | keyof InferParamPath<Rest>]: string }\n : Path extends `${infer _Start}/*`\n ? { [K in \"_\"]: string }\n : Path extends `${infer _Start}/${infer Rest}`\n ? InferParamPath<Rest>\n : undefined;\n\n\nexport type Prettify<T> = {\n [key in keyof T]: T[key];\n} & {};\n\ntype ContextTools = {\n setHeader: (key: string, value: string) => void\n setCookie: (key: string, value: string) => void\n getCookie: (key: string) => string | undefined\n}\n\nexport type Context<Path extends string, Opts extends EndpointOptions, Extra extends Record<string, any> = {}> = InferBody<Opts[\"body\"]> & InferParam<Path> & InferMethod<Opts['method']> & InferHeaders<Opts[\"requireHeaders\"]> & InferRequest<Opts[\"requireRequest\"]> & InferQuery<Opts[\"query\"]> & Extra\n\ntype InferMethod<M extends Method | Method[]> = M extends Array<Method> ? {\n method: M[number]\n} : {\n method?: M\n}\n\ntype InferHeaders<HeaderReq> = HeaderReq extends true ? {\n headers: Headers\n} : {\n headers?: Headers\n}\n\ntype InferRequest<RequestReq> = RequestReq extends true ? {\n request: Request\n} : {\n request?: Request\n}\n\n\ntype InferBody<Body> = Body extends ZodSchema ? Body extends ZodOptional<any> ? {\n body?: z.infer<Body>\n} : {\n body: z.infer<Body>\n} : {\n body?: undefined\n}\n\ntype InferQuery<Query> = Query extends ZodSchema ? Query extends ZodOptional<any> ? {\n query?: z.infer<Query>\n} : {\n query: z.infer<Query>\n} : {\n query?: undefined\n}\n\ntype InferParam<Path extends string, ParamPath extends InferParamPath<Path> = InferParamPath<Path>, WildCard extends InferParamWildCard<Path> = InferParamWildCard<Path>> = ParamPath extends undefined ? WildCard extends undefined ? {\n params?: undefined\n} : {\n params: WildCard\n} : {\n params: ParamPath & (WildCard extends undefined ? {} : WildCard)\n}\n\n\nexport type EndpointResponse = Record<string, any> | string | boolean | number | void | undefined\n\nexport type Handler<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse, Extra extends Record<string, any> = Record<string, any>> = (ctx: Context<Path, Opts, Extra>) => Promise<R>\n\nexport interface EndpointConfig {\n /**\n * Throw when the response isn't in 200 range\n */\n throwOnError?: boolean\n}\n\nexport function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, ContextTools>) {\n const responseHeader = new Headers()\n type Ctx = Context<Path, Opts>\n const handle = async (...ctx: HasRequiredKeys<Ctx> extends true ? [Ctx] : [Ctx?]) => {\n const internalCtx = ({\n setHeader(key, value) {\n responseHeader.set(key, value)\n },\n setCookie(key, value) {\n responseHeader.append(\"Set-Cookie\", `${key}=${value}`)\n },\n getCookie(key) {\n const header = ctx[0]?.headers\n return header?.get(\"cookie\")?.split(\";\").find(cookie => cookie.startsWith(`${key}=`))?.split(\"=\")[1]\n },\n ...(ctx[0] || {})\n }) as (Ctx & ContextTools)\n try {\n internalCtx.body = options.body ? options.body.parse(internalCtx.body) : undefined\n internalCtx.query = options.query ? options.query.parse(internalCtx.query) : undefined\n internalCtx.params = options.params ? options.params.parse(internalCtx.params) : undefined\n } catch (e) {\n if (e instanceof ZodError) {\n throw new APIError(\"Bad Request\", {\n message: e.message,\n details: e.errors\n })\n }\n throw e\n }\n if (options.requireHeaders && !internalCtx.headers) {\n throw new APIError(\"Bad Request\", {\n message: \"Headers are required\"\n })\n }\n if (options.requireRequest && !internalCtx.request) {\n throw new APIError(\"Bad Request\", {\n message: \"Request is required\"\n })\n }\n const res = await handler(internalCtx)\n return res as ReturnType<Handler<Path, Opts, R>>\n }\n handle.path = path\n handle.options = options\n handle.method = options.method\n handle.headers = responseHeader\n //for type inference\n handle.middleware = undefined as (Middleware<any> | undefined)\n\n return handle\n}\n\nexport type Endpoint = {\n path: string\n options: EndpointOptions\n middleware?: Middleware<any>\n headers?: Headers\n} & ((ctx: any) => Promise<any>)","import type { statusCode } from \"./utils\"\n\ntype Status = keyof typeof statusCode\n\nexport class APIError extends Error {\n status: Status\n body: Record<string, any>\n constructor(\n status: Status,\n body?: Record<string, any>,\n ) {\n super(\n `API Error: ${status} ${body?.message ?? \"\"}`,\n {\n cause: body,\n }\n )\n this.status = status\n this.body = body ?? {}\n this.stack = \"\";\n this.name = \"BetterCallAPIError\"\n }\n}","import type { Endpoint } from \"./endpoint\";\nimport {\n createRouter as createRou3Router,\n addRoute,\n findRoute,\n} from \"rou3\";\nimport { getBody, shouldSerialize, statusCode } from \"./utils\";\nimport { APIError } from \"./better-call-error\";\n\ninterface RouterConfig {\n /**\n * Throw error if error occurred other than APIError\n */\n throwError?: boolean\n /**\n * Handle error\n */\n onError?: (e: unknown) => void | Promise<void> | Response | Promise<Response>\n /**\n * Base path for the router\n */\n basePath?: string\n}\n\nexport const createRouter = <E extends Endpoint, Config extends RouterConfig>(endpoints: E[], config?: Config) => {\n const router = createRou3Router()\n for (const endpoint of endpoints) {\n if (Array.isArray(endpoint.options?.method)) {\n for (const method of endpoint.options.method) {\n addRoute(router, method, endpoint.path, endpoint)\n }\n } else {\n addRoute(router, endpoint.options.method, endpoint.path, endpoint)\n }\n }\n\n const handler = async (request: Request) => {\n const url = new URL(request.url);\n let path = url.pathname\n if (config?.basePath) {\n path = path.split(config.basePath)[1]\n }\n const method = request.method;\n const route = findRoute(router, method, path)\n const handler = route?.data as Endpoint\n const body = await getBody(request)\n const headers = request.headers\n\n //handler 404\n if (!handler) {\n return new Response(null, {\n status: 404,\n statusText: \"Not Found\"\n })\n }\n try {\n let middleware: Record<string, any> = {}\n if (handler.middleware) {\n middleware = await handler.middleware({\n path: path,\n method: method,\n headers,\n params: url.searchParams,\n request: request.clone(),\n body: body,\n }) || {}\n }\n const handlerRes = await handler({\n path: path,\n method: method as \"GET\",\n headers,\n params: route?.params as any,\n request: request,\n body: body,\n ...middleware?.context,\n })\n if (handlerRes instanceof Response) {\n return handlerRes\n }\n const resBody = shouldSerialize(handlerRes) ? JSON.stringify(handlerRes) : handlerRes\n return new Response(resBody as any, {\n headers: handler.headers\n })\n } catch (e) {\n if (config?.onError) {\n const onErrorRes = await config.onError(e)\n if (onErrorRes instanceof Response) {\n return onErrorRes\n }\n }\n if (e instanceof APIError) {\n return new Response(e.body ? JSON.stringify(e.body) : null, {\n status: statusCode[e.status],\n statusText: e.status,\n headers: {\n \"Content-Type\": \"application/json\",\n }\n })\n }\n if (config?.throwError) {\n throw e\n }\n return new Response(null, {\n status: 500,\n statusText: \"Internal Server Error\"\n })\n }\n }\n return { handler }\n}","export async function getBody(request: Request) {\n const contentType = request.headers.get('content-type') || '';\n\n if (!request.body) {\n return undefined\n }\n\n if (contentType.includes('application/json')) {\n return await request.json();\n }\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n const result: Record<string, string> = {};\n formData.forEach((value, key) => {\n result[key] = value.toString();\n });\n return result;\n }\n\n if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n const result: Record<string, any> = {};\n formData.forEach((value, key) => {\n result[key] = value;\n });\n return result;\n }\n\n if (contentType.includes('text/plain')) {\n return await request.text();\n }\n\n if (contentType.includes('application/octet-stream')) {\n return await request.arrayBuffer();\n }\n\n if (contentType.includes('application/pdf') || contentType.includes('image/') || contentType.includes('video/')) {\n const blob = await request.blob();\n return blob;\n }\n\n if (contentType.includes('application/stream') || request.body instanceof ReadableStream) {\n return request.body;\n }\n\n return await request.text();\n}\n\n\nexport function shouldSerialize(body: any) {\n return typeof body === \"object\" && body !== null && !(body instanceof Blob) && !(body instanceof FormData)\n}\n\nexport const statusCode = {\n \"OK\": 200,\n \"Created\": 201,\n \"Accepted\": 202,\n \"No Content\": 204,\n \"Multiple Choices\": 300,\n \"Moved Permanently\": 301,\n \"Found\": 302,\n \"See Other\": 303,\n \"Not Modified\": 304,\n \"Temporary Redirect\": 307,\n \"Bad Request\": 400,\n \"Unauthorized\": 401,\n \"Payment Required\": 402,\n \"Forbidden\": 403,\n \"Not Found\": 404,\n \"Method Not Allowed\": 405,\n \"Not Acceptable\": 406,\n \"Proxy Authentication Required\": 407,\n \"Request Timeout\": 408,\n \"Conflict\": 409,\n \"Gone\": 410,\n \"Length Required\": 411,\n \"Precondition Failed\": 412,\n \"Payload Too Large\": 413,\n \"URI Too Long\": 414,\n \"Unsupported Media Type\": 415,\n \"Range Not Satisfiable\": 416,\n \"Expectation Failed\": 417,\n \"I'm a teapot\": 418,\n \"Misdirected Request\": 421,\n \"Unprocessable Entity\": 422,\n \"Locked\": 423,\n \"Failed Dependency\": 424,\n \"Too Early\": 425,\n \"Upgrade Required\": 426,\n \"Precondition Required\": 428,\n \"Too Many Requests\": 429,\n \"Request Header Fields Too Large\": 431,\n \"Unavailable For Legal Reasons\": 451,\n \"Internal Server Error\": 500,\n \"Not Implemented\": 501,\n \"Bad Gateway\": 502,\n \"Service Unavailable\": 503,\n \"Gateway Timeout\": 504,\n \"HTTP Version Not Supported\": 505,\n \"Variant Also Negotiates\": 506,\n \"Insufficient Storage\": 507,\n \"Loop Detected\": 508,\n \"Not Extended\": 510,\n \"Network Authentication Required\": 511,\n}","import type { HasRequiredKeys } from \"type-fest\"\nimport { type Context, type EndpointOptions, type EndpointResponse, type Handler } from \"./endpoint\"\n\n\nexport type Middleware<E extends Record<string, string>> = (ctx: Context<string, EndpointOptions, E>) => Promise<{\n context: E\n} | void>\n\nexport const createMiddleware = <E extends Record<string, any>, M extends Middleware<E>>(middleware: M) => {\n type MiddlewareContext = Awaited<ReturnType<M>> extends {\n context: infer C\n } ? C extends Record<string, any> ? C : {} : {}\n return <Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R, MiddlewareContext>) => {\n type Ctx = Context<Path, Opts, MiddlewareContext>\n const handle = async (...ctx: HasRequiredKeys<Ctx> extends true ? [Ctx] : [Ctx?]) => {\n const res = await handler((ctx[0] || {}) as Ctx)\n return res as ReturnType<Handler<Path, Opts, R>>\n }\n handle.path = path\n handle.options = options\n handle.middleware = middleware\n return handle\n }\n}"],"mappings":"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","names":["ZodError","APIError","status","body","__publicField","createEndpoint","path","options","handler","responseHeader","handle","ctx","internalCtx","key","value","cookie","e","ZodError","APIError","createRou3Router","addRoute","findRoute","getBody","request","contentType","formData","result","value","key","shouldSerialize","body","statusCode","createRouter","endpoints","config","router","createRou3Router","endpoint","method","addRoute","request","url","path","route","findRoute","handler","body","getBody","headers","middleware","handlerRes","resBody","shouldSerialize","e","onErrorRes","APIError","statusCode","createMiddleware","middleware","path","options","handler","handle","ctx"]}
1
+ {"version":3,"sources":["../src/endpoint.ts","../src/better-call-error.ts","../src/router.ts","../src/utils.ts","../src/middleware.ts"],"sourcesContent":["import { z, ZodError, type ZodOptional, type ZodSchema } from \"zod\"\nimport type { Middleware } from \"./middleware\"\nimport { APIError } from \"./better-call-error\";\nimport type { HasRequiredKeys, UnionToIntersection } from \"./helper\";\nimport type { Context, ContextTools, Endpoint, EndpointOptions, EndpointResponse, Handler } from \"./types\";\n\n\nexport interface EndpointConfig {\n /**\n * Throw when the response isn't in 200 range\n */\n throwOnError?: boolean\n}\n\nexport function createEndpoint<Path extends string, Opts extends EndpointOptions, R extends EndpointResponse>(path: Path, options: Opts, handler: Handler<Path, Opts, R>) {\n const responseHeader = new Headers()\n type Ctx = Context<Path, Opts>\n const handle = async (...ctx: HasRequiredKeys<Ctx> extends true ? [Ctx] : [Ctx?]) => {\n let internalCtx = ({\n setHeader(key: string, value: string) {\n responseHeader.set(key, value)\n },\n setCookie(key: string, value: string) {\n responseHeader.append(\"Set-Cookie\", `${key}=${value}`)\n },\n getCookie(key: string) {\n const header = ctx[0]?.headers\n return header?.get(\"cookie\")?.split(\";\").find(cookie => cookie.startsWith(`${key}=`))?.split(\"=\")[1]\n },\n ...(ctx[0] || {}),\n context: {}\n })\n if (options.use?.length) {\n for (const middleware of options.use) {\n const res = await middleware(internalCtx) as Endpoint\n const body = res.options?.body ? res.options.body.parse(internalCtx.body) : undefined\n if (res) {\n internalCtx = {\n ...internalCtx,\n body: body ? {\n ...body,\n ...internalCtx.body\n } : internalCtx.body,\n context: {\n ...internalCtx.context || {},\n ...res\n }\n }\n }\n }\n }\n try {\n const body = options.body ? options.body.parse(internalCtx.body) : internalCtx.body\n internalCtx = {\n ...internalCtx,\n body: body ? {\n ...body,\n ...internalCtx.body\n } : internalCtx.body,\n }\n internalCtx.query = options.query ? options.query.parse(internalCtx.query) : internalCtx.query\n internalCtx.params = options.params ? options.params.parse(internalCtx.params) : internalCtx.params\n } catch (e) {\n if (e instanceof ZodError) {\n throw new APIError(\"BAD_REQUEST\", {\n message: e.message,\n details: e.errors\n })\n }\n throw e\n }\n if (options.requireHeaders && !internalCtx.headers) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Headers are required\"\n })\n }\n if (options.requireRequest && !internalCtx.request) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Request is required\"\n })\n }\n //@ts-expect-error\n const res = await handler(internalCtx)\n return res as ReturnType<Handler<Path, Opts, R>>\n }\n handle.path = path\n handle.options = options\n handle.method = options.method\n handle.headers = responseHeader\n return handle\n}","import type { statusCode } from \"./utils\"\n\ntype Status = keyof typeof statusCode\n\nexport class APIError extends Error {\n status: Status\n body: Record<string, any>\n constructor(\n status: Status,\n body?: Record<string, any>,\n ) {\n super(\n `API Error: ${status} ${body?.message ?? \"\"}`,\n {\n cause: body,\n }\n )\n this.status = status\n this.body = body ?? {}\n this.stack = \"\";\n this.name = \"BetterCallAPIError\"\n }\n}","import {\n createRouter as createRou3Router,\n addRoute,\n findRoute,\n} from \"rou3\";\nimport { getBody, shouldSerialize, statusCode } from \"./utils\";\nimport { APIError } from \"./better-call-error\";\nimport type { Middleware, MiddlewareHandler } from \"./middleware\";\nimport type { Endpoint, Method } from \"./types\";\n\ninterface RouterConfig {\n /**\n * Throw error if error occurred other than APIError\n */\n throwError?: boolean\n /**\n * Handle error\n */\n onError?: (e: unknown) => void | Promise<void> | Response | Promise<Response>\n /**\n * Base path for the router\n */\n basePath?: string\n /**\n * Middlewares for the router\n */\n routerMiddleware?: ({\n /**\n * The path to match for the middleware to be called\n */\n path: string,\n /**\n * The method to match for the middleware to be called\n * \n * @default \"*\"\n */\n method?: Method | Method[],\n /**\n * The middleware handler\n */\n handler: Endpoint\n })[]\n}\n\nexport const createRouter = <E extends Endpoint, Config extends RouterConfig>(endpoints: E[], config?: Config) => {\n const router = createRou3Router()\n for (const endpoint of endpoints) {\n if (Array.isArray(endpoint.options?.method)) {\n for (const method of endpoint.options.method) {\n addRoute(router, method, endpoint.path, endpoint)\n }\n } else {\n addRoute(router, endpoint.options.method, endpoint.path, endpoint)\n }\n }\n\n const middlewareRouter = createRou3Router()\n for (const route of (config?.routerMiddleware || [])) {\n const methods = Array.isArray(route.method) ? route.method : [route.method]\n for (const method of methods) {\n addRoute(middlewareRouter, method, route.path, route.handler)\n }\n }\n\n const handler = async (request: Request) => {\n const url = new URL(request.url);\n let path = url.pathname\n if (config?.basePath) {\n path = path.split(config.basePath)[1]\n }\n const method = request.method;\n const route = findRoute(router, method, path)\n const handler = route?.data as Endpoint\n const body = await getBody(request)\n const headers = request.headers\n const query = Object.fromEntries(url.searchParams)\n const middleware = findRoute(middlewareRouter, method, path)?.data as Endpoint | undefined\n\n //handler 404\n if (!handler) {\n return new Response(null, {\n status: 404,\n statusText: \"Not Found\"\n })\n }\n try {\n let middlewareContext: Record<string, any> = {}\n if (middleware) {\n const res = await middleware({\n path: path,\n method: method as \"GET\",\n headers,\n params: route?.params as any,\n request: request,\n body: body,\n query\n })\n if (res) {\n middlewareContext = {\n ...res,\n ...middlewareContext\n }\n }\n }\n const handlerRes = await handler({\n path: path,\n method: method as \"GET\",\n headers,\n params: route?.params as any,\n request: request,\n body: body,\n query,\n ...middlewareContext,\n })\n if (handlerRes instanceof Response) {\n return handlerRes\n }\n const resBody = shouldSerialize(handlerRes) ? JSON.stringify(handlerRes) : handlerRes\n return new Response(resBody as any, {\n headers: handler.headers\n })\n } catch (e) {\n if (config?.onError) {\n const onErrorRes = await config.onError(e)\n if (onErrorRes instanceof Response) {\n return onErrorRes\n }\n }\n if (e instanceof APIError) {\n return new Response(e.body ? JSON.stringify(e.body) : null, {\n status: statusCode[e.status],\n statusText: e.status,\n headers: {\n \"Content-Type\": \"application/json\",\n }\n })\n }\n if (config?.throwError) {\n throw e\n }\n return new Response(null, {\n status: 500,\n statusText: \"Internal Server Error\"\n })\n }\n }\n return {\n handler\n }\n}","export async function getBody(request: Request) {\n const contentType = request.headers.get('content-type') || '';\n\n if (!request.body) {\n return undefined\n }\n\n if (contentType.includes('application/json')) {\n return await request.json();\n }\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const formData = await request.formData();\n const result: Record<string, string> = {};\n formData.forEach((value, key) => {\n result[key] = value.toString();\n });\n return result;\n }\n\n if (contentType.includes('multipart/form-data')) {\n const formData = await request.formData();\n const result: Record<string, any> = {};\n formData.forEach((value, key) => {\n result[key] = value;\n });\n return result;\n }\n\n if (contentType.includes('text/plain')) {\n return await request.text();\n }\n\n if (contentType.includes('application/octet-stream')) {\n return await request.arrayBuffer();\n }\n\n if (contentType.includes('application/pdf') || contentType.includes('image/') || contentType.includes('video/')) {\n const blob = await request.blob();\n return blob;\n }\n\n if (contentType.includes('application/stream') || request.body instanceof ReadableStream) {\n return request.body;\n }\n\n return await request.text();\n}\n\n\nexport function shouldSerialize(body: any) {\n return typeof body === \"object\" && body !== null && !(body instanceof Blob) && !(body instanceof FormData)\n}\n\nexport const statusCode = {\n \"OK\": 200,\n \"CREATED\": 201,\n \"ACCEPTED\": 202,\n \"NO_CONTENT\": 204,\n \"MULTIPLE_CHOICES\": 300,\n \"MOVED_PERMANENTLY\": 301,\n \"FOUND\": 302,\n \"SEE_OTHER\": 303,\n \"NOT_MODIFIED\": 304,\n \"TEMPORARY_REDIRECT\": 307,\n \"BAD_REQUEST\": 400,\n \"UNAUTHORIZED\": 401,\n \"PAYMENT_REQUIRED\": 402,\n \"FORBIDDEN\": 403,\n \"NOT_FOUND\": 404,\n \"METHOD_NOT_ALLOWED\": 405,\n \"NOT_ACCEPTABLE\": 406,\n \"PROXY_AUTHENTICATION_REQUIRED\": 407,\n \"REQUEST_TIMEOUT\": 408,\n \"CONFLICT\": 409,\n \"GONE\": 410,\n \"LENGTH_REQUIRED\": 411,\n \"PRECONDITION_FAILED\": 412,\n \"PAYLOAD_TOO_LARGE\": 413,\n \"URI_TOO_LONG\": 414,\n \"UNSUPPORTED_MEDIA_TYPE\": 415,\n \"RANGE_NOT_SATISFIABLE\": 416,\n \"EXPECTATION_FAILED\": 417,\n \"I'M_A_TEAPOT\": 418,\n \"MISDIRECTED_REQUEST\": 421,\n \"UNPROCESSABLE_ENTITY\": 422,\n \"LOCKED\": 423,\n \"FAILED_DEPENDENCY\": 424,\n \"TOO_EARLY\": 425,\n \"UPGRADE_REQUIRED\": 426,\n \"PRECONDITION_REQUIRED\": 428,\n \"TOO_MANY_REQUESTS\": 429,\n \"REQUEST_HEADER_FIELDS_TOO_LARGE\": 431,\n \"UNAVAILABLE_FOR_LEGAL_REASONS\": 451,\n \"INTERNAL_SERVER_ERROR\": 500,\n \"NOT_IMPLEMENTED\": 501,\n \"BAD_GATEWAY\": 502,\n \"SERVICE_UNAVAILABLE\": 503,\n \"GATEWAY_TIMEOUT\": 504,\n \"HTTP_VERSION_NOT_SUPPORTED\": 505,\n \"VARIANT_ALSO_NEGOTIATES\": 506,\n \"INSUFFICIENT_STORAGE\": 507,\n \"LOOP_DETECTED\": 508,\n \"NOT_EXTENDED\": 510,\n \"NETWORK_AUTHENTICATION_REQUIRED\": 511,\n}\n","import { z } from \"zod\"\nimport type { ContextTools, Endpoint, EndpointOptions, EndpointResponse, Handler, InferBody, InferHeaders, InferRequest, Prettify } from \"./types\"\nimport { createEndpoint } from \"./endpoint\"\n\nexport type MiddlewareHandler<Opts extends EndpointOptions, R extends EndpointResponse> = (ctx: Prettify<InferBody<Opts> & InferRequest<Opts> & InferHeaders<Opts> & {\n params?: Record<string, string>,\n query?: Record<string, string>,\n} & ContextTools>) => Promise<R>\n\nexport function createMiddleware<Opts extends EndpointOptions, R extends EndpointResponse>(optionsOrHandler: MiddlewareHandler<Opts, R>): Endpoint<Handler<string, Opts, R>, Opts>\nexport function createMiddleware<Opts extends Omit<EndpointOptions, \"method\">, R extends EndpointResponse>(optionsOrHandler: Opts, handler: MiddlewareHandler<Opts & {\n method: \"*\"\n}, R>): Endpoint<Handler<string, Opts & {\n method: \"*\"\n}, R>, Opts & {\n method: \"*\"\n}>\nexport function createMiddleware(optionsOrHandler: any, handler?: any) {\n if (typeof optionsOrHandler === \"function\") {\n return createEndpoint(\"*\", {\n method: \"*\"\n }, optionsOrHandler)\n }\n if (!handler) {\n throw new Error(\"Middleware handler is required\")\n }\n const endpoint = createEndpoint(\"*\", {\n ...optionsOrHandler,\n method: \"*\"\n }, handler)\n return endpoint as any\n}\n\nexport type Middleware<Opts extends EndpointOptions, R> = (opts: Opts, handler: (ctx: {\n body?: InferBody<Opts>,\n params?: Record<string, string>,\n query?: Record<string, string>\n}) => Promise<R>) => Endpoint\n\nconst m1 = createMiddleware({\n body: z.object({\n name: z.string()\n }),\n}, async (ctx) => {\n ctx\n})"],"mappings":"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","names":["ZodError","APIError","status","body","__publicField","createEndpoint","path","options","handler","responseHeader","handle","ctx","internalCtx","key","value","cookie","middleware","res","body","e","ZodError","APIError","createRou3Router","addRoute","findRoute","getBody","request","contentType","formData","result","value","key","shouldSerialize","body","statusCode","createRouter","endpoints","config","router","createRou3Router","endpoint","method","addRoute","middlewareRouter","route","methods","request","url","path","findRoute","handler","body","getBody","headers","query","middleware","middlewareContext","res","handlerRes","resBody","shouldSerialize","e","onErrorRes","APIError","statusCode","z","createMiddleware","optionsOrHandler","handler","createEndpoint","m1","z","ctx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-call",
3
- "version": "0.0.5-beta.2",
3
+ "version": "0.0.5-beta.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -12,10 +12,11 @@
12
12
  "build": "tsup"
13
13
  },
14
14
  "devDependencies": {
15
+ "@better-fetch/fetch": "^1.1.4",
15
16
  "@types/bun": "latest",
16
- "type-fest": "^4.23.0",
17
17
  "bumpp": "^9.4.1",
18
18
  "tsup": "^8.2.3",
19
+ "type-fest": "^4.23.0",
19
20
  "vitest": "^2.0.4",
20
21
  "zod": "^3.23.8"
21
22
  },