better-call 1.0.0-beta.6 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -32
- package/dist/index.cjs +1 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -63,9 +63,12 @@ Then you can use the rpc client to call the endpoints on client.
|
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
65
|
//client.ts
|
|
66
|
+
import type { router } from "./router" // import router type
|
|
66
67
|
import { createClient } from "better-call/client";
|
|
67
68
|
|
|
68
|
-
const client = createClient<typeof router>(
|
|
69
|
+
const client = createClient<typeof router>({
|
|
70
|
+
baseURL: "http://localhost:3000"
|
|
71
|
+
});
|
|
69
72
|
const items = await client("/item", {
|
|
70
73
|
body: {
|
|
71
74
|
id: "123"
|
|
@@ -85,7 +88,29 @@ const createItem = createEndpoint("/item", {
|
|
|
85
88
|
})
|
|
86
89
|
}, async (ctx) => {
|
|
87
90
|
if(ctx.body.id === "123") {
|
|
88
|
-
throw
|
|
91
|
+
throw ctx.error("Bad Request", {
|
|
92
|
+
message: "Id is not allowed"
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
item: {
|
|
97
|
+
id: ctx.body.id
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can also instead throw using a status code:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const createItem = createEndpoint("/item", {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: z.object({
|
|
109
|
+
id: z.string()
|
|
110
|
+
})
|
|
111
|
+
}, async (ctx) => {
|
|
112
|
+
if(ctx.body.id === "123") {
|
|
113
|
+
throw ctx.error(400, {
|
|
89
114
|
message: "Id is not allowed"
|
|
90
115
|
})
|
|
91
116
|
}
|
|
@@ -130,9 +155,10 @@ const endpoint = createEndpoint("/item/**:name", {
|
|
|
130
155
|
ctx.params.name
|
|
131
156
|
})
|
|
132
157
|
```
|
|
158
|
+
|
|
133
159
|
#### Body Schema
|
|
134
160
|
|
|
135
|
-
The `body` option accepts a
|
|
161
|
+
The `body` option accepts a standard 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.
|
|
136
162
|
|
|
137
163
|
```ts
|
|
138
164
|
const createItem = createEndpoint("/item", {
|
|
@@ -151,7 +177,7 @@ const createItem = createEndpoint("/item", {
|
|
|
151
177
|
|
|
152
178
|
#### Query Schema
|
|
153
179
|
|
|
154
|
-
The `query` option accepts a
|
|
180
|
+
The `query` option accepts a standard 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.
|
|
155
181
|
|
|
156
182
|
```ts
|
|
157
183
|
const createItem = createEndpoint("/item", {
|
|
@@ -170,7 +196,7 @@ const createItem = createEndpoint("/item", {
|
|
|
170
196
|
|
|
171
197
|
#### Require Headers
|
|
172
198
|
|
|
173
|
-
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.
|
|
199
|
+
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. This is only useful when you call the endpoint as a function.
|
|
174
200
|
|
|
175
201
|
```ts
|
|
176
202
|
const createItem = createEndpoint("/item", {
|
|
@@ -190,7 +216,7 @@ createItem({
|
|
|
190
216
|
|
|
191
217
|
#### Require Request
|
|
192
218
|
|
|
193
|
-
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.
|
|
219
|
+
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. This is only useful when you call the endpoint as a function.
|
|
194
220
|
|
|
195
221
|
```ts
|
|
196
222
|
const createItem = createEndpoint("/item", {
|
|
@@ -209,14 +235,11 @@ createItem({
|
|
|
209
235
|
})
|
|
210
236
|
```
|
|
211
237
|
|
|
212
|
-
|
|
213
238
|
### Handler
|
|
214
239
|
|
|
215
240
|
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.
|
|
216
241
|
|
|
217
|
-
It can return a response object, a string, a
|
|
218
|
-
|
|
219
|
-
If you return a response object, it will be returned as is even when it's mounted to a router.
|
|
242
|
+
It can return a response object, a string, a number, a boolean, an object or an array.
|
|
220
243
|
|
|
221
244
|
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.
|
|
222
245
|
|
|
@@ -229,11 +252,14 @@ Endpoints can use middleware by passing the `use` option to the endpoint. To cre
|
|
|
229
252
|
If you return a context object from the middleware, it will be available in the endpoint context.
|
|
230
253
|
|
|
231
254
|
```ts
|
|
255
|
+
import { createMiddleware, createEndpoint } from "better-call";
|
|
256
|
+
|
|
232
257
|
const middleware = createMiddleware(async (ctx) => {
|
|
233
258
|
return {
|
|
234
259
|
name: "hello"
|
|
235
260
|
}
|
|
236
261
|
})
|
|
262
|
+
|
|
237
263
|
const endpoint = createEndpoint("/", {
|
|
238
264
|
method: "GET",
|
|
239
265
|
use: [middleware],
|
|
@@ -243,28 +269,6 @@ const endpoint = createEndpoint("/", {
|
|
|
243
269
|
})
|
|
244
270
|
```
|
|
245
271
|
|
|
246
|
-
You can also pass an options object to the middleware and a handler function.
|
|
247
|
-
|
|
248
|
-
```ts
|
|
249
|
-
const middleware = createMiddleware({
|
|
250
|
-
body: z.object({
|
|
251
|
-
name: z.string()
|
|
252
|
-
})
|
|
253
|
-
}, async (ctx) => {
|
|
254
|
-
return {
|
|
255
|
-
name: "hello"
|
|
256
|
-
}
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
const endpoint = createEndpoint("/", {
|
|
260
|
-
method: "GET",
|
|
261
|
-
use: [middleware],
|
|
262
|
-
}, async (ctx) => {
|
|
263
|
-
//the body will also contain the middleware body
|
|
264
|
-
ctx.body
|
|
265
|
-
})
|
|
266
|
-
```
|
|
267
|
-
|
|
268
272
|
### Router
|
|
269
273
|
|
|
270
274
|
You can create a router by calling `createRouter` and passing it an array of endpoints. It returns a router object that has a `handler` method that can be used to serve the endpoints.
|
|
@@ -314,6 +318,22 @@ const router = createRouter({
|
|
|
314
318
|
|
|
315
319
|
**throwError**: If true, the router will throw an error if an error occurs in the middleware or the endpoint.
|
|
316
320
|
|
|
321
|
+
#### Node Adapter
|
|
322
|
+
|
|
323
|
+
You can use the node adapter to serve the router with node http server.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { createRouter } from "better-call";
|
|
327
|
+
import { toNodeHandler } from "better-call/node";
|
|
328
|
+
import { createItem } from "./item";
|
|
329
|
+
import http from "http";
|
|
330
|
+
|
|
331
|
+
const router = createRouter({
|
|
332
|
+
createItem
|
|
333
|
+
})
|
|
334
|
+
const server = http.createServer(toNodeHandler(router.handler))
|
|
335
|
+
```
|
|
336
|
+
|
|
317
337
|
### RPC Client
|
|
318
338
|
|
|
319
339
|
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.
|
|
@@ -379,5 +399,113 @@ const createItem = createEndpoint("/item", {
|
|
|
379
399
|
|
|
380
400
|
> other than normal cookies the ctx object also exposes signed cookies.
|
|
381
401
|
|
|
402
|
+
### Endpoint Creator
|
|
403
|
+
|
|
404
|
+
You can create an endpoint creator by calling `createEndpoint.create` that will let you apply set of middlewares to all the endpoints created by the creator.
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
const dbMiddleware = createMiddleware(async (ctx) => {
|
|
408
|
+
return {
|
|
409
|
+
db: new Database()
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
const create = createEndpoint.create({
|
|
413
|
+
use: [dbMiddleware]
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
const createItem = create("/item", {
|
|
417
|
+
method: "POST",
|
|
418
|
+
body: z.object({
|
|
419
|
+
id: z.string()
|
|
420
|
+
})
|
|
421
|
+
}, async (ctx) => {
|
|
422
|
+
await ctx.context.db.save(ctx.body)
|
|
423
|
+
})
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Open API
|
|
427
|
+
|
|
428
|
+
Better Call by default generate open api schema for the endpoints and exposes it on `/api/reference` path using scalar. By default, if you're using `zod` it'll be able to generate `body` and `query` schema.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { createEndpoint, createRouter } from "better-call"
|
|
432
|
+
|
|
433
|
+
const createItem = createEndpoint("/item/:id", {
|
|
434
|
+
method: "GET",
|
|
435
|
+
query: z.object({
|
|
436
|
+
id: z.string({
|
|
437
|
+
description: "The id of the item"
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
}, async (ctx) => {
|
|
441
|
+
return {
|
|
442
|
+
item: {
|
|
443
|
+
id: ctx.query.id
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
But you can also define custom schema for the open api schema.
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { createEndpoint, createRouter } from "better-call"
|
|
453
|
+
|
|
454
|
+
const createItem = createEndpoint("/item/:id", {
|
|
455
|
+
method: "GET",
|
|
456
|
+
query: z.object({
|
|
457
|
+
id: z.string({
|
|
458
|
+
description: "The id of the item"
|
|
459
|
+
})
|
|
460
|
+
}),
|
|
461
|
+
metadata: {
|
|
462
|
+
openAPI: {
|
|
463
|
+
requestBody: {
|
|
464
|
+
content: {
|
|
465
|
+
"application/json": {
|
|
466
|
+
schema: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: {
|
|
469
|
+
id: {
|
|
470
|
+
type: "string",
|
|
471
|
+
description: "The id of the item"
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}, async (ctx) => {
|
|
481
|
+
return {
|
|
482
|
+
item: {
|
|
483
|
+
id: ctx.query.id
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### Configuration
|
|
490
|
+
|
|
491
|
+
You can configure the open api schema by passing the `openAPI` option to the router.
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
const router = createRouter({
|
|
495
|
+
createItem
|
|
496
|
+
}, {
|
|
497
|
+
openAPI: {
|
|
498
|
+
disabled: false, //default false
|
|
499
|
+
path: "/api/reference", //default /api/reference
|
|
500
|
+
scalar: {
|
|
501
|
+
title: "My API",
|
|
502
|
+
version: "1.0.0",
|
|
503
|
+
description: "My API Description",
|
|
504
|
+
theme: "dark" //default saturn
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
```
|
|
509
|
+
|
|
382
510
|
## License
|
|
383
511
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -151,10 +151,7 @@ function toResponse(data, init) {
|
|
|
151
151
|
return toResponse(data.body, {
|
|
152
152
|
status: data.statusCode,
|
|
153
153
|
statusText: data.status.toString(),
|
|
154
|
-
headers:
|
|
155
|
-
...data.headers instanceof Headers ? Object.fromEntries(data.headers.entries()) : data?.headers,
|
|
156
|
-
...init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers
|
|
157
|
-
}
|
|
154
|
+
headers: init?.headers || data.headers
|
|
158
155
|
});
|
|
159
156
|
}
|
|
160
157
|
let body = data;
|