@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 CHANGED
@@ -1,11 +1,15 @@
1
- A flexible, framework validation agnostic, type-safe factory for creating Next.JS App Router route handlers.
1
+ [![npm](https://img.shields.io/npm/v/%40sugardarius%2Fanzen?style=flat&labelColor=101010&color=FFC799)](https://www.npmjs.com/package/@sugardarius/anzen)
2
+ [![size](https://img.shields.io/bundlephobia/minzip/%40sugardarius%2Fanzen?style=flat&labelColor=101010&label=size&color=FFC799)](https://bundlephobia.com/package/@sugardarius/anzen)
3
+ [![license](https://img.shields.io/github/license/sugardarius/anzen?style=flat&labelColor=101010&color=FFC799)](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 100kB unpacked.
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
- * 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
- ### 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
- async (ctx, req: NextRequest) => {
136
- console.log('pathname', req.nextUrl.pathname)
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
- #### `authorize?: AuthFunction<AC>`
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 { createSafeRouteHandler } from '@sugardarius/anzen'
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 const GET = createSafeRouteHandler(
64
+ export default createSafePageServerComponent(
173
65
  {
174
- authorize: async ({ req, url }) => {
175
- console.log('url', url)
176
- const session = await auth.getSession(req)
66
+ authorize: async ({ segments }) => {
67
+ const session = await auth.getSession()
177
68
  if (!session) {
178
- return new Response(null, { status: 401 })
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
- accountId: z.string(),
234
- projectId: z.string().optional(),
75
+ id: string,
76
+ },
77
+ searchParams: {
78
+ page: number,
235
79
  },
236
80
  },
237
- async ({ segments }) => {
238
- return Response.json({ segments })
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
- #### `onSegmentsValidationErrorResponse?: OnValidationErrorResponse`
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 { createSafeRouteHandler } from '@sugardarius/anzen'
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 const GET = createSafeRouteHandler(
99
+ export default createSafeLayoutServerComponent(
252
100
  {
253
101
  segments: {
254
102
  accountId: z.string(),
255
- projectId: z.string().optional(),
256
103
  },
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
- },
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
- ```tsx
295
- import { string, numeric, optional } from 'decoders'
296
- import { createSafeRouteHandler } from '@sugardarius/anzen'
110
+ const hasAccess = await checkAccountAccess(
111
+ session.user.id,
112
+ segments.accountId
113
+ )
114
+ if (!hasAccess) {
115
+ notFound()
116
+ }
297
117
 
298
- export const GET = createSafeRouteHandler(
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 ({ searchParams }) => {
309
- return Response.json({ searchParams })
310
- }
311
- )
312
- ```
313
-
314
- #### `body?: TBody`
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
- > When validating the body the request is cloned to let you consume the body in the original request (e.g second arguments of handler function).
345
-
346
- #### `onBodyValidationErrorResponse?: OnValidationErrorResponse`
132
+ ## Framework validation agnostic
347
133
 
348
- Callback triggered when body validation returned issues. By default it returns a simple `400` response and issues are logged into the console.
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
- body: z.object({
357
- name: z.string(),
358
- model: z.string(),
359
- apiKey: z.string(),
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 { createSafeRouteHandler } from '@sugardarius/anzen'
161
+ import { string, number } from 'decoders'
162
+ import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
386
163
 
387
- export const POST = createSafeRouteHandler(
164
+ export default createSafePageServerComponent(
388
165
  {
389
- formData: {
390
- id: z.string(),
391
- message: z.string(),
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 ({ formData }) => {
395
- return Response.json({ formData })
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
- > When validating the form data the request is cloned to let you consume the form data in the original request (e.g second arguments of handler function).
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
- export const POST = createSafeRouteHandler(
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
- ### Error handling
187
+ If you define an async validation then the factory will throw an error.
427
188
 
428
- By design the factory will catch any error thrown in the route handler will return a simple response with `500` status.
189
+ ## Fair use note
429
190
 
430
- You can customize the error response if you want to fine tune error response management.
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
- import { createSafeRouteHandler } from '@sugardarius/anzen'
434
- import { HttpError, DbUnknownError } from '~/lib/errors'
435
- import { db } from '~/lib/db'
436
-
437
- export const GET = createSafeRouteHandler(
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
- return Response.json({ data })
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
- import { createSafeRouteHandler } from '@sugardarius/anzen'
471
-
472
- export const GET = createSafeRouteHandler({}, async (ctx, req) => {
473
- console.log('integrity', req.integrity)
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
- ## Fair use note
479
-
480
- Please note that if you're not using any of the proposed options in `createSafeRouteHandler` it means you're surely don't need it.
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 const GET = createSafeRouteHandler({}, async () => {
485
- return new Response(null, { status: 200 })
224
+ export default createSafeLayoutServerComponent({}, async ({ children }) => {
225
+ return <div>{children}</div>
486
226
  })
487
227
 
488
- // is equal to declare the route handler this way ๐Ÿ‘‡๐Ÿป
489
- export function GET() {
490
- return new Response(null, { status: 200 })
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 factory ๐Ÿ™‚
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 factory `createSafeRouteHandler` requires Next.js `v14` or `v15` and typescript `v5` as peer dependencies.
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"]}