@sugardarius/anzen 1.1.3 โ 2.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 +121 -379
- 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 +26 -10
package/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://www.npmjs.com/package/@sugardarius/anzen)
|
|
2
|
+
[](https://bundlephobia.com/package/@sugardarius/anzen)
|
|
3
|
+
[](https://github.com/SugarDarius/anzen/blob/main/LICENSE)
|
|
4
|
+
|
|
5
|
+
A fast, framework validation agnostic, type-safe factories for creating route handlers, page and layout server components files in Next.js.
|
|
2
6
|
|
|
3
7
|
- ๐ง Framework validation agnostic, use a validation library of your choice supporting [Standard Schema](https://standardschema.dev/).
|
|
4
8
|
- ๐ง Focused functionalities, use only features you want.
|
|
5
9
|
- ๐งน Clean and flexible API.
|
|
6
10
|
- ๐ Type-safe.
|
|
7
11
|
- ๐ฑ Dependency free.
|
|
8
|
-
- ๐ชถ Less than
|
|
12
|
+
- ๐ชถ Less than 140kB unpacked.
|
|
9
13
|
|
|
10
14
|
## Install
|
|
11
15
|
|
|
@@ -15,6 +19,8 @@ npm i @sugardarius/anzen
|
|
|
15
19
|
|
|
16
20
|
## Usage
|
|
17
21
|
|
|
22
|
+
### Route Handlers
|
|
23
|
+
|
|
18
24
|
```tsx
|
|
19
25
|
import { object, string, number } from 'decoders'
|
|
20
26
|
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
@@ -35,469 +41,205 @@ export const POST = createSafeRouteHandler(
|
|
|
35
41
|
bar: number,
|
|
36
42
|
}),
|
|
37
43
|
},
|
|
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
44
|
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' }
|
|
45
|
+
{
|
|
46
|
+
auth, // Auth context is inferred from the authorize function
|
|
47
|
+
body, // Body is inferred from the body validation
|
|
133
48
|
},
|
|
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 })
|
|
49
|
+
req
|
|
50
|
+
): Promise<Response> => {
|
|
51
|
+
return Response.json({ user: auth.user, body }, { status: 200 })
|
|
158
52
|
}
|
|
159
53
|
)
|
|
160
54
|
```
|
|
161
55
|
|
|
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.
|
|
56
|
+
### Page Server Components
|
|
167
57
|
|
|
168
58
|
```tsx
|
|
169
|
-
import {
|
|
59
|
+
import { object, string, number } from 'decoders'
|
|
60
|
+
import { unauthorized } from 'next/navigation'
|
|
61
|
+
import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
|
|
170
62
|
import { auth } from '~/lib/auth'
|
|
171
63
|
|
|
172
|
-
export
|
|
64
|
+
export default createSafePageServerComponent(
|
|
173
65
|
{
|
|
174
|
-
authorize: async ({
|
|
175
|
-
|
|
176
|
-
const session = await auth.getSession(req)
|
|
66
|
+
authorize: async ({ segments }) => {
|
|
67
|
+
const session = await auth.getSession()
|
|
177
68
|
if (!session) {
|
|
178
|
-
|
|
69
|
+
unauthorized()
|
|
179
70
|
}
|
|
180
71
|
|
|
181
72
|
return { user: session.user }
|
|
182
73
|
},
|
|
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
74
|
segments: {
|
|
233
|
-
|
|
234
|
-
|
|
75
|
+
id: string,
|
|
76
|
+
},
|
|
77
|
+
searchParams: {
|
|
78
|
+
page: number,
|
|
235
79
|
},
|
|
236
80
|
},
|
|
237
|
-
async ({
|
|
238
|
-
|
|
81
|
+
async ({
|
|
82
|
+
auth, // Auth context is inferred from the authorize function
|
|
83
|
+
segments, // Segments are inferred from the segments validation
|
|
84
|
+
searchParams, // Search params are inferred from the searchParams validation
|
|
85
|
+
}) => {
|
|
86
|
+
return <div>Hello {auth.user.name}!</div>
|
|
239
87
|
}
|
|
240
88
|
)
|
|
241
89
|
```
|
|
242
90
|
|
|
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.
|
|
91
|
+
### Layout Server Components
|
|
246
92
|
|
|
247
93
|
```tsx
|
|
248
94
|
import { z } from 'zod'
|
|
249
|
-
import {
|
|
95
|
+
import { createSafeLayoutServerComponent } from '@sugardarius/anzen/server-components'
|
|
96
|
+
import { auth } from '~/lib/auth'
|
|
97
|
+
import { notFound, unauthorized } from 'next/navigation'
|
|
250
98
|
|
|
251
|
-
export
|
|
99
|
+
export default createSafeLayoutServerComponent(
|
|
252
100
|
{
|
|
253
101
|
segments: {
|
|
254
102
|
accountId: z.string(),
|
|
255
|
-
projectId: z.string().optional(),
|
|
256
103
|
},
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
},
|
|
283
|
-
},
|
|
284
|
-
async ({ searchParams }) => {
|
|
285
|
-
return Response.json({ searchParams })
|
|
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.
|
|
104
|
+
authorize: async ({ segments }) => {
|
|
105
|
+
const session = await auth.getSession()
|
|
106
|
+
if (!session) {
|
|
107
|
+
unauthorized()
|
|
108
|
+
}
|
|
293
109
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
110
|
+
const hasAccess = await checkAccountAccess(
|
|
111
|
+
session.user.id,
|
|
112
|
+
segments.accountId
|
|
113
|
+
)
|
|
114
|
+
if (!hasAccess) {
|
|
115
|
+
notFound()
|
|
116
|
+
}
|
|
297
117
|
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
searchParams: {
|
|
301
|
-
query: string,
|
|
302
|
-
page: optional(numeric),
|
|
303
|
-
},
|
|
304
|
-
onSearchParamsValidationErrorResponse: (issues) => {
|
|
305
|
-
return Response.json({ issues }, { status: 400 })
|
|
118
|
+
return { user: session.user }
|
|
306
119
|
},
|
|
307
120
|
},
|
|
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 })
|
|
121
|
+
async ({ auth, segments, children }) => {
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
<header>Account: {segments.accountId}</header>
|
|
125
|
+
{children}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
340
128
|
}
|
|
341
129
|
)
|
|
342
130
|
```
|
|
343
131
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
#### `onBodyValidationErrorResponse?: OnValidationErrorResponse`
|
|
132
|
+
## Framework validation agnostic
|
|
347
133
|
|
|
348
|
-
|
|
134
|
+
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
135
|
|
|
350
136
|
```tsx
|
|
137
|
+
// Route handler example
|
|
351
138
|
import { z } from 'zod'
|
|
139
|
+
import { object, string, number } from 'decoders'
|
|
352
140
|
import { createSafeRouteHandler } from '@sugardarius/anzen'
|
|
353
141
|
|
|
354
142
|
export const POST = createSafeRouteHandler(
|
|
355
143
|
{
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
144
|
+
// `zod` for segments dictionary validation
|
|
145
|
+
segments: { id: z.string() },
|
|
146
|
+
// `decoders` for body object validation
|
|
147
|
+
body: object({
|
|
148
|
+
id: number,
|
|
149
|
+
name: string,
|
|
360
150
|
}),
|
|
361
|
-
onBodyValidationErrorResponse: (issues) => {
|
|
362
|
-
return Response.json({ issues }, { status: 400 })
|
|
363
|
-
},
|
|
364
151
|
},
|
|
365
|
-
async ({ body }) => {
|
|
366
|
-
return Response.json({ body })
|
|
152
|
+
async ({ segments, body }) => {
|
|
153
|
+
return Response.json({ segments, body })
|
|
367
154
|
}
|
|
368
155
|
)
|
|
369
156
|
```
|
|
370
157
|
|
|
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
158
|
```tsx
|
|
159
|
+
// Page server component example
|
|
384
160
|
import { z } from 'zod'
|
|
385
|
-
import {
|
|
161
|
+
import { string, number } from 'decoders'
|
|
162
|
+
import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
|
|
386
163
|
|
|
387
|
-
export
|
|
164
|
+
export default createSafePageServerComponent(
|
|
388
165
|
{
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
166
|
+
// `zod` for segments dictionary validation
|
|
167
|
+
segments: { id: z.string() },
|
|
168
|
+
// `decoders` for search params dictionary validation
|
|
169
|
+
searchParams: {
|
|
170
|
+
page: number,
|
|
392
171
|
},
|
|
393
172
|
},
|
|
394
|
-
async ({
|
|
395
|
-
return
|
|
173
|
+
async ({ segments, searchParams }) => {
|
|
174
|
+
return (
|
|
175
|
+
<div>
|
|
176
|
+
Race {segments.id} - Page {searchParams.page}
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
396
179
|
}
|
|
397
180
|
)
|
|
398
181
|
```
|
|
399
182
|
|
|
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'
|
|
183
|
+
## Synchronous validations
|
|
409
184
|
|
|
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
|
-
```
|
|
185
|
+
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
186
|
|
|
426
|
-
|
|
187
|
+
If you define an async validation then the factory will throw an error.
|
|
427
188
|
|
|
428
|
-
|
|
189
|
+
## Fair use note
|
|
429
190
|
|
|
430
|
-
|
|
191
|
+
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
192
|
|
|
432
193
|
```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
|
-
}
|
|
194
|
+
// Route handler
|
|
195
|
+
// Calling ๐๐ป
|
|
196
|
+
export const GET = createSafeRouteHandler({}, async () => {
|
|
197
|
+
return new Response(null, { status: 200 })
|
|
198
|
+
})
|
|
459
199
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
)
|
|
200
|
+
// is equal to declare the route handler this way ๐๐ป
|
|
201
|
+
export function GET() {
|
|
202
|
+
return new Response(null, { status: 200 })
|
|
203
|
+
}
|
|
204
|
+
// except `createSafeRouteHandler` will provide by default a native error catching
|
|
205
|
+
// and will return a `500` response. That's the only advantage.
|
|
463
206
|
```
|
|
464
207
|
|
|
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
208
|
```tsx
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
export
|
|
473
|
-
|
|
474
|
-
return new Response(null, { status: 200 })
|
|
209
|
+
// Page server component
|
|
210
|
+
// Calling ๐๐ป
|
|
211
|
+
export default createSafePageServerComponent({}, async () => {
|
|
212
|
+
return <div>Hello</div>
|
|
475
213
|
})
|
|
476
|
-
```
|
|
477
214
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
215
|
+
// is equal to declare the page server component this way ๐๐ป
|
|
216
|
+
export default async function Page() {
|
|
217
|
+
return <div>Hello</div>
|
|
218
|
+
}
|
|
219
|
+
```
|
|
481
220
|
|
|
482
221
|
```tsx
|
|
222
|
+
// Layout server component
|
|
483
223
|
// Calling ๐๐ป
|
|
484
|
-
export
|
|
485
|
-
return
|
|
224
|
+
export default createSafeLayoutServerComponent({}, async ({ children }) => {
|
|
225
|
+
return <div>{children}</div>
|
|
486
226
|
})
|
|
487
227
|
|
|
488
|
-
// is equal to declare the
|
|
489
|
-
export function
|
|
490
|
-
|
|
228
|
+
// is equal to declare the layout server component this way ๐๐ป
|
|
229
|
+
export default async function Layout({
|
|
230
|
+
children,
|
|
231
|
+
}: {
|
|
232
|
+
children: React.ReactNode
|
|
233
|
+
}) {
|
|
234
|
+
return <div>{children}</div>
|
|
491
235
|
}
|
|
492
|
-
// excepts `createSafeRouteHandler` will provide by default a native error catching
|
|
493
|
-
// and will return a `500` response. That's the only advantage.
|
|
494
236
|
```
|
|
495
237
|
|
|
496
|
-
Feel free to open an issue or a PR if you think a relevant option could be added into the
|
|
238
|
+
Feel free to open an issue or a PR if you think a relevant option could be added into the factories ๐
|
|
497
239
|
|
|
498
240
|
## Requirements
|
|
499
241
|
|
|
500
|
-
The
|
|
242
|
+
The factories require Next.js `v14`, `v15` or `v16` and typescript `v5` as peer dependencies.
|
|
501
243
|
|
|
502
244
|
## Contributing
|
|
503
245
|
|
|
@@ -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"]}
|