@sugardarius/anzen 0.1.2 โ 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 +344 -8
- package/dist/index.d.cts +24 -5
- package/dist/index.d.ts +24 -5
- package/package.json +13 -10
package/README.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
## [Unreleased yet]
|
|
2
|
-
|
|
3
1
|
A flexible, framework validation agnostic, type-safe factory for creating Next.JS App Router route handlers.
|
|
4
2
|
|
|
5
3
|
- ๐ง Framework validation agnostic, use a validation library of your choice supporting [Standard Schema](https://standardschema.dev/).
|
|
@@ -7,6 +5,7 @@ A flexible, framework validation agnostic, type-safe factory for creating Next.J
|
|
|
7
5
|
- ๐งน Clean and flexible API.
|
|
8
6
|
- ๐ Type-safe.
|
|
9
7
|
- ๐ฑ Dependency free.
|
|
8
|
+
- ๐ชถ Less than 100kB unpacked.
|
|
10
9
|
|
|
11
10
|
## Install
|
|
12
11
|
|
|
@@ -17,10 +16,11 @@ npm i @sugardarius/anzen
|
|
|
17
16
|
## Usage
|
|
18
17
|
|
|
19
18
|
```tsx
|
|
19
|
+
import { object, string, number } from 'decoders'
|
|
20
20
|
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
21
21
|
import { auth } from '~/lib/auth'
|
|
22
22
|
|
|
23
|
-
export const
|
|
23
|
+
export const POST = createSafeRouteHandler(
|
|
24
24
|
{
|
|
25
25
|
authorize: async ({ req }) => {
|
|
26
26
|
const session = await auth.getSession(req)
|
|
@@ -30,9 +30,13 @@ export const GET = createSafeRouteHandler(
|
|
|
30
30
|
|
|
31
31
|
return { user: session.user }
|
|
32
32
|
},
|
|
33
|
+
body: object({
|
|
34
|
+
foo: string,
|
|
35
|
+
bar: number,
|
|
36
|
+
}),
|
|
33
37
|
},
|
|
34
|
-
async ({ auth }, req): Promise<Response> => {
|
|
35
|
-
return Response.json({ user: auth.user }, { status: 200 })
|
|
38
|
+
async ({ auth, body }, req): Promise<Response> => {
|
|
39
|
+
return Response.json({ user: auth.user, body }, { status: 200 })
|
|
36
40
|
}
|
|
37
41
|
)
|
|
38
42
|
```
|
|
@@ -41,11 +45,13 @@ The example above shows how to use the factory to authorize your requests.
|
|
|
41
45
|
|
|
42
46
|
## Framework validation agnostic
|
|
43
47
|
|
|
44
|
-
By design the factory is framework validation agnostic ๐. When doing your validations you can use whatever you want as framework validation as long as it implements the [Standard Schema](https://github.com/standard-schema/standard-schema) common interface. You can use your favorite validation library like [Zod](https://zod.dev/) or [decoders](https://decoders.cc/).
|
|
48
|
+
By design the factory is framework validation agnostic ๐. When doing your validations you can use whatever you want as framework validation as long as it implements the [Standard Schema](https://github.com/standard-schema/standard-schema) common interface. You can use your favorite validation library like [Zod](https://zod.dev/), [Validbot](https://valibot.dev/) or [decoders](https://decoders.cc/).
|
|
45
49
|
|
|
46
50
|
```tsx
|
|
51
|
+
// (POST) /app/api/races/[id]/route.ts
|
|
47
52
|
import { z } from 'zod'
|
|
48
53
|
import { object, string, number } from 'decoders'
|
|
54
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
49
55
|
|
|
50
56
|
export const POST = createSafeRouteHandler(
|
|
51
57
|
{
|
|
@@ -57,8 +63,8 @@ export const POST = createSafeRouteHandler(
|
|
|
57
63
|
name: string,
|
|
58
64
|
}),
|
|
59
65
|
},
|
|
60
|
-
async ({ body }) => {
|
|
61
|
-
return Response.json({ body })
|
|
66
|
+
async ({ segments, body }) => {
|
|
67
|
+
return Response.json({ segments, body })
|
|
62
68
|
}
|
|
63
69
|
)
|
|
64
70
|
```
|
|
@@ -73,6 +79,318 @@ If you define an async validation then the route handler will throw an error.
|
|
|
73
79
|
|
|
74
80
|
Check the API and the available options to configure the factory as you wish.
|
|
75
81
|
|
|
82
|
+
### Function signature
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import {
|
|
86
|
+
type CreateSafeRouteHandlerOptions,
|
|
87
|
+
type SafeRouteHandlerContext,
|
|
88
|
+
createSafeRouteHandler
|
|
89
|
+
} from '@sugardarius/anzen'
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns a Next.js API route handler function.
|
|
93
|
+
*/
|
|
94
|
+
export const VERB = createSafeRouteHandler(
|
|
95
|
+
/**
|
|
96
|
+
* Options to configure the route handler
|
|
97
|
+
*/
|
|
98
|
+
options: CreateSafeRouteHandlerOptions,
|
|
99
|
+
/**
|
|
100
|
+
* The route handler function.
|
|
101
|
+
*/
|
|
102
|
+
async (
|
|
103
|
+
/**
|
|
104
|
+
* Context object providing:
|
|
105
|
+
* auth context
|
|
106
|
+
* validated segments, search params, body and form data
|
|
107
|
+
*/
|
|
108
|
+
ctx: SafeRouteHandlerContext,
|
|
109
|
+
/**
|
|
110
|
+
* Original request
|
|
111
|
+
*/
|
|
112
|
+
req: Request
|
|
113
|
+
): Promise<Response> => Response.json({}))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Base options
|
|
117
|
+
|
|
118
|
+
When creating a safe route handler you can use a bunch of options for helping you achieve different tasks ๐๐ป
|
|
119
|
+
|
|
120
|
+
#### `id?: string`
|
|
121
|
+
|
|
122
|
+
Used for logging in development or when the `debug` option is enabled. You can also use it to add extra logging or monitoring.
|
|
123
|
+
By default the id is set to `[unknown:route:handler]`
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
export const POST = createSafeRouteHandler(
|
|
127
|
+
{
|
|
128
|
+
id: 'my-safe-route-handler',
|
|
129
|
+
},
|
|
130
|
+
async ({ id }) => {
|
|
131
|
+
return Response.json({ id })
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `authorize?: AuthFunction<AC>`
|
|
137
|
+
|
|
138
|
+
Function to use to authorize the request. By default it always authorize the request.
|
|
139
|
+
|
|
140
|
+
Returns a response when the request is not authorized.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
144
|
+
import { auth } from '~/lib/auth'
|
|
145
|
+
|
|
146
|
+
export const GET = createSafeRouteHandler(
|
|
147
|
+
{
|
|
148
|
+
authorize: async ({ req, url }) => {
|
|
149
|
+
console.log('url', url)
|
|
150
|
+
const session = await auth.getSession(req)
|
|
151
|
+
if (!session) {
|
|
152
|
+
return new Response(null, { status: 401 })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { user: session.user }
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
async ({ auth, body }, req): Promise<Response> => {
|
|
159
|
+
return Response.json({ user: auth.user }, { status: 200 })
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### `onErrorResponse?: (err: unknown) => Awaitable<Response>`
|
|
165
|
+
|
|
166
|
+
Callback triggered when the request fails.
|
|
167
|
+
By default it returns a simple `500` response and the error is logged into the console.
|
|
168
|
+
|
|
169
|
+
Use it if your handler use custom errors and you want to manage them properly by returning a proper response.
|
|
170
|
+
|
|
171
|
+
You can read more about it under the [Error handling](#error-handling) section.
|
|
172
|
+
|
|
173
|
+
#### `debug?: boolean`
|
|
174
|
+
|
|
175
|
+
Use this options to enable debug mode. It will add logs in the handler to help you debug the request.
|
|
176
|
+
|
|
177
|
+
By default it's set to `false` for production builds.
|
|
178
|
+
In development builds, it will be `true` if `NODE_ENV` is not set to `production`.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
182
|
+
|
|
183
|
+
export const GET = createSafeRouteHandler({ debug: true }, async () => {
|
|
184
|
+
return new Response(null, { status: 200 })
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Route handler options
|
|
189
|
+
|
|
190
|
+
You can configure route handler options to validation using a validation library dynamic route segments, URL query parameters, request json body or request form data body ๐๐ป
|
|
191
|
+
|
|
192
|
+
#### `segments?: TSegments`
|
|
193
|
+
|
|
194
|
+
[Dynamic route segments](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#dynamic-route-segments) used for the route handler path. By design it will handle if the segments are a `Promise` or not.
|
|
195
|
+
|
|
196
|
+
Please note the expected input is a `StandardSchemaDictionary`.
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { z } from 'zod'
|
|
200
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
201
|
+
|
|
202
|
+
export const GET = createSafeRouteHandler(
|
|
203
|
+
{
|
|
204
|
+
segments: {
|
|
205
|
+
accountId: z.string(),
|
|
206
|
+
projectId: z.string().optional(),
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
async ({ segments }) => {
|
|
210
|
+
return Response.json({ segments })
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### `onSegmentsValidationErrorResponse?: OnValidationErrorResponse`
|
|
216
|
+
|
|
217
|
+
Callback triggered when dynamic segments validations returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { z } from 'zod'
|
|
221
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
222
|
+
|
|
223
|
+
export const GET = createSafeRouteHandler(
|
|
224
|
+
{
|
|
225
|
+
segments: {
|
|
226
|
+
accountId: z.string(),
|
|
227
|
+
projectId: z.string().optional(),
|
|
228
|
+
},
|
|
229
|
+
onSegmentsValidationErrorResponse: (issues) => {
|
|
230
|
+
return Response.json({ issues }, { status: 400 })
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
async ({ segments }) => {
|
|
234
|
+
return Response.json({ segments })
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `searchParams?: TSearchParams`
|
|
240
|
+
|
|
241
|
+
Search params used in the route.
|
|
242
|
+
|
|
243
|
+
Please note the expected input is a `StandardSchemaDictionary`.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import { string, numeric, optional } from 'decoders'
|
|
247
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
248
|
+
|
|
249
|
+
export const GET = createSafeRouteHandler(
|
|
250
|
+
{
|
|
251
|
+
searchParams: {
|
|
252
|
+
query: string,
|
|
253
|
+
page: optional(numeric),
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
async ({ searchParams }) => {
|
|
257
|
+
return Response.json({ searchParams })
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### `onSearchParamsValidationErrorResponse?: OnValidationErrorResponse`
|
|
263
|
+
|
|
264
|
+
Callback triggered when search params validations returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { string, numeric, optional } from 'decoders'
|
|
268
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
269
|
+
|
|
270
|
+
export const GET = createSafeRouteHandler(
|
|
271
|
+
{
|
|
272
|
+
searchParams: {
|
|
273
|
+
query: string,
|
|
274
|
+
page: optional(numeric),
|
|
275
|
+
},
|
|
276
|
+
onSearchParamsValidationErrorResponse: (issues) => {
|
|
277
|
+
return Response.json({ issues }, { status: 400 })
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
async ({ searchParams }) => {
|
|
281
|
+
return Response.json({ searchParams })
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `body?: TBody`
|
|
287
|
+
|
|
288
|
+
Request body.
|
|
289
|
+
|
|
290
|
+
Returns a `405` response if the request method is not `POST`, 'PUT' or 'PATCH'.
|
|
291
|
+
|
|
292
|
+
Returns a `415`response if the request does not explicitly set the `Content-Type` to `application/json`.
|
|
293
|
+
|
|
294
|
+
Please note the body is parsed as JSON, so it must be a valid JSON object. Body shouldn't be used with `formData` at the same time. They are **exclusive**.
|
|
295
|
+
|
|
296
|
+
Why making the distinction? `formData` is used as a `StandardSchemaDictionary` whereas `body` is used as a `StandardSchemaV1`.
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import { z } from 'zod'
|
|
300
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
301
|
+
|
|
302
|
+
export const POST = createSafeRouteHandler(
|
|
303
|
+
{
|
|
304
|
+
body: z.object({
|
|
305
|
+
name: z.string(),
|
|
306
|
+
model: z.string(),
|
|
307
|
+
apiKey: z.string(),
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
async ({ body }) => {
|
|
311
|
+
return Response.json({ body })
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### `onBodyValidationErrorResponse?: OnValidationErrorResponse`
|
|
317
|
+
|
|
318
|
+
Callback triggered when body validation returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
import { z } from 'zod'
|
|
322
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
323
|
+
|
|
324
|
+
export const POST = createSafeRouteHandler(
|
|
325
|
+
{
|
|
326
|
+
body: z.object({
|
|
327
|
+
name: z.string(),
|
|
328
|
+
model: z.string(),
|
|
329
|
+
apiKey: z.string(),
|
|
330
|
+
}),
|
|
331
|
+
onBodyValidationErrorResponse: (issues) => {
|
|
332
|
+
return Response.json({ issues }, { status: 400 })
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
async ({ body }) => {
|
|
336
|
+
return Response.json({ body })
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### `formData?: TFormData`
|
|
342
|
+
|
|
343
|
+
Request form data.
|
|
344
|
+
|
|
345
|
+
Returns a `405` response if the request method is not `POST`, 'PUT' or 'PATCH'.
|
|
346
|
+
|
|
347
|
+
Returns a `415`response if the request does not explicitly set the `Content-Type` to `multipart/form-data` or to `application/x-www-form-urlencoded`.
|
|
348
|
+
|
|
349
|
+
Please note formData shouldn't be used with `body` at the same time. They are **exclusive**.
|
|
350
|
+
|
|
351
|
+
Why making the distinction? `formData` is used as a `StandardSchemaDictionary` whereas `body` is used as a `StandardSchemaV1`.
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { z } from 'zod'
|
|
355
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
356
|
+
|
|
357
|
+
export const POST = createSafeRouteHandler(
|
|
358
|
+
{
|
|
359
|
+
formData: {
|
|
360
|
+
id: z.string(),
|
|
361
|
+
message: z.string(),
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
async ({ formData }) => {
|
|
365
|
+
return Response.json({ formData })
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
#### `onFormDataValidationErrorResponse?: OnValidationErrorResponse`
|
|
371
|
+
|
|
372
|
+
Callback triggered when form data validation returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
import { z } from 'zod'
|
|
376
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
377
|
+
|
|
378
|
+
export const POST = createSafeRouteHandler(
|
|
379
|
+
{
|
|
380
|
+
formData: {
|
|
381
|
+
id: z.string(),
|
|
382
|
+
message: z.string(),
|
|
383
|
+
},
|
|
384
|
+
onFormDataValidationErrorResponse: (issues) => {
|
|
385
|
+
return Response.json({ issues }, { status: 400 })
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
async ({ formData }) => {
|
|
389
|
+
return Response.json({ formData })
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
```
|
|
393
|
+
|
|
76
394
|
### Error handling
|
|
77
395
|
|
|
78
396
|
By design the factory will catch any error thrown in the route handler will return a simple response with `500` status.
|
|
@@ -80,6 +398,7 @@ By design the factory will catch any error thrown in the route handler will retu
|
|
|
80
398
|
You can customize the error response if you want to fine tune error response management.
|
|
81
399
|
|
|
82
400
|
```tsx
|
|
401
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
83
402
|
import { HttpError, DbUnknownError } from '~/lib/errors'
|
|
84
403
|
import { db } from '~/lib/db'
|
|
85
404
|
|
|
@@ -111,6 +430,19 @@ export const GET = createSafeRouteHandler(
|
|
|
111
430
|
)
|
|
112
431
|
```
|
|
113
432
|
|
|
433
|
+
### Using the request in the route handler
|
|
434
|
+
|
|
435
|
+
The original `request` is cascaded in the route handler function if you need to access to it.
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
439
|
+
|
|
440
|
+
export const GET = createSafeRouteHandler({}, async (ctx, req) => {
|
|
441
|
+
console.log('integrity', req.integrity)
|
|
442
|
+
return new Response(null, { status: 200 })
|
|
443
|
+
})
|
|
444
|
+
```
|
|
445
|
+
|
|
114
446
|
## Fair use note
|
|
115
447
|
|
|
116
448
|
Please note that if you're not using any of the proposed options in `createSafeRouteHandler` it means you're surely don't need it.
|
|
@@ -131,6 +463,10 @@ export function GET() {
|
|
|
131
463
|
|
|
132
464
|
Feel free to open an issue or a PR if you think a relevant option could be added into the factory ๐
|
|
133
465
|
|
|
466
|
+
## Requirements
|
|
467
|
+
|
|
468
|
+
The factory `createSafeRouteHandler` requires Next.js `v14` or `v15` and typescript `v5` as peer dependencies.
|
|
469
|
+
|
|
134
470
|
## Contributing
|
|
135
471
|
|
|
136
472
|
All contributions are welcome! ๐ Feel free to open an issue if you find a bug or create a pull request if you have a feature request.
|
package/dist/index.d.cts
CHANGED
|
@@ -88,8 +88,7 @@ type BaseOptions<AC extends AuthContext | undefined> = {
|
|
|
88
88
|
* ID for the route handler.
|
|
89
89
|
* Used when logging in development or when `debug` is enabled.
|
|
90
90
|
*
|
|
91
|
-
* You can also use it
|
|
92
|
-
* or monitoring.
|
|
91
|
+
* You can also use it to add extra logging or monitoring.
|
|
93
92
|
*/
|
|
94
93
|
id?: string;
|
|
95
94
|
/**
|
|
@@ -112,7 +111,7 @@ type BaseOptions<AC extends AuthContext | undefined> = {
|
|
|
112
111
|
* Use this options to enable debug mode.
|
|
113
112
|
* It will add logs in the handler to help you debug the request.
|
|
114
113
|
*
|
|
115
|
-
* By default it's `false` for production builds.
|
|
114
|
+
* By default it's set to `false` for production builds.
|
|
116
115
|
* In development builds, it will be `true` if `NODE_ENV` is not set to `production`.
|
|
117
116
|
*/
|
|
118
117
|
debug?: boolean;
|
|
@@ -120,7 +119,10 @@ type BaseOptions<AC extends AuthContext | undefined> = {
|
|
|
120
119
|
type OnValidationErrorResponse = (issues: readonly StandardSchemaV1.Issue[]) => Awaitable<Response>;
|
|
121
120
|
type CreateSafeRouteHandlerOptions<AC extends AuthContext | undefined, TSegments extends TSegmentsDict | undefined, TSearchParams extends TSearchParamsDict | undefined, TBody extends TBodySchema | undefined, TFormData extends TFormDataDict | undefined> = {
|
|
122
121
|
/**
|
|
123
|
-
* Dynamic route segments used
|
|
122
|
+
* Dynamic route segments used for the route handler path.
|
|
123
|
+
* By design it will handler if the segments are a `Promise` or not.
|
|
124
|
+
*
|
|
125
|
+
* Please note the expected input is a `StandardSchemaDictionary`.
|
|
124
126
|
*/
|
|
125
127
|
segments?: TSegments;
|
|
126
128
|
/**
|
|
@@ -130,6 +132,8 @@ type CreateSafeRouteHandlerOptions<AC extends AuthContext | undefined, TSegments
|
|
|
130
132
|
onSegmentsValidationErrorResponse?: OnValidationErrorResponse;
|
|
131
133
|
/**
|
|
132
134
|
* Search params used in the route.
|
|
135
|
+
*
|
|
136
|
+
* Please note the expected input is a `StandardSchemaDictionary`.
|
|
133
137
|
*/
|
|
134
138
|
searchParams?: TSearchParams;
|
|
135
139
|
/**
|
|
@@ -144,7 +148,7 @@ type CreateSafeRouteHandlerOptions<AC extends AuthContext | undefined, TSegments
|
|
|
144
148
|
* Returns a `415`response if the request does not explicitly set the `Content-Type` to `application/json`.
|
|
145
149
|
*
|
|
146
150
|
* IMPORTANT: The body is parsed as JSON, so it must be a valid JSON object!
|
|
147
|
-
* IMPORTANT:
|
|
151
|
+
* IMPORTANT: Body shouldn't be used with `formData` at the same time. They are exclusive.
|
|
148
152
|
* Why making the distinction? `formData` is used as a `StandardSchemaDictionary` whereas `body` is used as a `StandardSchemaV1`.
|
|
149
153
|
*/
|
|
150
154
|
body?: TBody;
|
|
@@ -195,14 +199,29 @@ type SafeRouteHandlerContext<AC extends AuthContext | undefined, TSegments exten
|
|
|
195
199
|
*/
|
|
196
200
|
readonly url: URL;
|
|
197
201
|
} & (AC extends AuthContext ? {
|
|
202
|
+
/**
|
|
203
|
+
* Auth context
|
|
204
|
+
*/
|
|
198
205
|
readonly auth: AC;
|
|
199
206
|
} : EmptyObjectType) & (TSegments extends TSegmentsDict ? {
|
|
207
|
+
/**
|
|
208
|
+
* Validated route dynamic segments
|
|
209
|
+
*/
|
|
200
210
|
readonly segments: UnwrapReadonlyObject<StandardSchemaDictionary.InferOutput<TSegments>>;
|
|
201
211
|
} : EmptyObjectType) & (TSearchParams extends TSearchParamsDict ? {
|
|
212
|
+
/**
|
|
213
|
+
* Validated search params
|
|
214
|
+
*/
|
|
202
215
|
readonly searchParams: UnwrapReadonlyObject<StandardSchemaDictionary.InferOutput<TSearchParams>>;
|
|
203
216
|
} : EmptyObjectType) & (TBody extends TBodySchema ? {
|
|
217
|
+
/**
|
|
218
|
+
* Validated request body
|
|
219
|
+
*/
|
|
204
220
|
readonly body: StandardSchemaV1.InferOutput<TBody>;
|
|
205
221
|
} : EmptyObjectType) & (TFormData extends TFormDataDict ? {
|
|
222
|
+
/**
|
|
223
|
+
* Validated form data
|
|
224
|
+
*/
|
|
206
225
|
readonly formData: UnwrapReadonlyObject<StandardSchemaDictionary.InferOutput<TFormData>>;
|
|
207
226
|
} : EmptyObjectType);
|
|
208
227
|
type SafeRouteHandler<AC extends AuthContext | undefined, TSegments extends TSegmentsDict | undefined, TSearchParams extends TSearchParamsDict | undefined, TBody extends TBodySchema | undefined, TFormData extends TFormDataDict | undefined> = (
|
package/dist/index.d.ts
CHANGED
|
@@ -88,8 +88,7 @@ type BaseOptions<AC extends AuthContext | undefined> = {
|
|
|
88
88
|
* ID for the route handler.
|
|
89
89
|
* Used when logging in development or when `debug` is enabled.
|
|
90
90
|
*
|
|
91
|
-
* You can also use it
|
|
92
|
-
* or monitoring.
|
|
91
|
+
* You can also use it to add extra logging or monitoring.
|
|
93
92
|
*/
|
|
94
93
|
id?: string;
|
|
95
94
|
/**
|
|
@@ -112,7 +111,7 @@ type BaseOptions<AC extends AuthContext | undefined> = {
|
|
|
112
111
|
* Use this options to enable debug mode.
|
|
113
112
|
* It will add logs in the handler to help you debug the request.
|
|
114
113
|
*
|
|
115
|
-
* By default it's `false` for production builds.
|
|
114
|
+
* By default it's set to `false` for production builds.
|
|
116
115
|
* In development builds, it will be `true` if `NODE_ENV` is not set to `production`.
|
|
117
116
|
*/
|
|
118
117
|
debug?: boolean;
|
|
@@ -120,7 +119,10 @@ type BaseOptions<AC extends AuthContext | undefined> = {
|
|
|
120
119
|
type OnValidationErrorResponse = (issues: readonly StandardSchemaV1.Issue[]) => Awaitable<Response>;
|
|
121
120
|
type CreateSafeRouteHandlerOptions<AC extends AuthContext | undefined, TSegments extends TSegmentsDict | undefined, TSearchParams extends TSearchParamsDict | undefined, TBody extends TBodySchema | undefined, TFormData extends TFormDataDict | undefined> = {
|
|
122
121
|
/**
|
|
123
|
-
* Dynamic route segments used
|
|
122
|
+
* Dynamic route segments used for the route handler path.
|
|
123
|
+
* By design it will handler if the segments are a `Promise` or not.
|
|
124
|
+
*
|
|
125
|
+
* Please note the expected input is a `StandardSchemaDictionary`.
|
|
124
126
|
*/
|
|
125
127
|
segments?: TSegments;
|
|
126
128
|
/**
|
|
@@ -130,6 +132,8 @@ type CreateSafeRouteHandlerOptions<AC extends AuthContext | undefined, TSegments
|
|
|
130
132
|
onSegmentsValidationErrorResponse?: OnValidationErrorResponse;
|
|
131
133
|
/**
|
|
132
134
|
* Search params used in the route.
|
|
135
|
+
*
|
|
136
|
+
* Please note the expected input is a `StandardSchemaDictionary`.
|
|
133
137
|
*/
|
|
134
138
|
searchParams?: TSearchParams;
|
|
135
139
|
/**
|
|
@@ -144,7 +148,7 @@ type CreateSafeRouteHandlerOptions<AC extends AuthContext | undefined, TSegments
|
|
|
144
148
|
* Returns a `415`response if the request does not explicitly set the `Content-Type` to `application/json`.
|
|
145
149
|
*
|
|
146
150
|
* IMPORTANT: The body is parsed as JSON, so it must be a valid JSON object!
|
|
147
|
-
* IMPORTANT:
|
|
151
|
+
* IMPORTANT: Body shouldn't be used with `formData` at the same time. They are exclusive.
|
|
148
152
|
* Why making the distinction? `formData` is used as a `StandardSchemaDictionary` whereas `body` is used as a `StandardSchemaV1`.
|
|
149
153
|
*/
|
|
150
154
|
body?: TBody;
|
|
@@ -195,14 +199,29 @@ type SafeRouteHandlerContext<AC extends AuthContext | undefined, TSegments exten
|
|
|
195
199
|
*/
|
|
196
200
|
readonly url: URL;
|
|
197
201
|
} & (AC extends AuthContext ? {
|
|
202
|
+
/**
|
|
203
|
+
* Auth context
|
|
204
|
+
*/
|
|
198
205
|
readonly auth: AC;
|
|
199
206
|
} : EmptyObjectType) & (TSegments extends TSegmentsDict ? {
|
|
207
|
+
/**
|
|
208
|
+
* Validated route dynamic segments
|
|
209
|
+
*/
|
|
200
210
|
readonly segments: UnwrapReadonlyObject<StandardSchemaDictionary.InferOutput<TSegments>>;
|
|
201
211
|
} : EmptyObjectType) & (TSearchParams extends TSearchParamsDict ? {
|
|
212
|
+
/**
|
|
213
|
+
* Validated search params
|
|
214
|
+
*/
|
|
202
215
|
readonly searchParams: UnwrapReadonlyObject<StandardSchemaDictionary.InferOutput<TSearchParams>>;
|
|
203
216
|
} : EmptyObjectType) & (TBody extends TBodySchema ? {
|
|
217
|
+
/**
|
|
218
|
+
* Validated request body
|
|
219
|
+
*/
|
|
204
220
|
readonly body: StandardSchemaV1.InferOutput<TBody>;
|
|
205
221
|
} : EmptyObjectType) & (TFormData extends TFormDataDict ? {
|
|
222
|
+
/**
|
|
223
|
+
* Validated form data
|
|
224
|
+
*/
|
|
206
225
|
readonly formData: UnwrapReadonlyObject<StandardSchemaDictionary.InferOutput<TFormData>>;
|
|
207
226
|
} : EmptyObjectType);
|
|
208
227
|
type SafeRouteHandler<AC extends AuthContext | undefined, TSegments extends TSegmentsDict | undefined, TSearchParams extends TSearchParamsDict | undefined, TBody extends TBodySchema | undefined, TFormData extends TFormDataDict | undefined> = (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugardarius/anzen",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A fast, framework validation agnostic, type-safe factory for creating Next.JS App Router route handlers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"packageManager": "npm@11.3.0",
|
|
@@ -67,18 +67,21 @@
|
|
|
67
67
|
"typescript": "^5"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@arethetypeswrong/cli": "^0.
|
|
71
|
-
"@eslint/js": "^9.
|
|
70
|
+
"@arethetypeswrong/cli": "^0.18.1",
|
|
71
|
+
"@eslint/js": "^9.27.0",
|
|
72
72
|
"@release-it/keep-a-changelog": "^7.0.0",
|
|
73
|
-
"@types/node": "^22.15.
|
|
74
|
-
"
|
|
73
|
+
"@types/node": "^22.15.19",
|
|
74
|
+
"@types/react": "^19.1.4",
|
|
75
|
+
"@types/react-dom": "^19.1.5",
|
|
76
|
+
"eslint": "^9.27.0",
|
|
75
77
|
"prettier": "^3.5.3",
|
|
76
78
|
"publint": "^0.3.12",
|
|
77
|
-
"release-it": "^19.0.
|
|
78
|
-
"tsup": "^8.
|
|
79
|
-
"turbo": "^2.5.
|
|
79
|
+
"release-it": "^19.0.2",
|
|
80
|
+
"tsup": "^8.5.0",
|
|
81
|
+
"turbo": "^2.5.3",
|
|
80
82
|
"typescript": "^5.8.3",
|
|
81
|
-
"typescript-eslint": "^8.
|
|
82
|
-
"vitest": "^3.1.
|
|
83
|
+
"typescript-eslint": "^8.32.1",
|
|
84
|
+
"vitest": "^3.1.4",
|
|
85
|
+
"zod": "^3.25.7"
|
|
83
86
|
}
|
|
84
87
|
}
|