hono-takibi 0.9.9991 → 0.9.9993
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 +372 -105
- package/dist/config/index.d.ts +253 -245
- package/dist/config/index.js +61 -59
- package/dist/core/angular-query/index.d.ts +1 -1
- package/dist/core/angular-query/index.js +1 -1
- package/dist/core/docs/index.d.ts +1 -1
- package/dist/core/docs/index.js +1 -1
- package/dist/core/preact-query/index.d.ts +1 -1
- package/dist/core/preact-query/index.js +1 -1
- package/dist/core/rpc/index.d.ts +3 -2
- package/dist/core/rpc/index.js +26 -9
- package/dist/core/solid-query/index.d.ts +1 -1
- package/dist/core/solid-query/index.js +1 -1
- package/dist/core/svelte-query/index.d.ts +1 -1
- package/dist/core/svelte-query/index.js +1 -1
- package/dist/core/swr/index.d.ts +1 -1
- package/dist/core/swr/index.js +1 -1
- package/dist/core/tanstack-query/index.d.ts +1 -1
- package/dist/core/tanstack-query/index.js +1 -1
- package/dist/core/type/index.d.ts +1 -1
- package/dist/core/type/index.js +3 -2
- package/dist/core/vue-query/index.d.ts +1 -1
- package/dist/core/vue-query/index.js +2 -4
- package/dist/{docs-CSuOwViw.js → docs-nOKRf8eg.js} +1 -1
- package/dist/generator/zod-openapi-hono/openapi/index.d.ts +1 -1
- package/dist/generator/zod-openapi-hono/openapi/index.js +1 -1
- package/dist/{guard-CGdVjfki.js → guard-D6ntlNcR.js} +11 -1
- package/dist/{index-BDpZiPrC.d.ts → index-BOXAWTim.d.ts} +105 -8
- package/dist/index.js +36 -36
- package/dist/{openapi-wpzLwSa_.js → openapi-COY6p6RQ.js} +17 -23
- package/dist/{openapi-Bb4UFNO2.js → openapi-XWXCf9Ne.js} +903 -133
- package/dist/{query-74e6a3Ys.js → query-BdYoX_G6.js} +191 -203
- package/dist/{rpc-0ionzZZB.js → rpc-D48XeCBk.js} +1 -1
- package/dist/{utils-aOXRa2YU.js → utils-D2uh73oq.js} +15 -1
- package/dist/vite-plugin/index.js +9 -9
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -102,70 +102,6 @@ export const getRoute = createRoute({
|
|
|
102
102
|
})
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
## Custom Validation Error Messages
|
|
106
|
-
|
|
107
|
-
Use `x-*` vendor extensions to customize Zod error messages:
|
|
108
|
-
|
|
109
|
-
```yaml
|
|
110
|
-
name:
|
|
111
|
-
type: string
|
|
112
|
-
minLength: 1
|
|
113
|
-
x-error-message: 'Name is required'
|
|
114
|
-
x-minimum-message: 'Name cannot be empty'
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
// Generated output
|
|
119
|
-
z.string({ error: 'Name is required' }).min(1, { error: 'Name cannot be empty' })
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
| Extension | Applies to |
|
|
123
|
-
| ----------------------------- | ----------------------------------------------------------------- |
|
|
124
|
-
| `x-error-message` | Schema constructor (`z.string()`, `z.number()`, `z.enum()`, etc.) |
|
|
125
|
-
| `x-minimum-message` | `.min()`, `.gte()` |
|
|
126
|
-
| `x-maximum-message` | `.max()`, `.lte()` |
|
|
127
|
-
| `x-size-message` | `.length()` |
|
|
128
|
-
| `x-pattern-message` | `.regex()` |
|
|
129
|
-
| `x-multipleOf-message` | `.multipleOf()` |
|
|
130
|
-
| `x-enum-error-messages` | Per-value enum messages (`{ "value": "message" }`) |
|
|
131
|
-
| `x-anyOf-message` | `anyOf` |
|
|
132
|
-
| `x-oneOf-message` | `oneOf` |
|
|
133
|
-
| `x-not-message` | `not` |
|
|
134
|
-
| `x-propertyNames-message` | `propertyNames` |
|
|
135
|
-
| `x-dependentRequired-message` | `dependentRequired` |
|
|
136
|
-
|
|
137
|
-
## Branded Types
|
|
138
|
-
|
|
139
|
-
Use the `x-brand` vendor extension to generate [Zod branded types](https://zod.dev/api?id=branded-types), creating nominal types that are structurally identical but semantically distinct:
|
|
140
|
-
|
|
141
|
-
```yaml
|
|
142
|
-
components:
|
|
143
|
-
schemas:
|
|
144
|
-
Cat:
|
|
145
|
-
type: object
|
|
146
|
-
properties:
|
|
147
|
-
name:
|
|
148
|
-
type: string
|
|
149
|
-
required:
|
|
150
|
-
- name
|
|
151
|
-
x-brand: Cat
|
|
152
|
-
Dog:
|
|
153
|
-
type: object
|
|
154
|
-
properties:
|
|
155
|
-
name:
|
|
156
|
-
type: string
|
|
157
|
-
required:
|
|
158
|
-
- name
|
|
159
|
-
x-brand: Dog
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
```ts
|
|
163
|
-
// Generated output
|
|
164
|
-
const CatSchema = z.object({ name: z.string() }).brand<'Cat'>().openapi('Cat')
|
|
165
|
-
|
|
166
|
-
const DogSchema = z.object({ name: z.string() }).brand<'Dog'>().openapi('Dog')
|
|
167
|
-
```
|
|
168
|
-
|
|
169
105
|
## Vite Plugin
|
|
170
106
|
|
|
171
107
|
Watches your OpenAPI spec and `hono-takibi.config.ts` for changes, then auto-regenerates code on save.
|
|
@@ -210,8 +146,6 @@ This generates:
|
|
|
210
146
|
|
|
211
147
|
Re-running after updating your OpenAPI spec is safe — your hand-written handler logic and test customizations are preserved. Only new routes are added as stubs.
|
|
212
148
|
|
|
213
|
-
> **Note:** If you remove a path from your OpenAPI spec and re-run, the corresponding handler and test files will be deleted. Make sure to back up or migrate any custom logic before removing API definitions.
|
|
214
|
-
|
|
215
149
|
### Handler Generation Modes
|
|
216
150
|
|
|
217
151
|
#### `routeHandler: false` (default)
|
|
@@ -274,10 +208,6 @@ Supported: SWR, TanStack Query, Preact Query, Solid Query, Vue Query, Svelte Que
|
|
|
274
208
|
```ts
|
|
275
209
|
export default defineConfig({
|
|
276
210
|
input: 'openapi.yaml',
|
|
277
|
-
'zod-openapi': {
|
|
278
|
-
output: './src/routes.ts',
|
|
279
|
-
exportSchemas: true,
|
|
280
|
-
},
|
|
281
211
|
'tanstack-query': {
|
|
282
212
|
output: './src/tanstack-query',
|
|
283
213
|
import: '../client',
|
|
@@ -305,9 +235,6 @@ paths:
|
|
|
305
235
|
```ts
|
|
306
236
|
export default defineConfig({
|
|
307
237
|
input: 'openapi.yaml',
|
|
308
|
-
'zod-openapi': {
|
|
309
|
-
output: './src/routes.ts',
|
|
310
|
-
},
|
|
311
238
|
test: {
|
|
312
239
|
output: './src/test.ts',
|
|
313
240
|
import: '../index',
|
|
@@ -321,10 +248,6 @@ export default defineConfig({
|
|
|
321
248
|
```ts
|
|
322
249
|
export default defineConfig({
|
|
323
250
|
input: 'openapi.yaml',
|
|
324
|
-
'zod-openapi': {
|
|
325
|
-
output: './src/routes.ts',
|
|
326
|
-
readonly: true,
|
|
327
|
-
},
|
|
328
251
|
mock: {
|
|
329
252
|
output: './src/mock.ts',
|
|
330
253
|
},
|
|
@@ -338,10 +261,6 @@ Generate API reference Markdown with [hono-cli](https://github.com/honojs/cli) `
|
|
|
338
261
|
```ts
|
|
339
262
|
export default defineConfig({
|
|
340
263
|
input: 'openapi.yaml',
|
|
341
|
-
'zod-openapi': {
|
|
342
|
-
output: './src/routes.ts',
|
|
343
|
-
readonly: true,
|
|
344
|
-
},
|
|
345
264
|
docs: {
|
|
346
265
|
output: './docs/api.md',
|
|
347
266
|
entry: 'src/index.ts',
|
|
@@ -354,10 +273,6 @@ To generate `curl` commands instead of `hono request`:
|
|
|
354
273
|
```ts
|
|
355
274
|
export default defineConfig({
|
|
356
275
|
input: 'openapi.yaml',
|
|
357
|
-
'zod-openapi': {
|
|
358
|
-
output: './src/routes.ts',
|
|
359
|
-
readonly: true,
|
|
360
|
-
},
|
|
361
276
|
docs: {
|
|
362
277
|
output: './docs/api.md',
|
|
363
278
|
curl: true,
|
|
@@ -368,10 +283,6 @@ export default defineConfig({
|
|
|
368
283
|
|
|
369
284
|
## Full Config Reference
|
|
370
285
|
|
|
371
|
-
> `split: true` - `output` is a **directory** (many files + `index.ts`).
|
|
372
|
-
> `split` omitted or `false` - `output` is a **single `.ts` file**.
|
|
373
|
-
> `output` and `routes` are **mutually exclusive** in `zod-openapi`.
|
|
374
|
-
|
|
375
286
|
```ts
|
|
376
287
|
// hono-takibi.config.ts
|
|
377
288
|
import { defineConfig } from 'hono-takibi/config'
|
|
@@ -422,7 +333,14 @@ export default defineConfig({
|
|
|
422
333
|
routes: {
|
|
423
334
|
output: './src/routes',
|
|
424
335
|
split: true,
|
|
425
|
-
import: '@packages/routes',
|
|
336
|
+
import: '@packages/routes',
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// Split webhooks into separate files
|
|
340
|
+
webhooks: {
|
|
341
|
+
output: './src/webhooks',
|
|
342
|
+
split: true,
|
|
343
|
+
import: '@packages/webhooks',
|
|
426
344
|
},
|
|
427
345
|
|
|
428
346
|
// Split components into separate files
|
|
@@ -486,11 +404,6 @@ export default defineConfig({
|
|
|
486
404
|
split: true,
|
|
487
405
|
import: '../mediaTypes',
|
|
488
406
|
},
|
|
489
|
-
webhooks: {
|
|
490
|
-
output: './src/webhooks',
|
|
491
|
-
split: true,
|
|
492
|
-
import: '../webhooks',
|
|
493
|
-
},
|
|
494
407
|
},
|
|
495
408
|
},
|
|
496
409
|
|
|
@@ -575,22 +488,376 @@ export default defineConfig({
|
|
|
575
488
|
})
|
|
576
489
|
```
|
|
577
490
|
|
|
578
|
-
##
|
|
491
|
+
## Custom Validation Error Messages
|
|
579
492
|
|
|
580
|
-
|
|
493
|
+
Use `x-*` vendor extensions to attach custom Zod error messages, with **one extension per JSON Schema keyword** (1:1 mapping). The extension name follows the pattern `x-<jsonSchemaKeyword>-message` (e.g. `x-minLength-message`, `x-pattern-message`), plus four generic forms: `x-error-message`, `x-required-message`, `x-const-message`, `x-enum-message`.
|
|
494
|
+
|
|
495
|
+
```yaml
|
|
496
|
+
name:
|
|
497
|
+
type: string
|
|
498
|
+
minLength: 1
|
|
499
|
+
maxLength: 50
|
|
500
|
+
x-error-message: 'Name must be a string'
|
|
501
|
+
x-minLength-message: 'Name cannot be empty'
|
|
502
|
+
x-maxLength-message: 'Name must be at most 50 characters'
|
|
503
|
+
```
|
|
581
504
|
|
|
582
|
-
|
|
505
|
+
```ts
|
|
506
|
+
z.string({ error: 'Name must be a string' })
|
|
507
|
+
.min(1, { error: 'Name cannot be empty' })
|
|
508
|
+
.max(50, { error: 'Name must be at most 50 characters' })
|
|
509
|
+
```
|
|
583
510
|
|
|
584
|
-
|
|
511
|
+
### Extension Reference
|
|
512
|
+
|
|
513
|
+
All custom message extensions follow the `x-<keyword>-message` naming convention and map directly to Zod validator error messages.
|
|
514
|
+
|
|
515
|
+
#### Common (any schema type)
|
|
516
|
+
|
|
517
|
+
| Extension | Applies to |
|
|
518
|
+
| -------------------- | -------------------------------- |
|
|
519
|
+
| `x-error-message` | All schemas (top-level fallback) |
|
|
520
|
+
| `x-required-message` | Required properties |
|
|
521
|
+
| `x-const-message` | `const` |
|
|
522
|
+
| `x-enum-message` | `enum` |
|
|
523
|
+
|
|
524
|
+
#### Numeric (number / integer)
|
|
525
|
+
|
|
526
|
+
| Extension | Applies to |
|
|
527
|
+
| ---------------------------- | ------------------ |
|
|
528
|
+
| `x-minimum-message` | `minimum` |
|
|
529
|
+
| `x-maximum-message` | `maximum` |
|
|
530
|
+
| `x-exclusiveMinimum-message` | `exclusiveMinimum` |
|
|
531
|
+
| `x-exclusiveMaximum-message` | `exclusiveMaximum` |
|
|
532
|
+
| `x-multipleOf-message` | `multipleOf` |
|
|
533
|
+
|
|
534
|
+
#### String
|
|
535
|
+
|
|
536
|
+
| Extension | Applies to |
|
|
537
|
+
| --------------------- | ------------------------------------------ |
|
|
538
|
+
| `x-minLength-message` | `minLength` |
|
|
539
|
+
| `x-maxLength-message` | `maxLength` |
|
|
540
|
+
| `x-pattern-message` | `pattern` |
|
|
541
|
+
| `x-length-message` | Exact length (`minLength` === `maxLength`) |
|
|
542
|
+
|
|
543
|
+
#### Array
|
|
544
|
+
|
|
545
|
+
| Extension | Applies to |
|
|
546
|
+
| ----------------------- | ------------- |
|
|
547
|
+
| `x-minItems-message` | `minItems` |
|
|
548
|
+
| `x-maxItems-message` | `maxItems` |
|
|
549
|
+
| `x-uniqueItems-message` | `uniqueItems` |
|
|
550
|
+
| `x-contains-message` | `contains` |
|
|
551
|
+
| `x-minContains-message` | `minContains` |
|
|
552
|
+
| `x-maxContains-message` | `maxContains` |
|
|
553
|
+
|
|
554
|
+
#### Object
|
|
555
|
+
|
|
556
|
+
| Extension | Applies to |
|
|
557
|
+
| -------------------------------- | ---------------------- |
|
|
558
|
+
| `x-minProperties-message` | `minProperties` |
|
|
559
|
+
| `x-maxProperties-message` | `maxProperties` |
|
|
560
|
+
| `x-additionalProperties-message` | `additionalProperties` |
|
|
561
|
+
| `x-propertyNames-message` | `propertyNames` |
|
|
562
|
+
| `x-patternProperties-message` | `patternProperties` |
|
|
563
|
+
| `x-dependentRequired-message` | `dependentRequired` |
|
|
564
|
+
| `x-dependentSchemas-message` | `dependentSchemas` |
|
|
565
|
+
|
|
566
|
+
#### Combinators
|
|
567
|
+
|
|
568
|
+
| Extension | Applies to |
|
|
569
|
+
| ----------------- | ---------- |
|
|
570
|
+
| `x-allOf-message` | `allOf` |
|
|
571
|
+
| `x-anyOf-message` | `anyOf` |
|
|
572
|
+
| `x-oneOf-message` | `oneOf` |
|
|
573
|
+
| `x-not-message` | `not` |
|
|
574
|
+
|
|
575
|
+
#### Conditional
|
|
576
|
+
|
|
577
|
+
| Extension | Applies to |
|
|
578
|
+
| ---------------- | ---------- |
|
|
579
|
+
| `x-if-message` | `if` |
|
|
580
|
+
| `x-then-message` | `then` |
|
|
581
|
+
| `x-else-message` | `else` |
|
|
582
|
+
|
|
583
|
+
#### Typeless / Array Applicator
|
|
584
|
+
|
|
585
|
+
| Extension | Applies to |
|
|
586
|
+
| --------------------------------- | ------------------------------- |
|
|
587
|
+
| `x-properties-message` | `properties` (typeless schemas) |
|
|
588
|
+
| `x-prefixItems-message` | `prefixItems` |
|
|
589
|
+
| `x-items-message` | `items` |
|
|
590
|
+
| `x-unevaluatedProperties-message` | `unevaluatedProperties` |
|
|
591
|
+
| `x-unevaluatedItems-message` | `unevaluatedItems` |
|
|
592
|
+
|
|
593
|
+
## Behavior Extensions
|
|
594
|
+
|
|
595
|
+
### String Pre-validation Transforms
|
|
596
|
+
|
|
597
|
+
| Extension | Generated | Value |
|
|
598
|
+
| --------------- | ----------------------------- | --------------------------------------- |
|
|
599
|
+
| `x-trim` | `z.string().trim()` | `true` |
|
|
600
|
+
| `x-toLowerCase` | `z.string().toLowerCase()` | `true` |
|
|
601
|
+
| `x-toUpperCase` | `z.string().toUpperCase()` | `true` |
|
|
602
|
+
| `x-normalize` | `z.string().normalize('NFC')` | `'NFC'` / `'NFD'` / `'NFKC'` / `'NFKD'` |
|
|
585
603
|
|
|
586
|
-
|
|
587
|
-
|
|
604
|
+
```yaml
|
|
605
|
+
homepage:
|
|
606
|
+
type: string
|
|
607
|
+
format: url
|
|
608
|
+
x-trim: true
|
|
609
|
+
```
|
|
588
610
|
|
|
589
|
-
|
|
611
|
+
```ts
|
|
612
|
+
z.string().trim().pipe(z.url())
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Preprocess (Input Normalization)
|
|
616
|
+
|
|
617
|
+
#### `x-preprocess`
|
|
618
|
+
|
|
619
|
+
```yaml
|
|
620
|
+
username:
|
|
621
|
+
type: string
|
|
622
|
+
x-preprocess: 'z.preprocess((val) => typeof val === "string" ? val.trim() : val, z.string())'
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
z.preprocess((val) => (typeof val === 'string' ? val.trim() : val), z.string())
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Type Coercion
|
|
630
|
+
|
|
631
|
+
#### `x-coerce`
|
|
632
|
+
|
|
633
|
+
```yaml
|
|
634
|
+
asNumber:
|
|
635
|
+
type: number
|
|
636
|
+
x-coerce: true
|
|
637
|
+
asDate:
|
|
638
|
+
type: string
|
|
639
|
+
format: date-time
|
|
640
|
+
x-coerce: true
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
```ts
|
|
644
|
+
z.coerce.number()
|
|
645
|
+
z.coerce.date()
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Codec (Bidirectional Transform)
|
|
649
|
+
|
|
650
|
+
#### `x-codec`
|
|
651
|
+
|
|
652
|
+
```yaml
|
|
653
|
+
updatedAt:
|
|
654
|
+
type: string
|
|
655
|
+
format: date-time
|
|
656
|
+
x-codec: 'z.codec(z.iso.datetime(), z.date(), { decode: (val) => new Date(val), encode: (val) => val.toISOString() })'
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
```ts
|
|
660
|
+
z.codec(z.iso.datetime(), z.date(), {
|
|
661
|
+
decode: (val) => new Date(val),
|
|
662
|
+
encode: (val) => val.toISOString(),
|
|
663
|
+
})
|
|
664
|
+
```
|
|
590
665
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
-
|
|
666
|
+
### Custom Validation
|
|
667
|
+
|
|
668
|
+
#### `x-refine`
|
|
669
|
+
|
|
670
|
+
```yaml
|
|
671
|
+
password:
|
|
672
|
+
type: string
|
|
673
|
+
x-refine: '.refine((val) => val.length >= 8, { message: "Password must be at least 8 characters" }).refine((val) => /[A-Z]/.test(val), { message: "Password must contain an uppercase letter" })'
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
z.string()
|
|
678
|
+
.refine((val) => val.length >= 8, { message: 'Password must be at least 8 characters' })
|
|
679
|
+
.refine((val) => /[A-Z]/.test(val), { message: 'Password must contain an uppercase letter' })
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
#### `x-superRefine`
|
|
683
|
+
|
|
684
|
+
```yaml
|
|
685
|
+
normalizedEmail:
|
|
686
|
+
type: string
|
|
687
|
+
format: email
|
|
688
|
+
x-superRefine: '.superRefine((val, ctx) => { if (val.endsWith("@blocked.example")) ctx.addIssue({ code: "custom", message: "Blocked domain" }) })'
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
```ts
|
|
692
|
+
z.email().superRefine((val, ctx) => {
|
|
693
|
+
if (val.endsWith('@blocked.example')) {
|
|
694
|
+
ctx.addIssue({ code: 'custom', message: 'Blocked domain' })
|
|
695
|
+
}
|
|
696
|
+
})
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Transform & Pipe
|
|
700
|
+
|
|
701
|
+
#### `x-transform`
|
|
702
|
+
|
|
703
|
+
```yaml
|
|
704
|
+
code:
|
|
705
|
+
type: string
|
|
706
|
+
x-transform: 'z.string().transform((val) => val.toUpperCase())'
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
```ts
|
|
710
|
+
z.string().transform((val) => val.toUpperCase())
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
#### `x-pipe`
|
|
714
|
+
|
|
715
|
+
```yaml
|
|
716
|
+
port:
|
|
717
|
+
type: string
|
|
718
|
+
x-pipe: 'z.string().pipe(z.number().int().positive())'
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
z.string().pipe(z.number().int().positive())
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Default & Fallback Values
|
|
726
|
+
|
|
727
|
+
#### `x-prefault`
|
|
728
|
+
|
|
729
|
+
```yaml
|
|
730
|
+
greeting:
|
|
731
|
+
type: string
|
|
732
|
+
x-prefault: 'hello'
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
z.string().prefault('hello')
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
#### `x-catch`
|
|
740
|
+
|
|
741
|
+
```yaml
|
|
742
|
+
retries:
|
|
743
|
+
type: integer
|
|
744
|
+
x-catch: 0
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
z.int().catch(0)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
### Immutability
|
|
752
|
+
|
|
753
|
+
#### `x-freeze`
|
|
754
|
+
|
|
755
|
+
```yaml
|
|
756
|
+
config:
|
|
757
|
+
type: object
|
|
758
|
+
properties:
|
|
759
|
+
name:
|
|
760
|
+
type: string
|
|
761
|
+
x-freeze: true
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
z.object({ name: z.string() }).readonly()
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### String Content Checks
|
|
769
|
+
|
|
770
|
+
#### `x-startsWith` / `x-endsWith` / `x-includes`
|
|
771
|
+
|
|
772
|
+
```yaml
|
|
773
|
+
url:
|
|
774
|
+
type: string
|
|
775
|
+
x-startsWith: 'https://'
|
|
776
|
+
x-endsWith: '.com'
|
|
777
|
+
path:
|
|
778
|
+
type: string
|
|
779
|
+
x-includes: '/api/'
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
z.string().startsWith('https://').endsWith('.com')
|
|
784
|
+
z.string().includes('/api/')
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Format-Specific Options
|
|
788
|
+
|
|
789
|
+
```yaml
|
|
790
|
+
htmlEmail:
|
|
791
|
+
type: string
|
|
792
|
+
format: email
|
|
793
|
+
x-emailPattern: 'html5' # email pattern preset
|
|
794
|
+
uuidV7:
|
|
795
|
+
type: string
|
|
796
|
+
format: uuid
|
|
797
|
+
x-uuidVersion: v7 # uuid version
|
|
798
|
+
httpsUrl:
|
|
799
|
+
type: string
|
|
800
|
+
format: uri
|
|
801
|
+
x-urlProtocol: '^https$' # url protocol regex
|
|
802
|
+
x-urlNormalize: true
|
|
803
|
+
preciseDatetime:
|
|
804
|
+
type: string
|
|
805
|
+
format: date-time
|
|
806
|
+
x-isoPrecision: 3 # iso datetime precision / offset / local
|
|
807
|
+
x-isoOffset: true
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
| Extension | Maps to | Values |
|
|
811
|
+
| ---------------- | ------------------------------- | -------------------------------- |
|
|
812
|
+
| `x-emailPattern` | `z.email({ pattern })` | `html5` / `browser` / `unicode` |
|
|
813
|
+
| `x-emailRegex` | `z.email({ pattern: /.../ })` | custom regex string |
|
|
814
|
+
| `x-uuidVersion` | `z.uuid({ version })` | `v1` / `v4` / `v6` / `v7` / `v8` |
|
|
815
|
+
| `x-urlProtocol` | `z.url({ protocol: /.../ })` | regex string |
|
|
816
|
+
| `x-urlHostname` | `z.url({ hostname: /.../ })` | regex string |
|
|
817
|
+
| `x-urlNormalize` | `z.url({ normalize })` | `true` / `false` |
|
|
818
|
+
| `x-isoPrecision` | `z.iso.datetime({ precision })` | fractional second digits |
|
|
819
|
+
| `x-isoOffset` | `z.iso.datetime({ offset })` | `true` / `false` |
|
|
820
|
+
| `x-isoLocal` | `z.iso.datetime({ local })` | `true` / `false` |
|
|
821
|
+
| `x-macDelimiter` | `z.mac({ delimiter })` | `:` / `-` / `.` |
|
|
822
|
+
| `x-jwtAlg` | `z.jwt({ alg })` | `HS256` etc. |
|
|
823
|
+
| `x-hashAlg` | `z.hash(alg, ...)` | `sha256` etc. |
|
|
824
|
+
| `x-hashEnc` | `z.hash(alg, { enc })` | `hex` / `base64` / `base64url` |
|
|
825
|
+
|
|
826
|
+
## Branded Types
|
|
827
|
+
|
|
828
|
+
Use the `x-brand` vendor extension to generate [Zod branded types](https://zod.dev/api?id=branded-types), creating nominal types that are structurally identical but semantically distinct:
|
|
829
|
+
|
|
830
|
+
```yaml
|
|
831
|
+
components:
|
|
832
|
+
schemas:
|
|
833
|
+
Cat:
|
|
834
|
+
type: object
|
|
835
|
+
properties:
|
|
836
|
+
name:
|
|
837
|
+
type: string
|
|
838
|
+
required:
|
|
839
|
+
- name
|
|
840
|
+
x-brand: Cat
|
|
841
|
+
Dog:
|
|
842
|
+
type: object
|
|
843
|
+
properties:
|
|
844
|
+
name:
|
|
845
|
+
type: string
|
|
846
|
+
required:
|
|
847
|
+
- name
|
|
848
|
+
x-brand: Dog
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
```ts
|
|
852
|
+
// Generated output
|
|
853
|
+
const CatSchema = z.object({ name: z.string() }).brand<'Cat'>().openapi('Cat')
|
|
854
|
+
|
|
855
|
+
const DogSchema = z.object({ name: z.string() }).brand<'Dog'>().openapi('Dog')
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
## Projects Using Hono Takibi
|
|
859
|
+
|
|
860
|
+
- **[resend-local](https://github.com/y-hiraoka/resend-local)** — A local emulator for the Resend email API.
|
|
594
861
|
|
|
595
862
|
## Contributing
|
|
596
863
|
|