better-call 1.0.0-beta.5 → 1.0.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 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 new APIError("Bad Request", {
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 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.
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 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.
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. And even when you call the endpoint as a function, it will require headers to be passed in the context.
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. And even when you call the endpoint as a function, it will require a request to be passed in the context.
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 boolean, a number, an object with a status, body, headers and other properties or undefined.
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,92 @@ 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
+
382
489
  ## License
383
490
  MIT
package/dist/client.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BetterFetchOption, BetterFetchResponse } from '@better-fetch/fetch';
2
- import { j as Router, W as UnionToIntersection, b as Endpoint, T as HasRequiredKeys } from './router-G7pIJBUB.cjs';
2
+ import { j as Router, W as UnionToIntersection, b as Endpoint, T as HasRequiredKeys } from './router-Bpb8tT1Y.cjs';
3
3
 
4
4
  type HasRequired<T extends {
5
5
  body?: any;
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BetterFetchOption, BetterFetchResponse } from '@better-fetch/fetch';
2
- import { j as Router, W as UnionToIntersection, b as Endpoint, T as HasRequiredKeys } from './router-G7pIJBUB.js';
2
+ import { j as Router, W as UnionToIntersection, b as Endpoint, T as HasRequiredKeys } from './router-Bpb8tT1Y.js';
3
3
 
4
4
  type HasRequired<T extends {
5
5
  body?: any;
package/dist/index.cjs CHANGED
@@ -4843,7 +4843,7 @@ var createRouter = (endpoints, config) => {
4843
4843
  path,
4844
4844
  method: request.method,
4845
4845
  headers: request.headers,
4846
- params: route.params,
4846
+ params: route.params ? JSON.parse(JSON.stringify(route.params)) : {},
4847
4847
  request,
4848
4848
  body: await getBody(handler.options.cloneRequest ? request.clone() : request),
4849
4849
  query: Object.fromEntries(url.searchParams),