@sugardarius/anzen 1.1.3 → 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 CHANGED
@@ -1,4 +1,4 @@
1
- A flexible, framework validation agnostic, type-safe factory for creating Next.JS App Router route handlers.
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
- * 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' }
41
+ {
42
+ auth, // Auth context is inferred from the authorize function
43
+ body, // Body is inferred from the body validation
133
44
  },
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 })
45
+ req
46
+ ): Promise<Response> => {
47
+ return Response.json({ user: auth.user, body }, { status: 200 })
158
48
  }
159
49
  )
160
50
  ```
161
51
 
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.
52
+ ### Page Server Components
167
53
 
168
54
  ```tsx
169
- import { createSafeRouteHandler } from '@sugardarius/anzen'
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 const GET = createSafeRouteHandler(
60
+ export default createSafePageServerComponent(
173
61
  {
174
- authorize: async ({ req, url }) => {
175
- console.log('url', url)
176
- const session = await auth.getSession(req)
62
+ authorize: async ({ segments }) => {
63
+ const session = await auth.getSession()
177
64
  if (!session) {
178
- return new Response(null, { status: 401 })
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
- accountId: z.string(),
234
- projectId: z.string().optional(),
71
+ id: string,
72
+ },
73
+ searchParams: {
74
+ page: number,
235
75
  },
236
76
  },
237
- async ({ segments }) => {
238
- return Response.json({ segments })
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
- #### `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.
87
+ ### Layout Server Components
246
88
 
247
89
  ```tsx
248
90
  import { z } from 'zod'
249
- import { createSafeRouteHandler } from '@sugardarius/anzen'
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 const GET = createSafeRouteHandler(
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
- 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.
100
+ authorize: async ({ segments }) => {
101
+ const session = await auth.getSession()
102
+ if (!session) {
103
+ unauthorized()
104
+ }
293
105
 
294
- ```tsx
295
- import { string, numeric, optional } from 'decoders'
296
- import { createSafeRouteHandler } from '@sugardarius/anzen'
106
+ const hasAccess = await checkAccountAccess(
107
+ session.user.id,
108
+ segments.accountId
109
+ )
110
+ if (!hasAccess) {
111
+ notFound()
112
+ }
297
113
 
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 })
114
+ return { user: session.user }
306
115
  },
307
116
  },
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 })
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
- > 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`
128
+ ## Framework validation agnostic
347
129
 
348
- Callback triggered when body validation returned issues. By default it returns a simple `400` response and issues are logged into the console.
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
- body: z.object({
357
- name: z.string(),
358
- model: z.string(),
359
- apiKey: z.string(),
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 { createSafeRouteHandler } from '@sugardarius/anzen'
157
+ import { string, number } from 'decoders'
158
+ import { createSafePageServerComponent } from '@sugardarius/anzen/server-components'
386
159
 
387
- export const POST = createSafeRouteHandler(
160
+ export default createSafePageServerComponent(
388
161
  {
389
- formData: {
390
- id: z.string(),
391
- message: z.string(),
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 ({ formData }) => {
395
- return Response.json({ formData })
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
- > 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'
179
+ ## Synchronous validations
409
180
 
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
- ```
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
- ### Error handling
183
+ If you define an async validation then the factory will throw an error.
427
184
 
428
- By design the factory will catch any error thrown in the route handler will return a simple response with `500` status.
185
+ ## Fair use note
429
186
 
430
- You can customize the error response if you want to fine tune error response management.
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
- 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
- }
190
+ // Route handler
191
+ // Calling 👇🏻
192
+ export const GET = createSafeRouteHandler({}, async () => {
193
+ return new Response(null, { status: 200 })
194
+ })
459
195
 
460
- return Response.json({ data })
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
- 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 })
205
+ // Page server component
206
+ // Calling 👇🏻
207
+ export default createSafePageServerComponent({}, async () => {
208
+ return <div>Hello</div>
475
209
  })
476
- ```
477
210
 
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.
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 const GET = createSafeRouteHandler({}, async () => {
485
- return new Response(null, { status: 200 })
220
+ export default createSafeLayoutServerComponent({}, async ({ children }) => {
221
+ return <div>{children}</div>
486
222
  })
487
223
 
488
- // is equal to declare the route handler this way 👇🏻
489
- export function GET() {
490
- return new Response(null, { status: 200 })
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 factory 🙂
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 factory `createSafeRouteHandler` requires Next.js `v14` or `v15` and typescript `v5` as peer dependencies.
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"]}