@sugardarius/anzen 1.1.2 → 2.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 +116 -378
- package/dist/chunk-DT3TEL5X.js +2 -0
- package/dist/chunk-DT3TEL5X.js.map +1 -0
- package/dist/chunk-UDCVGQMH.cjs +2 -0
- package/dist/chunk-UDCVGQMH.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -240
- package/dist/index.d.ts +3 -240
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server-components/index.cjs +2 -0
- package/dist/server-components/index.cjs.map +1 -0
- package/dist/server-components/index.d.cts +208 -0
- package/dist/server-components/index.d.ts +208 -0
- package/dist/server-components/index.js +2 -0
- package/dist/server-components/index.js.map +1 -0
- package/dist/types-LPIIICMI.d.cts +266 -0
- package/dist/types-LPIIICMI.d.ts +266 -0
- package/package.json +23 -11
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
A
|
|
1
|
+
A fast, framework validation agnostic, type-safe factories for creating route handlers, page and layout server components files in Next.js.
|
|
2
2
|
|
|
3
3
|
- 🔧 Framework validation agnostic, use a validation library of your choice supporting [Standard Schema](https://standardschema.dev/).
|
|
4
4
|
- 🧠 Focused functionalities, use only features you want.
|
|
@@ -15,6 +15,8 @@ npm i @sugardarius/anzen
|
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
18
|
+
### Route Handlers
|
|
19
|
+
|
|
18
20
|
```tsx
|
|
19
21
|
import { object, string, number } from 'decoders'
|
|
20
22
|
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
@@ -35,469 +37,205 @@ export const POST = createSafeRouteHandler(
|
|
|
35
37
|
bar: number,
|
|
36
38
|
}),
|
|
37
39
|
},
|
|
38
|
-
async ({ auth, body }, req): Promise<Response> => {
|
|
39
|
-
return Response.json({ user: auth.user, body }, { status: 200 })
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The example above shows how to use the factory to authorize your requests.
|
|
45
|
-
|
|
46
|
-
## Framework validation agnostic
|
|
47
|
-
|
|
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/).
|
|
49
|
-
|
|
50
|
-
```tsx
|
|
51
|
-
// (POST) /app/api/races/[id]/route.ts
|
|
52
|
-
import { z } from 'zod'
|
|
53
|
-
import { object, string, number } from 'decoders'
|
|
54
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
55
|
-
|
|
56
|
-
export const POST = createSafeRouteHandler(
|
|
57
|
-
{
|
|
58
|
-
// `zod` for segments dictionary validation
|
|
59
|
-
segments: { id: z.string() }
|
|
60
|
-
// `decoders` for body validation
|
|
61
|
-
body: object({
|
|
62
|
-
id: number,
|
|
63
|
-
name: string,
|
|
64
|
-
}),
|
|
65
|
-
},
|
|
66
|
-
async ({ segments, body }) => {
|
|
67
|
-
return Response.json({ segments, body })
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Synchronous validations
|
|
73
|
-
|
|
74
|
-
The factory do not supports async validations. As required by the [Standard Schema](https://github.com/standard-schema/standard-schema) common interface we should avoid it. In the context of a route handler it's not necessary.
|
|
75
|
-
|
|
76
|
-
If you define an async validation then the route handler will throw an error.
|
|
77
|
-
|
|
78
|
-
## API
|
|
79
|
-
|
|
80
|
-
Check the API and the available options to configure the factory as you wish.
|
|
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
40
|
async (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
### Using `NextRequest` type
|
|
117
|
-
|
|
118
|
-
By default the factory uses the native `Request` type. If you want to use the `NextRequest` type from [Next.js](https://nextjs.org/), you can do it by just using the `NextRequest` type in the factory handler.
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
import { NextRequest } from 'next/server'
|
|
122
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
123
|
-
|
|
124
|
-
export const GET = createSafeRouteHandler(
|
|
125
|
-
{
|
|
126
|
-
id: 'next/request',
|
|
127
|
-
authorize: async ({
|
|
128
|
-
// Due to `NextRequest` limitations as the req is cloned it's always a `Request`
|
|
129
|
-
req,
|
|
130
|
-
}) => {
|
|
131
|
-
console.log(req)
|
|
132
|
-
return { user: 'John Doe' }
|
|
41
|
+
{
|
|
42
|
+
auth, // Auth context is inferred from the authorize function
|
|
43
|
+
body, // Body is inferred from the body validation
|
|
133
44
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return new Response(null, 200)
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Base options
|
|
143
|
-
|
|
144
|
-
When creating a safe route handler you can use a bunch of options for helping you achieve different tasks 👇🏻
|
|
145
|
-
|
|
146
|
-
#### `id?: string`
|
|
147
|
-
|
|
148
|
-
Used for logging in development or when the `debug` option is enabled. You can also use it to add extra logging or monitoring.
|
|
149
|
-
By default the id is set to `[unknown:route:handler]`
|
|
150
|
-
|
|
151
|
-
```tsx
|
|
152
|
-
export const POST = createSafeRouteHandler(
|
|
153
|
-
{
|
|
154
|
-
id: 'auth/login',
|
|
155
|
-
},
|
|
156
|
-
async ({ id }) => {
|
|
157
|
-
return Response.json({ id })
|
|
45
|
+
req
|
|
46
|
+
): Promise<Response> => {
|
|
47
|
+
return Response.json({ user: auth.user, body }, { status: 200 })
|
|
158
48
|
}
|
|
159
49
|
)
|
|
160
50
|
```
|
|
161
51
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
Function to use to authorize the request. By default it always authorize the request.
|
|
165
|
-
|
|
166
|
-
Returns a response when the request is not authorized.
|
|
52
|
+
### Page Server Components
|
|
167
53
|
|
|
168
54
|
```tsx
|
|
169
|
-
import {
|
|
55
|
+
import { object, string, number } from 'decoders'
|
|
56
|
+
import { unauthorized } from 'next/navigation'
|
|
57
|
+
import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
|
|
170
58
|
import { auth } from '~/lib/auth'
|
|
171
59
|
|
|
172
|
-
export
|
|
60
|
+
export default createSafePageServerComponent(
|
|
173
61
|
{
|
|
174
|
-
authorize: async ({
|
|
175
|
-
|
|
176
|
-
const session = await auth.getSession(req)
|
|
62
|
+
authorize: async ({ segments }) => {
|
|
63
|
+
const session = await auth.getSession()
|
|
177
64
|
if (!session) {
|
|
178
|
-
|
|
65
|
+
unauthorized()
|
|
179
66
|
}
|
|
180
67
|
|
|
181
68
|
return { user: session.user }
|
|
182
69
|
},
|
|
183
|
-
},
|
|
184
|
-
async ({ auth, body }, req): Promise<Response> => {
|
|
185
|
-
return Response.json({ user: auth.user }, { status: 200 })
|
|
186
|
-
}
|
|
187
|
-
)
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
The original is cloned from the incoming request to avoid side effects and to make it consumable in the `authorize` function.
|
|
191
|
-
|
|
192
|
-
#### `onErrorResponse?: (err: unknown) => Awaitable<Response>`
|
|
193
|
-
|
|
194
|
-
Callback triggered when the request fails.
|
|
195
|
-
By default it returns a simple `500` response and the error is logged into the console.
|
|
196
|
-
|
|
197
|
-
Use it if your handler use custom errors and you want to manage them properly by returning a proper response.
|
|
198
|
-
|
|
199
|
-
You can read more about it under the [Error handling](#error-handling) section.
|
|
200
|
-
|
|
201
|
-
#### `debug?: boolean`
|
|
202
|
-
|
|
203
|
-
Use this options to enable debug mode. It will add logs in the handler to help you debug the request.
|
|
204
|
-
|
|
205
|
-
By default it's set to `false` for production builds.
|
|
206
|
-
In development builds, it will be `true` if `NODE_ENV` is not set to `production`.
|
|
207
|
-
|
|
208
|
-
```tsx
|
|
209
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
210
|
-
|
|
211
|
-
export const GET = createSafeRouteHandler({ debug: true }, async () => {
|
|
212
|
-
return new Response(null, { status: 200 })
|
|
213
|
-
})
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Route handler options
|
|
217
|
-
|
|
218
|
-
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 👇🏻
|
|
219
|
-
|
|
220
|
-
#### `segments?: TSegments`
|
|
221
|
-
|
|
222
|
-
[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.
|
|
223
|
-
|
|
224
|
-
Please note the expected input is a `StandardSchemaDictionary`.
|
|
225
|
-
|
|
226
|
-
```tsx
|
|
227
|
-
import { z } from 'zod'
|
|
228
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
229
|
-
|
|
230
|
-
export const GET = createSafeRouteHandler(
|
|
231
|
-
{
|
|
232
70
|
segments: {
|
|
233
|
-
|
|
234
|
-
|
|
71
|
+
id: string,
|
|
72
|
+
},
|
|
73
|
+
searchParams: {
|
|
74
|
+
page: number,
|
|
235
75
|
},
|
|
236
76
|
},
|
|
237
|
-
async ({
|
|
238
|
-
|
|
77
|
+
async ({
|
|
78
|
+
auth, // Auth context is inferred from the authorize function
|
|
79
|
+
segments, // Segments are inferred from the segments validation
|
|
80
|
+
searchParams, // Search params are inferred from the searchParams validation
|
|
81
|
+
}) => {
|
|
82
|
+
return <div>Hello {auth.user.name}!</div>
|
|
239
83
|
}
|
|
240
84
|
)
|
|
241
85
|
```
|
|
242
86
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
Callback triggered when dynamic segments validations returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
87
|
+
### Layout Server Components
|
|
246
88
|
|
|
247
89
|
```tsx
|
|
248
90
|
import { z } from 'zod'
|
|
249
|
-
import {
|
|
91
|
+
import { createSafeLayoutServerComponent } from '@sugardarius/anzen/server-components'
|
|
92
|
+
import { auth } from '~/lib/auth'
|
|
93
|
+
import { notFound, unauthorized } from 'next/navigation'
|
|
250
94
|
|
|
251
|
-
export
|
|
95
|
+
export default createSafeLayoutServerComponent(
|
|
252
96
|
{
|
|
253
97
|
segments: {
|
|
254
98
|
accountId: z.string(),
|
|
255
|
-
projectId: z.string().optional(),
|
|
256
|
-
},
|
|
257
|
-
onSegmentsValidationErrorResponse: (issues) => {
|
|
258
|
-
return Response.json({ issues }, { status: 400 })
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
async ({ segments }) => {
|
|
262
|
-
return Response.json({ segments })
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
#### `searchParams?: TSearchParams`
|
|
268
|
-
|
|
269
|
-
Search params used in the route.
|
|
270
|
-
|
|
271
|
-
Please note the expected input is a `StandardSchemaDictionary`.
|
|
272
|
-
|
|
273
|
-
```tsx
|
|
274
|
-
import { string, numeric, optional } from 'decoders'
|
|
275
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
276
|
-
|
|
277
|
-
export const GET = createSafeRouteHandler(
|
|
278
|
-
{
|
|
279
|
-
searchParams: {
|
|
280
|
-
query: string,
|
|
281
|
-
page: optional(numeric),
|
|
282
99
|
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
#### `onSearchParamsValidationErrorResponse?: OnValidationErrorResponse`
|
|
291
|
-
|
|
292
|
-
Callback triggered when search params validations returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
100
|
+
authorize: async ({ segments }) => {
|
|
101
|
+
const session = await auth.getSession()
|
|
102
|
+
if (!session) {
|
|
103
|
+
unauthorized()
|
|
104
|
+
}
|
|
293
105
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
106
|
+
const hasAccess = await checkAccountAccess(
|
|
107
|
+
session.user.id,
|
|
108
|
+
segments.accountId
|
|
109
|
+
)
|
|
110
|
+
if (!hasAccess) {
|
|
111
|
+
notFound()
|
|
112
|
+
}
|
|
297
113
|
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
searchParams: {
|
|
301
|
-
query: string,
|
|
302
|
-
page: optional(numeric),
|
|
303
|
-
},
|
|
304
|
-
onSearchParamsValidationErrorResponse: (issues) => {
|
|
305
|
-
return Response.json({ issues }, { status: 400 })
|
|
114
|
+
return { user: session.user }
|
|
306
115
|
},
|
|
307
116
|
},
|
|
308
|
-
async ({
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
Request body.
|
|
317
|
-
|
|
318
|
-
Returns a `405` response if the request method is not `POST`, `PUT` or `PATCH`.
|
|
319
|
-
|
|
320
|
-
Returns a `415`response if the request does not explicitly set the `Content-Type` to `application/json`.
|
|
321
|
-
|
|
322
|
-
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**.
|
|
323
|
-
|
|
324
|
-
Why making the distinction? `formData` is used as a `StandardSchemaDictionary` whereas `body` is used as a `StandardSchemaV1`.
|
|
325
|
-
|
|
326
|
-
```tsx
|
|
327
|
-
import { z } from 'zod'
|
|
328
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
329
|
-
|
|
330
|
-
export const POST = createSafeRouteHandler(
|
|
331
|
-
{
|
|
332
|
-
body: z.object({
|
|
333
|
-
name: z.string(),
|
|
334
|
-
model: z.string(),
|
|
335
|
-
apiKey: z.string(),
|
|
336
|
-
}),
|
|
337
|
-
},
|
|
338
|
-
async ({ body }) => {
|
|
339
|
-
return Response.json({ body })
|
|
117
|
+
async ({ auth, segments, children }) => {
|
|
118
|
+
return (
|
|
119
|
+
<div>
|
|
120
|
+
<header>Account: {segments.accountId}</header>
|
|
121
|
+
{children}
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
340
124
|
}
|
|
341
125
|
)
|
|
342
126
|
```
|
|
343
127
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
#### `onBodyValidationErrorResponse?: OnValidationErrorResponse`
|
|
128
|
+
## Framework validation agnostic
|
|
347
129
|
|
|
348
|
-
|
|
130
|
+
By design the factories are 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://standardschema.dev/) common interface. You can use your favorite validation library like [Zod](https://zod.dev/), [Validbot](https://valibot.dev/) or [decoders](https://decoders.cc/).
|
|
349
131
|
|
|
350
132
|
```tsx
|
|
133
|
+
// Route handler example
|
|
351
134
|
import { z } from 'zod'
|
|
135
|
+
import { object, string, number } from 'decoders'
|
|
352
136
|
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
353
137
|
|
|
354
138
|
export const POST = createSafeRouteHandler(
|
|
355
139
|
{
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
140
|
+
// `zod` for segments dictionary validation
|
|
141
|
+
segments: { id: z.string() },
|
|
142
|
+
// `decoders` for body object validation
|
|
143
|
+
body: object({
|
|
144
|
+
id: number,
|
|
145
|
+
name: string,
|
|
360
146
|
}),
|
|
361
|
-
onBodyValidationErrorResponse: (issues) => {
|
|
362
|
-
return Response.json({ issues }, { status: 400 })
|
|
363
|
-
},
|
|
364
147
|
},
|
|
365
|
-
async ({ body }) => {
|
|
366
|
-
return Response.json({ body })
|
|
148
|
+
async ({ segments, body }) => {
|
|
149
|
+
return Response.json({ segments, body })
|
|
367
150
|
}
|
|
368
151
|
)
|
|
369
152
|
```
|
|
370
153
|
|
|
371
|
-
#### `formData?: TFormData`
|
|
372
|
-
|
|
373
|
-
Request form data.
|
|
374
|
-
|
|
375
|
-
Returns a `405` response if the request method is not `POST`, `PUT` or `PATCH`.
|
|
376
|
-
|
|
377
|
-
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`.
|
|
378
|
-
|
|
379
|
-
Please note formData shouldn't be used with `body` at the same time. They are **exclusive**.
|
|
380
|
-
|
|
381
|
-
Why making the distinction? `formData` is used as a `StandardSchemaDictionary` whereas `body` is used as a `StandardSchemaV1`.
|
|
382
|
-
|
|
383
154
|
```tsx
|
|
155
|
+
// Page server component example
|
|
384
156
|
import { z } from 'zod'
|
|
385
|
-
import {
|
|
157
|
+
import { string, number } from 'decoders'
|
|
158
|
+
import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
|
|
386
159
|
|
|
387
|
-
export
|
|
160
|
+
export default createSafePageServerComponent(
|
|
388
161
|
{
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
162
|
+
// `zod` for segments dictionary validation
|
|
163
|
+
segments: { id: z.string() },
|
|
164
|
+
// `decoders` for search params dictionary validation
|
|
165
|
+
searchParams: {
|
|
166
|
+
page: number,
|
|
392
167
|
},
|
|
393
168
|
},
|
|
394
|
-
async ({
|
|
395
|
-
return
|
|
169
|
+
async ({ segments, searchParams }) => {
|
|
170
|
+
return (
|
|
171
|
+
<div>
|
|
172
|
+
Race {segments.id} - Page {searchParams.page}
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
396
175
|
}
|
|
397
176
|
)
|
|
398
177
|
```
|
|
399
178
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
#### `onFormDataValidationErrorResponse?: OnValidationErrorResponse`
|
|
403
|
-
|
|
404
|
-
Callback triggered when form data validation returned issues. By default it returns a simple `400` response and issues are logged into the console.
|
|
405
|
-
|
|
406
|
-
```tsx
|
|
407
|
-
import { z } from 'zod'
|
|
408
|
-
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
179
|
+
## Synchronous validations
|
|
409
180
|
|
|
410
|
-
|
|
411
|
-
{
|
|
412
|
-
formData: {
|
|
413
|
-
id: z.string(),
|
|
414
|
-
message: z.string(),
|
|
415
|
-
},
|
|
416
|
-
onFormDataValidationErrorResponse: (issues) => {
|
|
417
|
-
return Response.json({ issues }, { status: 400 })
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
async ({ formData }) => {
|
|
421
|
-
return Response.json({ formData })
|
|
422
|
-
}
|
|
423
|
-
)
|
|
424
|
-
```
|
|
181
|
+
The factories do not support async validations. As required by the [Standard Schema](https://standardschema.dev/) common interface we should avoid it. In the context of route handlers and server components it's not necessary.
|
|
425
182
|
|
|
426
|
-
|
|
183
|
+
If you define an async validation then the factory will throw an error.
|
|
427
184
|
|
|
428
|
-
|
|
185
|
+
## Fair use note
|
|
429
186
|
|
|
430
|
-
|
|
187
|
+
Please note that if you're not using any of the proposed options in the factories it means you're surely don't need them.
|
|
431
188
|
|
|
432
189
|
```tsx
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
{
|
|
439
|
-
onErrorResponse: async (err: unknown): Promise<Response> => {
|
|
440
|
-
if (err instanceof HttpError) {
|
|
441
|
-
return new Response(err.message, { status: err.status })
|
|
442
|
-
} else if (err instanceof DbUnknownError) {
|
|
443
|
-
return new Response(err.message, { status: err.status })
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return new Response('Internal server error', { status: 500 })
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
async (): Promise<Response> => {
|
|
450
|
-
const [data, err] = await db.findUnique({ id: 'liveblocks' })
|
|
451
|
-
|
|
452
|
-
if (err) {
|
|
453
|
-
throw new DbUnknownError(err.message, 500)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (data === null) {
|
|
457
|
-
throw new HttpError(404)
|
|
458
|
-
}
|
|
190
|
+
// Route handler
|
|
191
|
+
// Calling 👇🏻
|
|
192
|
+
export const GET = createSafeRouteHandler({}, async () => {
|
|
193
|
+
return new Response(null, { status: 200 })
|
|
194
|
+
})
|
|
459
195
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
)
|
|
196
|
+
// is equal to declare the route handler this way 👇🏻
|
|
197
|
+
export function GET() {
|
|
198
|
+
return new Response(null, { status: 200 })
|
|
199
|
+
}
|
|
200
|
+
// except `createSafeRouteHandler` will provide by default a native error catching
|
|
201
|
+
// and will return a `500` response. That's the only advantage.
|
|
463
202
|
```
|
|
464
203
|
|
|
465
|
-
### Using the request in the route handler
|
|
466
|
-
|
|
467
|
-
The original `request` is cascaded in the route handler function if you need to access to it.
|
|
468
|
-
|
|
469
204
|
```tsx
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
export
|
|
473
|
-
|
|
474
|
-
return new Response(null, { status: 200 })
|
|
205
|
+
// Page server component
|
|
206
|
+
// Calling 👇🏻
|
|
207
|
+
export default createSafePageServerComponent({}, async () => {
|
|
208
|
+
return <div>Hello</div>
|
|
475
209
|
})
|
|
476
|
-
```
|
|
477
210
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
211
|
+
// is equal to declare the page server component this way 👇🏻
|
|
212
|
+
export default async function Page() {
|
|
213
|
+
return <div>Hello</div>
|
|
214
|
+
}
|
|
215
|
+
```
|
|
481
216
|
|
|
482
217
|
```tsx
|
|
218
|
+
// Layout server component
|
|
483
219
|
// Calling 👇🏻
|
|
484
|
-
export
|
|
485
|
-
return
|
|
220
|
+
export default createSafeLayoutServerComponent({}, async ({ children }) => {
|
|
221
|
+
return <div>{children}</div>
|
|
486
222
|
})
|
|
487
223
|
|
|
488
|
-
// is equal to declare the
|
|
489
|
-
export function
|
|
490
|
-
|
|
224
|
+
// is equal to declare the layout server component this way 👇🏻
|
|
225
|
+
export default async function Layout({
|
|
226
|
+
children,
|
|
227
|
+
}: {
|
|
228
|
+
children: React.ReactNode
|
|
229
|
+
}) {
|
|
230
|
+
return <div>{children}</div>
|
|
491
231
|
}
|
|
492
|
-
// excepts `createSafeRouteHandler` will provide by default a native error catching
|
|
493
|
-
// and will return a `500` response. That's the only advantage.
|
|
494
232
|
```
|
|
495
233
|
|
|
496
|
-
Feel free to open an issue or a PR if you think a relevant option could be added into the
|
|
234
|
+
Feel free to open an issue or a PR if you think a relevant option could be added into the factories 🙂
|
|
497
235
|
|
|
498
236
|
## Requirements
|
|
499
237
|
|
|
500
|
-
The
|
|
238
|
+
The factories require Next.js `v14`, `v15` or `v16` and typescript `v5` as peer dependencies.
|
|
501
239
|
|
|
502
240
|
## Contributing
|
|
503
241
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function i(e){return e!==null&&(typeof e=="object"||typeof e=="function")&&typeof e.then=="function"}function u(e,t){if(i(e))throw new Error(t)}function p(e=!1){let t=e||process.env.NODE_ENV!=="production";return{info:(r,...n)=>{t&&console.log(r,...n)},error:(r,...n)=>{t&&console.error(r,...n)},warn:(r,...n)=>{t&&console.warn(r,...n)}}}function c(){let e=null,t=null;return{start:()=>{e=performance.now()},stop:()=>{if(e===null)throw new Error("Execution clock was not started.");t=performance.now()},get:()=>{if(!e||!t)throw new Error("Execution clock has not been started or stopped.");return`${(t-e).toFixed(2)}ms`}}}function m(e,t,r="Validation must be synchronous but schema returned a Promise."){let n=e["~standard"].validate(t);return u(n,r),n}var d=(e,t)=>t in e;function S(e,t){let r={},n=[];for(let o in e){if(!d(e,o))continue;let a=e[o]["~standard"].validate(t[o]);if(u(a,`Validation must be synchronous, but ${o} returned a Promise.`),a.issues){n.push(...a.issues.map(s=>({...s,path:[o,...s.path??[]]})));continue}r[o]=a.value}return n.length>0?{issues:n}:{value:r}}export{p as a,c as b,m as c,S as d};
|
|
2
|
+
//# sourceMappingURL=chunk-DT3TEL5X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/standard-schema.ts"],"sourcesContent":["export function isPromise<T>(\n value: unknown\n): value is Promise<T> | PromiseLike<T> {\n return (\n value !== null &&\n (typeof value === 'object' || typeof value === 'function') &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof (value as any).then === 'function'\n )\n}\n\nexport function assertsSyncOperation<T>(\n value: T | Promise<T> | PromiseLike<T>,\n message: string\n): asserts value is T {\n if (isPromise<T>(value)) {\n throw new Error(message)\n }\n}\n\nexport function createLogger(debug: boolean = false) {\n const shouldLog = debug || process.env.NODE_ENV !== 'production'\n return {\n info: (message: string, ...rest: unknown[]): void => {\n if (shouldLog) {\n console.log(message, ...rest)\n }\n },\n error: (message: string, ...rest: unknown[]): void => {\n if (shouldLog) {\n console.error(message, ...rest)\n }\n },\n warn: (message: string, ...rest: unknown[]): void => {\n if (shouldLog) {\n console.warn(message, ...rest)\n }\n },\n }\n}\n\nexport function createExecutionClock() {\n let startTime: number | null = null\n let endTime: number | null = null\n\n return {\n start: (): void => {\n startTime = performance.now()\n },\n stop: (): void => {\n if (startTime === null) {\n throw new Error('Execution clock was not started.')\n }\n endTime = performance.now()\n },\n get: (): string => {\n if (!startTime || !endTime) {\n throw new Error('Execution clock has not been started or stopped.')\n }\n\n const duration = endTime - startTime\n return `${duration.toFixed(2)}ms`\n },\n }\n}\n","import { assertsSyncOperation } from './utils'\n\n/** The Standard Schema interface. */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n /** The Standard Schema properties. */\n readonly '~standard': StandardSchemaV1.Props<Input, Output>\n}\n\nexport declare namespace StandardSchemaV1 {\n /** The Standard Schema properties interface. */\n export interface Props<Input = unknown, Output = Input> {\n /** The version number of the standard. */\n readonly version: 1\n /** The vendor name of the schema library. */\n readonly vendor: string\n /** Validates unknown input values. */\n readonly validate: (\n value: unknown\n ) => Result<Output> | Promise<Result<Output>>\n /** Inferred types associated with the schema. */\n readonly types?: Types<Input, Output> | undefined\n }\n\n /** The result interface of the validate function. */\n export type Result<Output> = SuccessResult<Output> | FailureResult\n\n /** The result interface if validation succeeds. */\n export interface SuccessResult<Output> {\n /** The typed output value. */\n readonly value: Output\n /** The non-existent issues. */\n readonly issues?: undefined\n }\n\n /** The result interface if validation fails. */\n export interface FailureResult {\n /** The issues of failed validation. */\n readonly issues: ReadonlyArray<Issue>\n }\n\n /** The issue interface of the failure output. */\n export interface Issue {\n /** The error message of the issue. */\n readonly message: string\n /** The path of the issue, if any. */\n readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined\n }\n\n /** The path segment interface of the issue. */\n export interface PathSegment {\n /** The key representing a path segment. */\n readonly key: PropertyKey\n }\n\n /** The Standard Schema types interface. */\n export interface Types<Input = unknown, Output = Input> {\n /** The input type of the schema. */\n readonly input: Input\n /** The output type of the schema. */\n readonly output: Output\n }\n\n /** Infers the input type of a Standard Schema. */\n export type InferInput<Schema extends StandardSchemaV1> = NonNullable<\n Schema['~standard']['types']\n >['input']\n\n /** Infers the output type of a Standard Schema. */\n export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<\n Schema['~standard']['types']\n >['output']\n}\n\nexport function validateWithSchema<TSchema extends StandardSchemaV1>(\n schema: TSchema,\n value: unknown,\n errSyncMsg = 'Validation must be synchronous but schema returned a Promise.'\n): StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>> {\n const result = schema['~standard'].validate(value)\n assertsSyncOperation(result, errSyncMsg)\n\n return result\n}\n\n// Thanks to `@t3-env/core` (👉🏻 https://github.com/t3-oss/t3-env/blob/main/packages/core/src/standard.ts)\n// for this awesome dictionary schema.\nexport type StandardSchemaDictionary<\n Input = Record<string, unknown>,\n Output extends Record<keyof Input, unknown> = Input,\n> = {\n [K in keyof Input]-?: StandardSchemaV1<Input[K], Output[K]>\n}\n\nexport namespace StandardSchemaDictionary {\n export type InferInput<T extends StandardSchemaDictionary> = {\n [K in keyof T]: StandardSchemaV1.InferInput<T[K]>\n }\n export type InferOutput<T extends StandardSchemaDictionary> = {\n [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>\n }\n}\n\nconst hasDictKey = <T extends object, K extends PropertyKey>(\n obj: T,\n key: K\n): obj is T & Record<typeof key, unknown> => {\n return key in obj\n}\n\nexport function parseWithDictionary<TDict extends StandardSchemaDictionary>(\n dictionary: TDict,\n value: Record<string, unknown>\n): StandardSchemaV1.Result<StandardSchemaDictionary.InferOutput<TDict>> {\n const result: Record<string, unknown> = {}\n const issues: StandardSchemaV1.Issue[] = []\n\n for (const key in dictionary) {\n if (!hasDictKey(dictionary, key)) {\n continue\n }\n // NOTE: safe to assert as we're ensuring just before key isn't undefined\n const propResult = dictionary[key]!['~standard'].validate(value[key])\n\n assertsSyncOperation(\n propResult,\n `Validation must be synchronous, but ${key} returned a Promise.`\n )\n\n if (propResult.issues) {\n issues.push(\n ...propResult.issues.map((issue) => ({\n ...issue,\n path: [key, ...(issue.path ?? [])],\n }))\n )\n continue\n }\n result[key] = propResult.value\n }\n\n if (issues.length > 0) {\n return { issues }\n }\n\n return { value: result as never }\n}\n"],"mappings":"AAAO,SAASA,EACdC,EACsC,CACtC,OACEA,IAAU,OACT,OAAOA,GAAU,UAAY,OAAOA,GAAU,aAE/C,OAAQA,EAAc,MAAS,UAEnC,CAEO,SAASC,EACdD,EACAE,EACoB,CACpB,GAAIH,EAAaC,CAAK,EACpB,MAAM,IAAI,MAAME,CAAO,CAE3B,CAEO,SAASC,EAAaC,EAAiB,GAAO,CACnD,IAAMC,EAAYD,GAAS,QAAQ,IAAI,WAAa,aACpD,MAAO,CACL,KAAM,CAACF,KAAoBI,IAA0B,CAC/CD,GACF,QAAQ,IAAIH,EAAS,GAAGI,CAAI,CAEhC,EACA,MAAO,CAACJ,KAAoBI,IAA0B,CAChDD,GACF,QAAQ,MAAMH,EAAS,GAAGI,CAAI,CAElC,EACA,KAAM,CAACJ,KAAoBI,IAA0B,CAC/CD,GACF,QAAQ,KAAKH,EAAS,GAAGI,CAAI,CAEjC,CACF,CACF,CAEO,SAASC,GAAuB,CACrC,IAAIC,EAA2B,KAC3BC,EAAyB,KAE7B,MAAO,CACL,MAAO,IAAY,CACjBD,EAAY,YAAY,IAAI,CAC9B,EACA,KAAM,IAAY,CAChB,GAAIA,IAAc,KAChB,MAAM,IAAI,MAAM,kCAAkC,EAEpDC,EAAU,YAAY,IAAI,CAC5B,EACA,IAAK,IAAc,CACjB,GAAI,CAACD,GAAa,CAACC,EACjB,MAAM,IAAI,MAAM,kDAAkD,EAIpE,MAAO,IADUA,EAAUD,GACR,QAAQ,CAAC,CAAC,IAC/B,CACF,CACF,CCSO,SAASE,EACdC,EACAC,EACAC,EAAa,gEACmD,CAChE,IAAMC,EAASH,EAAO,WAAW,EAAE,SAASC,CAAK,EACjD,OAAAG,EAAqBD,EAAQD,CAAU,EAEhCC,CACT,CAoBA,IAAME,EAAa,CACjBC,EACAC,IAEOA,KAAOD,EAGT,SAASE,EACdC,EACAR,EACsE,CACtE,IAAME,EAAkC,CAAC,EACnCO,EAAmC,CAAC,EAE1C,QAAWH,KAAOE,EAAY,CAC5B,GAAI,CAACJ,EAAWI,EAAYF,CAAG,EAC7B,SAGF,IAAMI,EAAaF,EAAWF,CAAG,EAAG,WAAW,EAAE,SAASN,EAAMM,CAAG,CAAC,EAOpE,GALAH,EACEO,EACA,uCAAuCJ,CAAG,sBAC5C,EAEII,EAAW,OAAQ,CACrBD,EAAO,KACL,GAAGC,EAAW,OAAO,IAAKC,IAAW,CACnC,GAAGA,EACH,KAAM,CAACL,EAAK,GAAIK,EAAM,MAAQ,CAAC,CAAE,CACnC,EAAE,CACJ,EACA,QACF,CACAT,EAAOI,CAAG,EAAII,EAAW,KAC3B,CAEA,OAAID,EAAO,OAAS,EACX,CAAE,OAAAA,CAAO,EAGX,CAAE,MAAOP,CAAgB,CAClC","names":["isPromise","value","assertsSyncOperation","message","createLogger","debug","shouldLog","rest","createExecutionClock","startTime","endTime","validateWithSchema","schema","value","errSyncMsg","result","assertsSyncOperation","hasDictKey","obj","key","parseWithDictionary","dictionary","issues","propResult","issue"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }function i(e){return e!==null&&(typeof e=="object"||typeof e=="function")&&typeof e.then=="function"}function u(e,t){if(i(e))throw new Error(t)}function p(e=!1){let t=e||process.env.NODE_ENV!=="production";return{info:(r,...n)=>{t&&console.log(r,...n)},error:(r,...n)=>{t&&console.error(r,...n)},warn:(r,...n)=>{t&&console.warn(r,...n)}}}function c(){let e=null,t=null;return{start:()=>{e=performance.now()},stop:()=>{if(e===null)throw new Error("Execution clock was not started.");t=performance.now()},get:()=>{if(!e||!t)throw new Error("Execution clock has not been started or stopped.");return`${(t-e).toFixed(2)}ms`}}}function m(e,t,r="Validation must be synchronous but schema returned a Promise."){let n=e["~standard"].validate(t);return u(n,r),n}var d=(e,t)=>t in e;function S(e,t){let r={},n=[];for(let o in e){if(!d(e,o))continue;let a=e[o]["~standard"].validate(t[o]);if(u(a,`Validation must be synchronous, but ${o} returned a Promise.`),a.issues){n.push(...a.issues.map(s=>({...s,path:[o,..._nullishCoalesce(s.path, () => ([]))]})));continue}r[o]=a.value}return n.length>0?{issues:n}:{value:r}}exports.a = p; exports.b = c; exports.c = m; exports.d = S;
|
|
2
|
+
//# sourceMappingURL=chunk-UDCVGQMH.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts"],"names":["isPromise","value","assertsSyncOperation","message","createLogger","debug","shouldLog","rest","createExecutionClock","startTime","endTime"],"mappings":"AAAO,sLAASA,CAAAA,CACdC,CAAAA,CACsC,CACtC,OACEA,CAAAA,GAAU,IAAA,EAAA,CACT,OAAOA,CAAAA,EAAU,QAAA,EAAY,OAAOA,CAAAA,EAAU,UAAA,CAAA,EAE/C,OAAQA,CAAAA,CAAc,IAAA,EAAS,UAEnC,CAEO,SAASC,CAAAA,CACdD,CAAAA,CACAE,CAAAA,CACoB,CACpB,EAAA,CAAIH,CAAAA,CAAaC,CAAK,CAAA,CACpB,MAAM,IAAI,KAAA,CAAME,CAAO,CAE3B,CAEO,SAASC,CAAAA,CAAaC,CAAAA,CAAiB,CAAA,CAAA,CAAO,CACnD,IAAMC,CAAAA,CAAYD,CAAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,QAAA,GAAa,YAAA,CACpD,MAAO,CACL,IAAA,CAAM,CAACF,CAAAA,CAAAA,GAAoBI,CAAAA,CAAAA,EAA0B,CAC/CD,CAAAA,EACF,OAAA,CAAQ,GAAA,CAAIH,CAAAA,CAAS,GAAGI,CAAI,CAEhC,CAAA,CACA,KAAA,CAAO,CAACJ,CAAAA,CAAAA,GAAoBI,CAAAA,CAAAA,EAA0B,CAChDD,CAAAA,EACF,OAAA,CAAQ,KAAA,CAAMH,CAAAA,CAAS,GAAGI,CAAI,CAElC,CAAA,CACA,IAAA,CAAM,CAACJ,CAAAA,CAAAA,GAAoBI,CAAAA,CAAAA,EAA0B,CAC/CD,CAAAA,EACF,OAAA,CAAQ,IAAA,CAAKH,CAAAA,CAAS,GAAGI,CAAI,CAEjC,CACF,CACF,CAEO,SAASC,CAAAA,CAAAA,CAAuB,CACrC,IAAIC,CAAAA,CAA2B,IAAA,CAC3BC,CAAAA,CAAyB,IAAA,CAE7B,MAAO,CACL,KAAA,CAAO,CAAA,CAAA,EAAY,CACjBD,CAAAA,CAAY,WAAA,CAAY,GAAA,CAAI,CAC9B,CAAA,CACA,IAAA,CAAM,CAAA,CAAA,EAAY,CAChB,EAAA,CAAIA,CAAAA,GAAc,IAAA,CAChB,MAAM,IAAI,KAAA,CAAM,kCAAkC,CAAA,CAEpDC,CAAAA,CAAU,WAAA,CAAY,GAAA,CAAI,CAC5B,CAAA,CACA,GAAA,CAAK,CAAA,CAAA,EAAc,CACjB,EAAA,CAAI,CAACD,CAAAA,EAAa,CAACC,CAAAA,CACjB,MAAM,IAAI,KAAA,CAAM,kDAAkD,CAAA,CAIpE,MAAO,CAAA,EAAA","file":"/home/runner/work/anzen/anzen/dist/chunk-UDCVGQMH.cjs","sourcesContent":["export function isPromise<T>(\n value: unknown\n): value is Promise<T> | PromiseLike<T> {\n return (\n value !== null &&\n (typeof value === 'object' || typeof value === 'function') &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof (value as any).then === 'function'\n )\n}\n\nexport function assertsSyncOperation<T>(\n value: T | Promise<T> | PromiseLike<T>,\n message: string\n): asserts value is T {\n if (isPromise<T>(value)) {\n throw new Error(message)\n }\n}\n\nexport function createLogger(debug: boolean = false) {\n const shouldLog = debug || process.env.NODE_ENV !== 'production'\n return {\n info: (message: string, ...rest: unknown[]): void => {\n if (shouldLog) {\n console.log(message, ...rest)\n }\n },\n error: (message: string, ...rest: unknown[]): void => {\n if (shouldLog) {\n console.error(message, ...rest)\n }\n },\n warn: (message: string, ...rest: unknown[]): void => {\n if (shouldLog) {\n console.warn(message, ...rest)\n }\n },\n }\n}\n\nexport function createExecutionClock() {\n let startTime: number | null = null\n let endTime: number | null = null\n\n return {\n start: (): void => {\n startTime = performance.now()\n },\n stop: (): void => {\n if (startTime === null) {\n throw new Error('Execution clock was not started.')\n }\n endTime = performance.now()\n },\n get: (): string => {\n if (!startTime || !endTime) {\n throw new Error('Execution clock has not been started or stopped.')\n }\n\n const duration = endTime - startTime\n return `${duration.toFixed(2)}ms`\n },\n }\n}\n"]}
|