better-call 0.0.5-beta.9 → 0.1.0
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 +208 -90
- package/dist/client.cjs +2 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +40 -0
- package/dist/client.d.ts +40 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -146
- package/dist/index.d.ts +4 -146
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/router-fNn8BCPr.d.cts +198 -0
- package/dist/router-fNn8BCPr.d.ts +198 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# better-call
|
|
2
2
|
|
|
3
|
-
Better call is a tiny web framework for creating endpoints that can be invoked as a normal function or mounted to a router
|
|
3
|
+
Better call is a tiny web framework for creating endpoints that can be invoked as a normal function or mounted to a router to be served by any web standard compatible server (like Bun, node, nextjs, sveltekit...) and also includes a typed RPC client for typesafe client-side invocation of these endpoints.
|
|
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
|
-
> ⚠️ This project
|
|
7
|
+
> ⚠️ This project in development and not ready for production use. But feel free to try it out and give feedback.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -12,9 +12,15 @@ Built for typescript and it comes with a very high performance router based on [
|
|
|
12
12
|
pnpm i better-call
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
make sure to install zod if you haven't
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm i zod
|
|
19
|
+
```
|
|
20
|
+
|
|
15
21
|
## Usage
|
|
16
22
|
|
|
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
|
|
23
|
+
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 handler that will be invoked when the endpoint is called.
|
|
18
24
|
|
|
19
25
|
```ts
|
|
20
26
|
import { createEndpoint, createRouter } from "better-call"
|
|
@@ -46,15 +52,178 @@ OR you can mount the endpoint to a router and serve it with any web standard com
|
|
|
46
52
|
> The example below uses [Bun](https://bun.sh/)
|
|
47
53
|
|
|
48
54
|
```ts
|
|
49
|
-
const router = createRouter(
|
|
55
|
+
const router = createRouter({
|
|
50
56
|
createItem
|
|
51
|
-
|
|
57
|
+
})
|
|
52
58
|
|
|
53
59
|
Bun.serve({
|
|
54
60
|
fetch: router.handler
|
|
55
61
|
})
|
|
56
62
|
```
|
|
57
63
|
|
|
64
|
+
Then you can use the rpc client to call the endpoints on client.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
//client.ts
|
|
68
|
+
import { createClient } from "better-call/client";
|
|
69
|
+
|
|
70
|
+
const client = createClient<typeof router>();
|
|
71
|
+
const items = await client("/item", {
|
|
72
|
+
body: {
|
|
73
|
+
id: "123"
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Returning non 200 responses
|
|
79
|
+
|
|
80
|
+
To return a non 200 response, you will need to throw Better Call's `APIError` error. If the endpoint is called as a function, the error will be thrown but if it's mounted to a router, the error will be converted to a response object with the correct status code and headers.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const createItem = createEndpoint("/item", {
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: z.object({
|
|
86
|
+
id: z.string()
|
|
87
|
+
})
|
|
88
|
+
}, async (ctx) => {
|
|
89
|
+
if(ctx.body.id === "123") {
|
|
90
|
+
throw new APIError("Bad Request", {
|
|
91
|
+
message: "Id is not allowed"
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
item: {
|
|
96
|
+
id: ctx.body.id
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Endpoint
|
|
103
|
+
|
|
104
|
+
Endpoints are building blocks of better-call.
|
|
105
|
+
|
|
106
|
+
#### Path
|
|
107
|
+
|
|
108
|
+
The path is the URL path that the endpoint will respond to. It can be a direct path or a path with parameters and wildcards.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
//direct path
|
|
112
|
+
const endpoint = createEndpoint("/item", {
|
|
113
|
+
method: "GET",
|
|
114
|
+
}, async (ctx) => {})
|
|
115
|
+
|
|
116
|
+
//path with parameters
|
|
117
|
+
const endpoint = createEndpoint("/item/:id", {
|
|
118
|
+
method: "GET",
|
|
119
|
+
}, async (ctx) => {
|
|
120
|
+
return {
|
|
121
|
+
item: {
|
|
122
|
+
id: ctx.params.id
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
//path with wildcards
|
|
128
|
+
const endpoint = createEndpoint("/item/**:name", {
|
|
129
|
+
method: "GET",
|
|
130
|
+
}, async (ctx) => {
|
|
131
|
+
//the name will be the remaining path
|
|
132
|
+
ctx.params.name
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
#### Body Schema
|
|
136
|
+
|
|
137
|
+
The `body` option accepts a zod schema and will validate the request body. If the request body doesn't match the schema, the endpoint will throw an error. If it's mounted to a router, it'll return a 400 error.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const createItem = createEndpoint("/item", {
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: z.object({
|
|
143
|
+
id: z.string()
|
|
144
|
+
})
|
|
145
|
+
}, async (ctx) => {
|
|
146
|
+
return {
|
|
147
|
+
item: {
|
|
148
|
+
id: ctx.body.id
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Query Schema
|
|
155
|
+
|
|
156
|
+
The `query` option accepts a zod schema and will validate the request query. If the request query doesn't match the schema, the endpoint will throw an error. If it's mounted to a router, it'll return a 400 error.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const createItem = createEndpoint("/item", {
|
|
160
|
+
method: "GET",
|
|
161
|
+
query: z.object({
|
|
162
|
+
id: z.string()
|
|
163
|
+
})
|
|
164
|
+
}, async (ctx) => {
|
|
165
|
+
return {
|
|
166
|
+
item: {
|
|
167
|
+
id: ctx.query.id
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### Require Headers
|
|
174
|
+
|
|
175
|
+
The `requireHeaders` option is used to require the request to have headers. If the request doesn't have headers, the endpoint will throw an error. And even when you call the endpoint as a function, it will require headers to be passed in the context.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const createItem = createEndpoint("/item", {
|
|
179
|
+
method: "GET",
|
|
180
|
+
requireHeaders: true
|
|
181
|
+
}, async (ctx) => {
|
|
182
|
+
return {
|
|
183
|
+
item: {
|
|
184
|
+
id: ctx.headers.get("id")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
createItem({
|
|
189
|
+
headers: new Headers()
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Require Request
|
|
194
|
+
|
|
195
|
+
The `requireRequest` option is used to require the request to have a request object. If the request doesn't have a request object, the endpoint will throw an error. And even when you call the endpoint as a function, it will require a request to be passed in the context.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const createItem = createEndpoint("/item", {
|
|
199
|
+
method: "GET",
|
|
200
|
+
requireRequest: true
|
|
201
|
+
}, async (ctx) => {
|
|
202
|
+
return {
|
|
203
|
+
item: {
|
|
204
|
+
id: ctx.request.id
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
createItem({
|
|
210
|
+
request: new Request()
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
### Handler
|
|
216
|
+
|
|
217
|
+
this is the function that will be invoked when the endpoint is called. It accepts a context object that contains the request, headers, body, query, params and other information.
|
|
218
|
+
|
|
219
|
+
It can return a response object, a string, a boolean, a number, an object with a status, body, headers and other properties or undefined.
|
|
220
|
+
|
|
221
|
+
If you return a response object, it will be returned as is even when it's mounted to a router.
|
|
222
|
+
|
|
223
|
+
It can also throw an error and if it throws APIError, it will be converted to a response object with the correct status code and headers.
|
|
224
|
+
|
|
225
|
+
- **Context**: the context object contains the request, headers, body, query, params and a helper function to set headers, cookies and get cookies. If there is a middleware, the context will be extended with the middleware context.
|
|
226
|
+
|
|
58
227
|
### Middleware
|
|
59
228
|
|
|
60
229
|
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.
|
|
@@ -106,16 +275,22 @@ You can create a router by calling `createRouter` and passing it an array of end
|
|
|
106
275
|
import { createRouter } from "better-call"
|
|
107
276
|
import { createItem } from "./item"
|
|
108
277
|
|
|
109
|
-
const router = createRouter(
|
|
278
|
+
const router = createRouter({
|
|
110
279
|
createItem
|
|
111
|
-
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
Bun.serve({
|
|
283
|
+
fetch: router.handler
|
|
284
|
+
})
|
|
112
285
|
```
|
|
113
286
|
|
|
114
287
|
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.
|
|
115
288
|
|
|
116
289
|
#### Router Options
|
|
117
290
|
|
|
118
|
-
**routerMiddleware
|
|
291
|
+
**routerMiddleware:**
|
|
292
|
+
|
|
293
|
+
A router middleware is similar to an endpoint middleware but it's applied to any path that matches the route. It's like any traditional middleware. You have to pass endpoints to the router middleware as an array.
|
|
119
294
|
|
|
120
295
|
```ts
|
|
121
296
|
const routeMiddleware = createEndpoint("/api/**", {
|
|
@@ -125,10 +300,13 @@ const routeMiddleware = createEndpoint("/api/**", {
|
|
|
125
300
|
name: "hello"
|
|
126
301
|
}
|
|
127
302
|
})
|
|
128
|
-
const router = createRouter(
|
|
303
|
+
const router = createRouter({
|
|
129
304
|
createItem
|
|
130
|
-
|
|
131
|
-
routerMiddleware: [
|
|
305
|
+
}, {
|
|
306
|
+
routerMiddleware: [{
|
|
307
|
+
path: "/api/**",
|
|
308
|
+
middleware:routeMiddleware
|
|
309
|
+
}]
|
|
132
310
|
})
|
|
133
311
|
```
|
|
134
312
|
|
|
@@ -138,30 +316,29 @@ const router = createRouter([
|
|
|
138
316
|
|
|
139
317
|
**throwError**: If true, the router will throw an error if an error occurs in the middleware or the endpoint.
|
|
140
318
|
|
|
319
|
+
### RPC Client
|
|
141
320
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
To return a non 200 response, you will need to throw Better Call's `APIError` error. If the endpoint is called as a function, the error will be thrown but if it's mounted to a router, the error will be converted to a response object with the correct status code and headers.
|
|
321
|
+
better-call comes with a rpc client that can be used to call endpoints from the client. The client wraps over better-fetch so you can pass any options that are supported by better-fetch.
|
|
145
322
|
|
|
146
323
|
```ts
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
324
|
+
import { createClient } from "better-call/client";
|
|
325
|
+
import { router } from "@serve/router";
|
|
326
|
+
|
|
327
|
+
const client = createClient<typeof router>({
|
|
328
|
+
/**
|
|
329
|
+
* if you add custom path like `http://
|
|
330
|
+
* localhost:3000/api` make sure to add the
|
|
331
|
+
* custom path on the router config as well.
|
|
332
|
+
*/
|
|
333
|
+
baseURL: "http://localhost:3000"
|
|
334
|
+
});
|
|
335
|
+
const items = await client("/item", {
|
|
336
|
+
body: {
|
|
337
|
+
id: "123"
|
|
162
338
|
}
|
|
163
|
-
})
|
|
339
|
+
});
|
|
164
340
|
```
|
|
341
|
+
> You can also pass object that contains endpoints as a generic type to create client.
|
|
165
342
|
|
|
166
343
|
### Headers and Cookies
|
|
167
344
|
|
|
@@ -202,66 +379,7 @@ const createItem = createEndpoint("/item", {
|
|
|
202
379
|
})
|
|
203
380
|
```
|
|
204
381
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
### `path`
|
|
208
|
-
|
|
209
|
-
The path defines how the framework matches requests to endpoints. It can include parameters and wildcards that are extracted from the request and passed to the endpoint function.
|
|
210
|
-
|
|
211
|
-
1. **Path Parameters**:
|
|
212
|
-
- Use `:` to define a parameter in the path. For example, `/item/:id` will extract the `id` from the request URL and pass it to the endpoint function as part of the `params` object.
|
|
213
|
-
- Example: For a request to `/item/123`, the endpoint function will receive `{ id: '123' }`.
|
|
214
|
-
|
|
215
|
-
2. **Wildcards**:
|
|
216
|
-
- Use `*` or `**` to match any part of the path. When using a wildcard, the remaining path will be captured in the `params` object under the `_` property.
|
|
217
|
-
- Example: For a path `/item/*`, a request to `/item/abc/def` will pass `{ _: 'abc/def' }` to the endpoint function.
|
|
218
|
-
|
|
219
|
-
3. **Named Wildcards**:
|
|
220
|
-
- To capture the remaining path with a specific name, use `*:` followed by the desired name. The remaining path will be assigned to this named property.
|
|
221
|
-
- Example: For a path `/item/*:name`, a request to `/item/abc/def` will pass `{ name: 'abc/def' }` to the endpoint function.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
### `EndpointOptions`
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
type EndpointOptions = {
|
|
228
|
-
method?: METHOD | METHOD[]
|
|
229
|
-
body?: ZodSchema
|
|
230
|
-
query?: ZodSchema
|
|
231
|
-
params?: ZodSchema
|
|
232
|
-
requireHeaders?: boolean
|
|
233
|
-
requireRequest?: boolean
|
|
234
|
-
use?: Endpoint[]
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
- **method**: the endpoint options accept method which can be a method or an array of methods. If you pass an array of methods, the endpoint will match if the request method is one of the methods in the array. And method will be required when calling the endpoint even as a function.
|
|
239
|
-
|
|
240
|
-
- **body**: the body option accepts a zod schema and will validate the request body. If the request body doesn't match the schema, the endpoint will throw an error. If it's mounted to a router, it'll return a 400 error.
|
|
241
|
-
|
|
242
|
-
- **query**: the query option accepts a zod schema and will validate the request query. If the request query doesn't match the schema, the endpoint will throw an error. If it's mounted to a router, it'll return a 400 error.
|
|
243
|
-
|
|
244
|
-
- **params**: the params option accepts a zod schema and will validate the request params. The params are defined in the path and will be extracted from the request.
|
|
245
|
-
|
|
246
|
-
- **requireHeaders**: if true, the endpoint will throw an error if the request doesn't have headers. And even when you call the endpoint as a function, it will require headers to be passed in the context.
|
|
247
|
-
|
|
248
|
-
- **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.
|
|
249
|
-
|
|
250
|
-
- **use**: the use option accepts an array of endpoints and will be called before the endpoint is called.
|
|
251
|
-
|
|
252
|
-
### `EndpointFunction`
|
|
253
|
-
|
|
254
|
-
this is the function that will be invoked when the endpoint is called. It accepts a context object that contains the request, headers, body, query, params and other information.
|
|
255
|
-
|
|
256
|
-
It can return a response object, a string, a boolean, a number, an object with a status, body, headers and other properties or undefined.
|
|
257
|
-
|
|
258
|
-
If you return a response object, it will be returned as is even when it's mounted to a router.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
- **Context**: the context object contains the request, headers, body, query, params and a helper function to set headers, cookies and get cookies. If there is a middleware, the context will be extended with the middleware context.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
382
|
+
> other than normal cookies the ctx object also exposes signed cookies.
|
|
265
383
|
|
|
266
384
|
## License
|
|
267
385
|
MIT
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var o=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var a=(t,e)=>{for(var n in e)o(t,n,{get:e[n],enumerable:!0})},u=(t,e,n,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of p(e))!y.call(t,r)&&r!==n&&o(t,r,{get:()=>e[r],enumerable:!(s=d(e,r))||s.enumerable});return t};var x=t=>u(o({},"__esModule",{value:!0}),t);var R={};a(R,{createClient:()=>T});module.exports=x(R);var i=require("@better-fetch/fetch"),T=t=>{let e=(0,i.createFetch)(t);return async(n,...s)=>await e(n,{...s[0]})};0&&(module.exports={createClient});
|
|
2
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { Endpoint, Prettify } from \"./types\";\n\nimport { type BetterFetchOption, type BetterFetchResponse, createFetch } from \"@better-fetch/fetch\";\nimport type { Router } from \"./router\";\nimport type { HasRequiredKeys, UnionToIntersection } from \"./helper\";\n\ntype HasRequired<\n\tT extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = HasRequiredKeys<T> extends true\n\t? HasRequiredKeys<T[\"body\"]> extends false\n\t\t? HasRequiredKeys<T[\"query\"]> extends false\n\t\t\t? HasRequiredKeys<T[\"params\"]> extends false\n\t\t\t\t? false\n\t\t\t\t: true\n\t\t\t: true\n\t\t: true\n\t: true;\n\ntype InferContext<T> = T extends (ctx: infer Ctx) => any\n\t? Ctx extends object\n\t\t? Ctx\n\t\t: never\n\t: never;\n\nexport interface ClientOptions extends BetterFetchOption {\n\tbaseURL: string;\n}\n\ntype WithRequired<T, K> = T & {\n\t[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];\n};\n\nexport type RequiredOptionKeys<\n\tC extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = (undefined extends C[\"body\"]\n\t? {}\n\t: {\n\t\t\tbody: true;\n\t\t}) &\n\t(undefined extends C[\"query\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tquery: true;\n\t\t\t}) &\n\t(undefined extends C[\"params\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tparams: true;\n\t\t\t});\n\nexport const createClient = <R extends Router | Router[\"endpoints\"]>(options: ClientOptions) => {\n\tconst fetch = createFetch(options);\n\ttype API = R extends Router ? R[\"endpoints\"] : R;\n\ttype Options = API extends {\n\t\t[key: string]: infer T;\n\t}\n\t\t? T extends Endpoint\n\t\t\t? {\n\t\t\t\t\t[key in T[\"options\"][\"method\"] extends \"GET\"\n\t\t\t\t\t\t? T[\"path\"]\n\t\t\t\t\t\t: `@${T[\"options\"][\"method\"] extends string ? Lowercase<T[\"options\"][\"method\"]> : never}${T[\"path\"]}`]: T;\n\t\t\t\t}\n\t\t\t: {}\n\t\t: {};\n\n\ttype O = Prettify<UnionToIntersection<Options>>;\n\treturn async <OPT extends O, K extends keyof OPT, C extends InferContext<OPT[K]>>(\n\t\tpath: K,\n\t\t...options: HasRequired<C> extends true\n\t\t\t? [\n\t\t\t\t\tWithRequired<\n\t\t\t\t\t\tBetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t\t\t\t\t\tkeyof RequiredOptionKeys<C>\n\t\t\t\t\t>,\n\t\t\t\t]\n\t\t\t: [BetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>?]\n\t): Promise<\n\t\tBetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>>\n\t> => {\n\t\treturn (await fetch(path as string, {\n\t\t\t...options[0],\n\t\t})) as any;\n\t};\n};\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,IAAA,eAAAC,EAAAH,GAEA,IAAAI,EAA8E,+BAwDjEF,EAAwDG,GAA2B,CAC/F,IAAMC,KAAQ,eAAYD,CAAO,EAejC,MAAO,OACNE,KACGF,IAWK,MAAMC,EAAMC,EAAgB,CACnC,GAAGF,EAAQ,CAAC,CACb,CAAC,CAEH","names":["client_exports","__export","createClient","__toCommonJS","import_fetch","options","fetch","path"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { R as Router, U as UnionToIntersection, f as Endpoint, b as HasRequiredKeys } from './router-fNn8BCPr.cjs';
|
|
2
|
+
import { BetterFetchOption, BetterFetchResponse } from '@better-fetch/fetch';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
type HasRequired<T extends {
|
|
6
|
+
body?: any;
|
|
7
|
+
query?: any;
|
|
8
|
+
params?: any;
|
|
9
|
+
}> = HasRequiredKeys<T> extends true ? HasRequiredKeys<T["body"]> extends false ? HasRequiredKeys<T["query"]> extends false ? HasRequiredKeys<T["params"]> extends false ? false : true : true : true : true;
|
|
10
|
+
type InferContext<T> = T extends (ctx: infer Ctx) => any ? Ctx extends object ? Ctx : never : never;
|
|
11
|
+
interface ClientOptions extends BetterFetchOption {
|
|
12
|
+
baseURL: string;
|
|
13
|
+
}
|
|
14
|
+
type WithRequired<T, K> = T & {
|
|
15
|
+
[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];
|
|
16
|
+
};
|
|
17
|
+
type RequiredOptionKeys<C extends {
|
|
18
|
+
body?: any;
|
|
19
|
+
query?: any;
|
|
20
|
+
params?: any;
|
|
21
|
+
}> = (undefined extends C["body"] ? {} : {
|
|
22
|
+
body: true;
|
|
23
|
+
}) & (undefined extends C["query"] ? {} : {
|
|
24
|
+
query: true;
|
|
25
|
+
}) & (undefined extends C["params"] ? {} : {
|
|
26
|
+
params: true;
|
|
27
|
+
});
|
|
28
|
+
declare const createClient: <R extends Router | Router["endpoints"]>(options: ClientOptions) => <OPT extends UnionToIntersection<(R extends {
|
|
29
|
+
handler: (request: Request) => Promise<Response>;
|
|
30
|
+
endpoints: Record<string, Endpoint>;
|
|
31
|
+
} ? R["endpoints"] : R) extends {
|
|
32
|
+
[key: string]: infer T_1;
|
|
33
|
+
} ? T_1 extends Endpoint ? { [key_1 in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1; } : {} : {}> extends infer T ? { [key in keyof T]: UnionToIntersection<(R extends {
|
|
34
|
+
handler: (request: Request) => Promise<Response>;
|
|
35
|
+
endpoints: Record<string, Endpoint>;
|
|
36
|
+
} ? R["endpoints"] : R) extends {
|
|
37
|
+
[key: string]: infer T_1;
|
|
38
|
+
} ? T_1 extends Endpoint ? { [key_1 in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1; } : {} : {}>[key]; } : never, K extends keyof OPT, C extends InferContext<OPT[K]>>(path: K, ...options: HasRequired<C> extends true ? [WithRequired<BetterFetchOption<C["body"], C["query"], C["params"]>, keyof RequiredOptionKeys<C>>] : [BetterFetchOption<C["body"], C["query"], C["params"]>?]) => Promise<BetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>>>;
|
|
39
|
+
|
|
40
|
+
export { type ClientOptions, type RequiredOptionKeys, createClient };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { R as Router, U as UnionToIntersection, f as Endpoint, b as HasRequiredKeys } from './router-fNn8BCPr.js';
|
|
2
|
+
import { BetterFetchOption, BetterFetchResponse } from '@better-fetch/fetch';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
type HasRequired<T extends {
|
|
6
|
+
body?: any;
|
|
7
|
+
query?: any;
|
|
8
|
+
params?: any;
|
|
9
|
+
}> = HasRequiredKeys<T> extends true ? HasRequiredKeys<T["body"]> extends false ? HasRequiredKeys<T["query"]> extends false ? HasRequiredKeys<T["params"]> extends false ? false : true : true : true : true;
|
|
10
|
+
type InferContext<T> = T extends (ctx: infer Ctx) => any ? Ctx extends object ? Ctx : never : never;
|
|
11
|
+
interface ClientOptions extends BetterFetchOption {
|
|
12
|
+
baseURL: string;
|
|
13
|
+
}
|
|
14
|
+
type WithRequired<T, K> = T & {
|
|
15
|
+
[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];
|
|
16
|
+
};
|
|
17
|
+
type RequiredOptionKeys<C extends {
|
|
18
|
+
body?: any;
|
|
19
|
+
query?: any;
|
|
20
|
+
params?: any;
|
|
21
|
+
}> = (undefined extends C["body"] ? {} : {
|
|
22
|
+
body: true;
|
|
23
|
+
}) & (undefined extends C["query"] ? {} : {
|
|
24
|
+
query: true;
|
|
25
|
+
}) & (undefined extends C["params"] ? {} : {
|
|
26
|
+
params: true;
|
|
27
|
+
});
|
|
28
|
+
declare const createClient: <R extends Router | Router["endpoints"]>(options: ClientOptions) => <OPT extends UnionToIntersection<(R extends {
|
|
29
|
+
handler: (request: Request) => Promise<Response>;
|
|
30
|
+
endpoints: Record<string, Endpoint>;
|
|
31
|
+
} ? R["endpoints"] : R) extends {
|
|
32
|
+
[key: string]: infer T_1;
|
|
33
|
+
} ? T_1 extends Endpoint ? { [key_1 in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1; } : {} : {}> extends infer T ? { [key in keyof T]: UnionToIntersection<(R extends {
|
|
34
|
+
handler: (request: Request) => Promise<Response>;
|
|
35
|
+
endpoints: Record<string, Endpoint>;
|
|
36
|
+
} ? R["endpoints"] : R) extends {
|
|
37
|
+
[key: string]: infer T_1;
|
|
38
|
+
} ? T_1 extends Endpoint ? { [key_1 in T_1["options"]["method"] extends "GET" ? T_1["path"] : `@${T_1["options"]["method"] extends string ? Lowercase<T_1["options"]["method"]> : never}${T_1["path"]}`]: T_1; } : {} : {}>[key]; } : never, K extends keyof OPT, C extends InferContext<OPT[K]>>(path: K, ...options: HasRequired<C> extends true ? [WithRequired<BetterFetchOption<C["body"], C["query"], C["params"]>, keyof RequiredOptionKeys<C>>] : [BetterFetchOption<C["body"], C["query"], C["params"]>?]) => Promise<BetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>>>;
|
|
39
|
+
|
|
40
|
+
export { type ClientOptions, type RequiredOptionKeys, createClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { Endpoint, Prettify } from \"./types\";\n\nimport { type BetterFetchOption, type BetterFetchResponse, createFetch } from \"@better-fetch/fetch\";\nimport type { Router } from \"./router\";\nimport type { HasRequiredKeys, UnionToIntersection } from \"./helper\";\n\ntype HasRequired<\n\tT extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = HasRequiredKeys<T> extends true\n\t? HasRequiredKeys<T[\"body\"]> extends false\n\t\t? HasRequiredKeys<T[\"query\"]> extends false\n\t\t\t? HasRequiredKeys<T[\"params\"]> extends false\n\t\t\t\t? false\n\t\t\t\t: true\n\t\t\t: true\n\t\t: true\n\t: true;\n\ntype InferContext<T> = T extends (ctx: infer Ctx) => any\n\t? Ctx extends object\n\t\t? Ctx\n\t\t: never\n\t: never;\n\nexport interface ClientOptions extends BetterFetchOption {\n\tbaseURL: string;\n}\n\ntype WithRequired<T, K> = T & {\n\t[P in K extends string ? K : never]-?: T[P extends keyof T ? P : never];\n};\n\nexport type RequiredOptionKeys<\n\tC extends {\n\t\tbody?: any;\n\t\tquery?: any;\n\t\tparams?: any;\n\t},\n> = (undefined extends C[\"body\"]\n\t? {}\n\t: {\n\t\t\tbody: true;\n\t\t}) &\n\t(undefined extends C[\"query\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tquery: true;\n\t\t\t}) &\n\t(undefined extends C[\"params\"]\n\t\t? {}\n\t\t: {\n\t\t\t\tparams: true;\n\t\t\t});\n\nexport const createClient = <R extends Router | Router[\"endpoints\"]>(options: ClientOptions) => {\n\tconst fetch = createFetch(options);\n\ttype API = R extends Router ? R[\"endpoints\"] : R;\n\ttype Options = API extends {\n\t\t[key: string]: infer T;\n\t}\n\t\t? T extends Endpoint\n\t\t\t? {\n\t\t\t\t\t[key in T[\"options\"][\"method\"] extends \"GET\"\n\t\t\t\t\t\t? T[\"path\"]\n\t\t\t\t\t\t: `@${T[\"options\"][\"method\"] extends string ? Lowercase<T[\"options\"][\"method\"]> : never}${T[\"path\"]}`]: T;\n\t\t\t\t}\n\t\t\t: {}\n\t\t: {};\n\n\ttype O = Prettify<UnionToIntersection<Options>>;\n\treturn async <OPT extends O, K extends keyof OPT, C extends InferContext<OPT[K]>>(\n\t\tpath: K,\n\t\t...options: HasRequired<C> extends true\n\t\t\t? [\n\t\t\t\t\tWithRequired<\n\t\t\t\t\t\tBetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>,\n\t\t\t\t\t\tkeyof RequiredOptionKeys<C>\n\t\t\t\t\t>,\n\t\t\t\t]\n\t\t\t: [BetterFetchOption<C[\"body\"], C[\"query\"], C[\"params\"]>?]\n\t): Promise<\n\t\tBetterFetchResponse<Awaited<ReturnType<OPT[K] extends Endpoint ? OPT[K] : never>>>\n\t> => {\n\t\treturn (await fetch(path as string, {\n\t\t\t...options[0],\n\t\t})) as any;\n\t};\n};\n"],"mappings":"AAEA,OAA2D,eAAAA,MAAmB,sBAwDvE,IAAMC,EAAwDC,GAA2B,CAC/F,IAAMC,EAAQH,EAAYE,CAAO,EAejC,MAAO,OACNE,KACGF,IAWK,MAAMC,EAAMC,EAAgB,CACnC,GAAGF,EAAQ,CAAC,CACb,CAAC,CAEH","names":["createFetch","createClient","options","fetch","path"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var h=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var v=(r,e,t)=>e in r?h(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var K=(r,e)=>{for(var t in e)h(r,t,{get:e[t],enumerable:!0})},F=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Q(e))!W.call(r,s)&&s!==t&&h(r,s,{get:()=>e[s],enumerable:!(n=$(e,s))||n.enumerable});return r};var G=r=>F(h({},"__esModule",{value:!0}),r);var R=(r,e,t)=>v(r,typeof e!="symbol"?e+"":e,t);var te={};K(te,{APIError:()=>y,createEndpoint:()=>m,createEndpointCreator:()=>J,createMiddleware:()=>z,createMiddlewareCreator:()=>Z,createRouter:()=>ee,getBody:()=>T,shouldSerialize:()=>S,statusCode:()=>_});module.exports=G(te);var B=require("zod");var ne=require("zod");function z(r,e){if(typeof r=="function")return m("*",{method:"*"},r);if(!e)throw new Error("Middleware handler is required");return m("*",{...r,method:"*"},e)}var Z=()=>{function r(e,t){if(typeof e=="function")return m("*",{method:"*"},e);if(!t)throw new Error("Middleware handler is required");return m("*",{...e,method:"*"},t)}return r};var y=class extends Error{constructor(t,n){super(`API Error: ${t} ${n?.message??""}`,{cause:n});R(this,"status");R(this,"body");this.status=t,this.body=n??{},this.stack="",this.name="BetterCallAPIError"}};var C={name:"HMAC",hash:"SHA-256"},A=async r=>{let e=typeof r=="string"?new TextEncoder().encode(r):r;return await crypto.subtle.importKey("raw",e,C,!1,["sign","verify"])},Y=async(r,e)=>{let t=await A(e),n=await crypto.subtle.sign(C.name,t,new TextEncoder().encode(r));return btoa(String.fromCharCode(...new Uint8Array(n)))},V=async(r,e,t)=>{try{let n=atob(r),s=new Uint8Array(n.length);for(let i=0,o=n.length;i<o;i++)s[i]=n.charCodeAt(i);return await crypto.subtle.verify(C,t,s,new TextEncoder().encode(e))}catch{return!1}},j=/^[\w!#$%&'*.^`|~+-]+$/,X=/^[ !#-:<-[\]-~]*$/,P=(r,e)=>r.trim().split(";").reduce((n,s)=>{s=s.trim();let i=s.indexOf("=");if(i===-1)return n;let o=s.substring(0,i).trim();if(e&&e!==o||!j.test(o))return n;let p=s.substring(i+1).trim();return p.startsWith('"')&&p.endsWith('"')&&(p=p.slice(1,-1)),X.test(p)&&(n[o]=decodeURIComponent(p)),n},{}),H=async(r,e,t)=>{let n={},s=await A(e);for(let[i,o]of Object.entries(P(r,t))){let p=o.lastIndexOf(".");if(p<1)continue;let a=o.substring(0,p),d=o.substring(p+1);if(d.length!==44||!d.endsWith("="))continue;let u=await V(d,a,s);n[i]=u?a:!1}return n},N=(r,e,t={})=>{let n=`${r}=${e}`;if(r.startsWith("__Secure-")&&!t.secure)throw new Error("__Secure- Cookie must have Secure attributes");if(r.startsWith("__Host-")){if(!t.secure)throw new Error("__Host- Cookie must have Secure attributes");if(t.path!=="/")throw new Error('__Host- Cookie must have Path attributes with "/"');if(t.domain)throw new Error("__Host- Cookie must not have Domain attributes")}if(t&&typeof t.maxAge=="number"&&t.maxAge>=0){if(t.maxAge>3456e4)throw new Error("Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.");n+=`; Max-Age=${Math.floor(t.maxAge)}`}if(t.domain&&t.prefix!=="host"&&(n+=`; Domain=${t.domain}`),t.path&&(n+=`; Path=${t.path}`),t.expires){if(t.expires.getTime()-Date.now()>3456e7)throw new Error("Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.");n+=`; Expires=${t.expires.toUTCString()}`}if(t.httpOnly&&(n+="; HttpOnly"),t.secure&&(n+="; Secure"),t.sameSite&&(n+=`; SameSite=${t.sameSite.charAt(0).toUpperCase()+t.sameSite.slice(1)}`),t.partitioned){if(!t.secure)throw new Error("Partitioned Cookie must have Secure attributes");n+="; Partitioned"}return n},O=(r,e,t)=>(e=encodeURIComponent(e),N(r,e,t)),l=async(r,e,t,n={})=>{let s=await Y(e,t);return e=`${e}.${s}`,e=encodeURIComponent(e),N(r,e,n)};var D=(r,e,t)=>{if(!r)return;let n=e;if(t==="secure")n="__Secure-"+e;else if(t==="host")n="__Host-"+e;else return;return P(r,n)[n]},U=(r,e,t,n)=>{let s;n?.prefix==="secure"?s=O("__Secure-"+e,t,{path:"/",...n,secure:!0}):n?.prefix==="host"?s=O("__Host-"+e,t,{...n,path:"/",secure:!0,domain:void 0}):s=O(e,t,{path:"/",...n}),r.append("Set-Cookie",s)},M=async(r,e,t,n,s)=>{let i;s?.prefix==="secure"?i=await l("__Secure-"+e,t,n,{path:"/",...s,secure:!0}):s?.prefix==="host"?i=await l("__Host-"+e,t,n,{...s,path:"/",secure:!0,domain:void 0}):i=await l(e,t,n,{path:"/",...s}),r.append("Set-Cookie",i)},q=async(r,e,t,n)=>{let s=r.get("Cookie");if(!s)return;let i=t;return n==="secure"?i="__Secure-"+t:n==="host"&&(i="__Host-"+t),(await H(s,e,i))[i]};function J(){return(r,e,t)=>m(r,e,t)}function m(r,e,t){let n=new Headers,s=async(...i)=>{let o={setHeader(a,d){n.set(a,d)},setCookie(a,d,u){U(n,a,d,u)},getCookie(a,d){let u=i[0]?.headers;return D(u?.get("Cookie")||"",a,d)},getSignedCookie(a,d,u){let c=i[0]?.headers;if(!c)throw new TypeError("Headers are required");return q(c,d,a,u)},async setSignedCookie(a,d,u,c){await M(n,a,d,u,c)},...i[0]||{},context:{}};if(e.use?.length)for(let a of e.use){let d=await a(o),u=d.options?.body?d.options.body.parse(o.body):void 0;d&&(o={...o,body:u?{...u,...o.body}:o.body,context:{...o.context||{},...d}})}try{let a=e.body?e.body.parse(o.body):o.body;o={...o,body:a?{...a,...o.body}:o.body},o.query=e.query?e.query.parse(o.query):o.query}catch(a){throw a instanceof B.ZodError?new y("BAD_REQUEST",{message:a.message,details:a.errors}):a}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 t(o)};return s.path=r,s.options=e,s.method=e.method,s.headers=n,s}var E=require("rou3");async function T(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 t=await r.formData(),n={};return t.forEach((s,i)=>{n[i]=s.toString()}),n}if(e.includes("multipart/form-data")){let t=await r.formData(),n={};return t.forEach((s,i)=>{n[i]=s}),n}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 S(r){return typeof r=="object"&&r!==null&&!(r instanceof Blob)&&!(r instanceof FormData)}var _={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 ee=(r,e)=>{let t=Object.values(r),n=(0,E.createRouter)();for(let o of t)if(Array.isArray(o.options?.method))for(let p of o.options.method)(0,E.addRoute)(n,p,o.path,o);else(0,E.addRoute)(n,o.options.method,o.path,o);let s=(0,E.createRouter)();for(let o of e?.routerMiddleware||[])(0,E.addRoute)(s,"*",o.path,o.middleware);return{handler:async o=>{let p=new URL(o.url),a=p.pathname;e?.basePath&&(a=a.split(e.basePath)[1]);let d=o.method,u=(0,E.findRoute)(n,d,a),c=u?.data,g=await T(o),w=o.headers,I=Object.fromEntries(p.searchParams),k=(0,E.findRoute)(s,"*",a)?.data;if(!c)return new Response(null,{status:404,statusText:"Not Found"});try{let f={};if(k){let b=await k({path:a,method:d,headers:w,params:u?.params,request:o,body:g,query:I,...e?.extraContext});b&&(f={...b,...f})}let x=await c({path:a,method:d,headers:w,params:u?.params,request:o,body:g,query:I,...f,...e?.extraContext});if(x instanceof Response)return x;let L=S(x)?JSON.stringify(x):x;return new Response(L,{headers:c.headers})}catch(f){if(e?.onError){let x=await e.onError(f);if(x instanceof Response)return x}if(f instanceof y)return new Response(f.body?JSON.stringify(f.body):null,{status:_[f.status],statusText:f.status,headers:c.headers});if(e?.throwError)throw f;return new Response(null,{status:500,statusText:"Internal Server Error"})}},endpoints:r}};var Ce=require("zod");0&&(module.exports={APIError,createEndpoint,createEndpointCreator,createMiddleware,createMiddlewareCreator,createRouter,getBody,shouldSerialize,statusCode});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|