@kubb/plugin-zod 5.0.0-alpha.9 → 5.0.0-beta.15
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/LICENSE +17 -10
- package/README.md +25 -7
- package/dist/index.cjs +1129 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +369 -4
- package/dist/index.js +1116 -105
- package/dist/index.js.map +1 -1
- package/extension.yaml +505 -0
- package/package.json +43 -73
- package/src/components/Operations.tsx +25 -18
- package/src/components/Zod.tsx +21 -121
- package/src/constants.ts +5 -0
- package/src/generators/zodGenerator.tsx +213 -166
- package/src/index.ts +11 -2
- package/src/plugin.ts +67 -156
- package/src/printers/printerZod.ts +368 -0
- package/src/printers/printerZodMini.ts +313 -0
- package/src/resolvers/resolverZod.ts +57 -0
- package/src/types.ts +130 -115
- package/src/utils.ts +222 -0
- package/dist/components-B7zUFnAm.cjs +0 -890
- package/dist/components-B7zUFnAm.cjs.map +0 -1
- package/dist/components-eECfXVou.js +0 -842
- package/dist/components-eECfXVou.js.map +0 -1
- package/dist/components.cjs +0 -4
- package/dist/components.d.ts +0 -56
- package/dist/components.js +0 -2
- package/dist/generators-BjPDdJUz.cjs +0 -301
- package/dist/generators-BjPDdJUz.cjs.map +0 -1
- package/dist/generators-lTWPS6oN.js +0 -290
- package/dist/generators-lTWPS6oN.js.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -508
- package/dist/generators.js +0 -2
- package/dist/templates/ToZod.source.cjs +0 -7
- package/dist/templates/ToZod.source.cjs.map +0 -1
- package/dist/templates/ToZod.source.d.ts +0 -7
- package/dist/templates/ToZod.source.js +0 -6
- package/dist/templates/ToZod.source.js.map +0 -1
- package/dist/types-CoCoOc2u.d.ts +0 -172
- package/src/components/index.ts +0 -2
- package/src/generators/index.ts +0 -2
- package/src/generators/operationsGenerator.tsx +0 -50
- package/src/parser.ts +0 -909
- package/src/templates/ToZod.source.ts +0 -4
- package/templates/ToZod.ts +0 -61
|
@@ -1,35 +1,42 @@
|
|
|
1
1
|
import { stringifyObject } from '@internals/utils'
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import type { ast } from '@kubb/core'
|
|
3
|
+
import { Const, File, Type } from '@kubb/renderer-jsx'
|
|
4
|
+
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
5
|
+
|
|
6
|
+
type SchemaNames = {
|
|
7
|
+
request: string | undefined
|
|
8
|
+
parameters: {
|
|
9
|
+
path: string | undefined
|
|
10
|
+
query: string | undefined
|
|
11
|
+
header: string | undefined
|
|
12
|
+
}
|
|
13
|
+
responses: { default?: string } & Record<number | string, string>
|
|
14
|
+
errors: Record<number | string, string>
|
|
15
|
+
}
|
|
6
16
|
|
|
7
17
|
type Props = {
|
|
8
18
|
name: string
|
|
9
|
-
operations: Array<{
|
|
19
|
+
operations: Array<{ node: ast.OperationNode; data: SchemaNames }>
|
|
10
20
|
}
|
|
11
21
|
|
|
12
|
-
export function Operations({ name, operations }: Props):
|
|
13
|
-
const operationsJSON = operations.reduce(
|
|
22
|
+
export function Operations({ name, operations }: Props): KubbReactNode {
|
|
23
|
+
const operationsJSON = operations.reduce<Record<string, unknown>>(
|
|
14
24
|
(prev, acc) => {
|
|
15
|
-
prev[`"${acc.
|
|
25
|
+
prev[`"${acc.node.operationId}"`] = acc.data
|
|
16
26
|
|
|
17
27
|
return prev
|
|
18
28
|
},
|
|
19
29
|
{} as Record<string, unknown>,
|
|
20
30
|
)
|
|
21
31
|
|
|
22
|
-
const pathsJSON = operations.reduce(
|
|
23
|
-
|
|
24
|
-
prev[`"${acc.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
32
|
+
const pathsJSON = operations.reduce<Record<string, Record<string, string>>>((prev, acc) => {
|
|
33
|
+
prev[`"${acc.node.path}"`] = {
|
|
34
|
+
...(prev[`"${acc.node.path}"`] ?? {}),
|
|
35
|
+
[acc.node.method]: `operations["${acc.node.operationId}"]`,
|
|
36
|
+
}
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{} as Record<string, Record<HttpMethod, string>>,
|
|
32
|
-
)
|
|
38
|
+
return prev
|
|
39
|
+
}, {})
|
|
33
40
|
|
|
34
41
|
return (
|
|
35
42
|
<>
|
package/src/components/Zod.tsx
CHANGED
|
@@ -1,140 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import * as parserZod from '../parser.ts'
|
|
7
|
-
import type { PluginZod } from '../types.ts'
|
|
1
|
+
import type { ast } from '@kubb/core'
|
|
2
|
+
import { Const, File, Type } from '@kubb/renderer-jsx'
|
|
3
|
+
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
4
|
+
import type { PrinterZodFactory } from '../printers/printerZod.ts'
|
|
5
|
+
import type { PrinterZodMiniFactory } from '../printers/printerZodMini.ts'
|
|
8
6
|
|
|
9
7
|
type Props = {
|
|
10
8
|
name: string
|
|
11
|
-
|
|
9
|
+
node: ast.SchemaNode
|
|
10
|
+
/**
|
|
11
|
+
* Pre-configured printer instance created by the generator.
|
|
12
|
+
* The generator selects `printerZod` or `printerZodMini` based on the `mini` option,
|
|
13
|
+
* then merges in any user-supplied `printer.nodes` overrides.
|
|
14
|
+
*/
|
|
15
|
+
printer: ast.Printer<PrinterZodFactory> | ast.Printer<PrinterZodMiniFactory>
|
|
12
16
|
inferTypeName?: string
|
|
13
|
-
tree: Array<Schema>
|
|
14
|
-
schema: SchemaObject
|
|
15
|
-
description?: string
|
|
16
|
-
coercion: PluginZod['resolvedOptions']['coercion']
|
|
17
|
-
mapper: PluginZod['resolvedOptions']['mapper']
|
|
18
|
-
keysToOmit?: string[]
|
|
19
|
-
wrapOutput?: PluginZod['resolvedOptions']['wrapOutput']
|
|
20
|
-
version: '3' | '4'
|
|
21
|
-
guidType: PluginZod['resolvedOptions']['guidType']
|
|
22
|
-
emptySchemaType: PluginZod['resolvedOptions']['emptySchemaType']
|
|
23
|
-
mini?: boolean
|
|
24
17
|
}
|
|
25
18
|
|
|
26
|
-
export function Zod({
|
|
27
|
-
|
|
28
|
-
typeName,
|
|
29
|
-
tree,
|
|
30
|
-
schema,
|
|
31
|
-
inferTypeName,
|
|
32
|
-
mapper,
|
|
33
|
-
coercion,
|
|
34
|
-
keysToOmit,
|
|
35
|
-
description,
|
|
36
|
-
wrapOutput,
|
|
37
|
-
version,
|
|
38
|
-
guidType,
|
|
39
|
-
emptySchemaType,
|
|
40
|
-
mini = false,
|
|
41
|
-
}: Props): FabricReactNode {
|
|
42
|
-
const hasTuple = !!SchemaGenerator.find(tree, schemaKeywords.tuple)
|
|
19
|
+
export function Zod({ name, node, printer, inferTypeName }: Props): KubbReactNode {
|
|
20
|
+
const output = printer.print(node)
|
|
43
21
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return true
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
// In mini mode, filter out modifiers from the main schema parsing
|
|
53
|
-
const baseSchemas = mini ? parserZod.filterMiniModifiers(schemas) : schemas
|
|
54
|
-
|
|
55
|
-
const output = baseSchemas
|
|
56
|
-
.map((schemaKeyword, index) => {
|
|
57
|
-
const siblings = baseSchemas.filter((_, i) => i !== index)
|
|
58
|
-
|
|
59
|
-
return parserZod.parse({ schema, parent: undefined, current: schemaKeyword, siblings, name }, { mapper, coercion, wrapOutput, version, guidType, mini })
|
|
60
|
-
})
|
|
61
|
-
.filter(Boolean)
|
|
62
|
-
.join('')
|
|
63
|
-
|
|
64
|
-
let suffix = ''
|
|
65
|
-
const firstSchema = schemas.at(0)
|
|
66
|
-
const lastSchema = schemas.at(-1)
|
|
67
|
-
|
|
68
|
-
if (!mini && lastSchema && isKeyword(lastSchema, schemaKeywords.nullable)) {
|
|
69
|
-
if (firstSchema && isKeyword(firstSchema, schemaKeywords.ref)) {
|
|
70
|
-
if (version === '3') {
|
|
71
|
-
suffix = '.unwrap().schema.unwrap()'
|
|
72
|
-
} else {
|
|
73
|
-
suffix = '.unwrap().unwrap()'
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
suffix = '.unwrap()'
|
|
77
|
-
}
|
|
78
|
-
} else if (!mini) {
|
|
79
|
-
if (firstSchema && isKeyword(firstSchema, schemaKeywords.ref)) {
|
|
80
|
-
if (version === '3') {
|
|
81
|
-
suffix = '.schema'
|
|
82
|
-
} else {
|
|
83
|
-
suffix = '.unwrap()'
|
|
84
|
-
}
|
|
85
|
-
}
|
|
22
|
+
if (!output) {
|
|
23
|
+
return
|
|
86
24
|
}
|
|
87
25
|
|
|
88
|
-
const emptyValue = parserZod.parse(
|
|
89
|
-
{
|
|
90
|
-
schema,
|
|
91
|
-
parent: undefined,
|
|
92
|
-
current: {
|
|
93
|
-
keyword: schemaKeywords[emptySchemaType],
|
|
94
|
-
},
|
|
95
|
-
siblings: [],
|
|
96
|
-
},
|
|
97
|
-
{ mapper, coercion, wrapOutput, version, guidType, mini },
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
let baseSchemaOutput =
|
|
101
|
-
[output, keysToOmit?.length ? `${suffix}.omit({ ${keysToOmit.map((key) => `'${key}': true`).join(',')} })` : undefined].filter(Boolean).join('') ||
|
|
102
|
-
emptyValue ||
|
|
103
|
-
''
|
|
104
|
-
|
|
105
|
-
// For mini mode, wrap the output with modifiers using the parser function
|
|
106
|
-
if (mini) {
|
|
107
|
-
baseSchemaOutput = parserZod.wrapWithMiniModifiers(baseSchemaOutput, parserZod.extractMiniModifiers(schemas))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const wrappedSchemaOutput = wrapOutput ? wrapOutput({ output: baseSchemaOutput, schema }) || baseSchemaOutput : baseSchemaOutput
|
|
111
|
-
const finalOutput = typeName ? `${wrappedSchemaOutput} as unknown as ${version === '4' ? 'z.ZodType' : 'ToZod'}<${typeName}>` : wrappedSchemaOutput
|
|
112
|
-
|
|
113
26
|
return (
|
|
114
27
|
<>
|
|
115
28
|
<File.Source name={name} isExportable isIndexable>
|
|
116
|
-
<Const
|
|
117
|
-
|
|
118
|
-
name={name}
|
|
119
|
-
JSDoc={{
|
|
120
|
-
comments: [description ? `@description ${jsStringEscape(description)}` : undefined].filter(Boolean),
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
{finalOutput}
|
|
29
|
+
<Const export name={name}>
|
|
30
|
+
{output}
|
|
124
31
|
</Const>
|
|
125
32
|
</File.Source>
|
|
126
33
|
{inferTypeName && (
|
|
127
34
|
<File.Source name={inferTypeName} isExportable isIndexable isTypeOnly>
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
</Type>
|
|
132
|
-
)}
|
|
133
|
-
{!typeName && (
|
|
134
|
-
<Type export name={inferTypeName}>
|
|
135
|
-
{`z.infer<typeof ${name}>`}
|
|
136
|
-
</Type>
|
|
137
|
-
)}
|
|
35
|
+
<Type export name={inferTypeName}>
|
|
36
|
+
{`z.infer<typeof ${name}>`}
|
|
37
|
+
</Type>
|
|
138
38
|
</File.Source>
|
|
139
39
|
)}
|
|
140
40
|
</>
|
package/src/constants.ts
ADDED
|
@@ -1,201 +1,248 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import type { PluginZod } from '../types'
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import type { Adapter } from '@kubb/core'
|
|
2
|
+
import { ast, defineGenerator } from '@kubb/core'
|
|
3
|
+
import type { AdapterOas } from '@kubb/adapter-oas'
|
|
4
|
+
import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
5
|
+
import { Operations } from '../components/Operations.tsx'
|
|
6
|
+
import { Zod } from '../components/Zod.tsx'
|
|
7
|
+
import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
|
|
8
|
+
import { printerZod } from '../printers/printerZod.ts'
|
|
9
|
+
import { printerZodMini } from '../printers/printerZodMini.ts'
|
|
10
|
+
import type { PluginZod, ResolverZod } from '../types'
|
|
11
|
+
import { buildSchemaNames } from '../utils.ts'
|
|
12
|
+
|
|
13
|
+
type ZodPrinterEntry = { printer: ReturnType<typeof printerZod>; coercion: unknown; guidType: unknown; dateType: unknown }
|
|
14
|
+
type ZodMiniPrinterEntry = { printer: ReturnType<typeof printerZodMini>; guidType: unknown }
|
|
15
|
+
|
|
16
|
+
// Per-build caches: keyed on resolver (unique per plugin instance per build, GC'd when released)
|
|
17
|
+
const zodPrinterCache = new WeakMap<ResolverZod, ZodPrinterEntry>()
|
|
18
|
+
const zodMiniPrinterCache = new WeakMap<ResolverZod, ZodMiniPrinterEntry>()
|
|
19
|
+
|
|
20
|
+
export const zodGenerator = defineGenerator<PluginZod>({
|
|
13
21
|
name: 'zod',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const oas = useOas()
|
|
24
|
-
const { getSchemas, getFile, getGroup } = useOperationManager(generator)
|
|
25
|
-
const schemaManager = useSchemaManager()
|
|
26
|
-
|
|
27
|
-
const file = getFile(operation)
|
|
28
|
-
const schemas = getSchemas(operation)
|
|
29
|
-
const schemaGenerator = new SchemaGenerator(options, {
|
|
30
|
-
fabric: generator.context.fabric,
|
|
31
|
-
oas,
|
|
32
|
-
plugin,
|
|
33
|
-
driver,
|
|
34
|
-
events: generator.context.events,
|
|
35
|
-
mode,
|
|
36
|
-
override: options.override,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const operationSchemas = [schemas.pathParams, schemas.queryParams, schemas.headerParams, schemas.statusCodes, schemas.request, schemas.response]
|
|
40
|
-
.flat()
|
|
41
|
-
.filter(Boolean)
|
|
42
|
-
const toZodPath = path.resolve(config.root, config.output.path, '.kubb/ToZod.ts')
|
|
22
|
+
renderer: jsxRendererSync,
|
|
23
|
+
schema(node, ctx) {
|
|
24
|
+
const { adapter, config, resolver, root, inputNode } = ctx
|
|
25
|
+
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options
|
|
26
|
+
const dateType = (adapter as Adapter<AdapterOas>).options.dateType
|
|
27
|
+
|
|
28
|
+
if (!node.name) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
43
31
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
let keysToOmit = keysToOmitOriginal
|
|
32
|
+
const mode = ctx.getMode(output)
|
|
33
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
const imports = adapter.getImports(node, (schemaName) => ({
|
|
36
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
37
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
38
|
+
}))
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
const meta = {
|
|
41
|
+
name: resolver.resolveSchemaName(node.name),
|
|
42
|
+
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
|
|
43
|
+
} as const
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
delete schemaObject.properties?.[key]
|
|
56
|
-
}
|
|
45
|
+
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : undefined
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
schemaObject.required = schemaObject.required.filter((key) => !keysToOmitOriginal.includes(key))
|
|
60
|
-
}
|
|
47
|
+
const cyclicSchemas = ast.findCircularSchemas(inputNode.schemas)
|
|
61
48
|
|
|
62
|
-
|
|
49
|
+
const schemaPrinter = mini ? getCachedMiniPrinter() : getCachedStdPrinter()
|
|
50
|
+
function getCachedStdPrinter() {
|
|
51
|
+
const cached = zodPrinterCache.get(resolver)
|
|
52
|
+
if (cached && cached.coercion === coercion && cached.guidType === guidType && cached.dateType === dateType) {
|
|
53
|
+
return cached.printer
|
|
63
54
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
schemaObject.required = Object.entries(schemaObject.properties || {})
|
|
73
|
-
.filter(([_key, value]) => value && Object.hasOwn(value, 'default'))
|
|
74
|
-
.map(([key]) => key)
|
|
55
|
+
const p = printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
56
|
+
zodPrinterCache.set(resolver, { printer: p, coercion, guidType, dateType })
|
|
57
|
+
return p
|
|
58
|
+
}
|
|
59
|
+
function getCachedMiniPrinter() {
|
|
60
|
+
const cached = zodMiniPrinterCache.get(resolver)
|
|
61
|
+
if (cached && cached.guidType === guidType) {
|
|
62
|
+
return cached.printer
|
|
75
63
|
}
|
|
64
|
+
const p = printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
65
|
+
zodMiniPrinterCache.set(resolver, { printer: p, guidType })
|
|
66
|
+
return p
|
|
67
|
+
}
|
|
76
68
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
name: schemaManager.getName(name, { type: 'function' }),
|
|
88
|
-
inferTypeName: schemaManager.getName(name, { type: 'type' }),
|
|
89
|
-
file: schemaManager.getFile(name),
|
|
90
|
-
}
|
|
69
|
+
return (
|
|
70
|
+
<File
|
|
71
|
+
baseName={meta.file.baseName}
|
|
72
|
+
path={meta.file.path}
|
|
73
|
+
meta={meta.file.meta}
|
|
74
|
+
banner={resolver.resolveBanner(inputNode, { output, config })}
|
|
75
|
+
footer={resolver.resolveFooter(inputNode, { output, config })}
|
|
76
|
+
>
|
|
77
|
+
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
78
|
+
{mode === 'split' && imports.map((imp) => <File.Import key={[node.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
91
79
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
80
|
+
<Zod name={meta.name} node={node} printer={schemaPrinter} inferTypeName={inferTypeName} />
|
|
81
|
+
</File>
|
|
82
|
+
)
|
|
83
|
+
},
|
|
84
|
+
operation(node, ctx) {
|
|
85
|
+
const { adapter, config, resolver, root, inputNode } = ctx
|
|
86
|
+
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options
|
|
87
|
+
const dateType = (adapter as Adapter<AdapterOas>).options.dateType
|
|
88
|
+
|
|
89
|
+
const mode = ctx.getMode(output)
|
|
90
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
91
|
+
|
|
92
|
+
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
93
|
+
|
|
94
|
+
const meta = {
|
|
95
|
+
file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
|
|
96
|
+
} as const
|
|
97
|
+
|
|
98
|
+
const cyclicSchemas = ast.findCircularSchemas(inputNode.schemas)
|
|
99
|
+
|
|
100
|
+
function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
|
|
101
|
+
if (!schema) return null
|
|
102
|
+
|
|
103
|
+
const inferTypeName = inferred ? resolver.resolveTypeName(name) : undefined
|
|
104
|
+
|
|
105
|
+
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
106
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
107
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
const cachedStd = zodPrinterCache.get(resolver)
|
|
111
|
+
const cachedMini = zodMiniPrinterCache.get(resolver)
|
|
112
|
+
const schemaPrinter = mini
|
|
113
|
+
? keysToOmit?.length
|
|
114
|
+
? printerZodMini({ guidType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
|
|
115
|
+
: cachedMini?.guidType === guidType
|
|
116
|
+
? cachedMini.printer
|
|
117
|
+
: printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
118
|
+
: keysToOmit?.length
|
|
119
|
+
? printerZod({ coercion, guidType, dateType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
|
|
120
|
+
: cachedStd?.coercion === coercion && cachedStd?.guidType === guidType && cachedStd?.dateType === dateType
|
|
121
|
+
? cachedStd.printer
|
|
122
|
+
: printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
102
123
|
|
|
103
124
|
return (
|
|
104
125
|
<>
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
))}
|
|
109
|
-
<Zod
|
|
110
|
-
name={zod.name}
|
|
111
|
-
typeName={typed ? type.name : undefined}
|
|
112
|
-
inferTypeName={inferred ? zod.inferTypeName : undefined}
|
|
113
|
-
description={description}
|
|
114
|
-
tree={tree}
|
|
115
|
-
schema={schemaObject}
|
|
116
|
-
mapper={mapper}
|
|
117
|
-
coercion={coercion}
|
|
118
|
-
keysToOmit={keysToOmit}
|
|
119
|
-
wrapOutput={wrapOutput}
|
|
120
|
-
version={plugin.options.version}
|
|
121
|
-
guidType={guidType}
|
|
122
|
-
emptySchemaType={plugin.options.emptySchemaType}
|
|
123
|
-
mini={mini}
|
|
124
|
-
/>
|
|
126
|
+
{mode === 'split' &&
|
|
127
|
+
imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
128
|
+
<Zod name={name} node={schema} printer={schemaPrinter} inferTypeName={inferTypeName} />
|
|
125
129
|
</>
|
|
126
130
|
)
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
const
|
|
133
|
+
const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param) }))
|
|
134
|
+
|
|
135
|
+
const responseSchemas = node.responses.map((res) =>
|
|
136
|
+
renderSchemaEntry({
|
|
137
|
+
schema: res.schema,
|
|
138
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
139
|
+
keysToOmit: res.keysToOmit,
|
|
140
|
+
}),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const responsesWithSchema = node.responses.filter((res) => res.schema)
|
|
144
|
+
const responseUnionSchema =
|
|
145
|
+
responsesWithSchema.length > 0
|
|
146
|
+
? (() => {
|
|
147
|
+
const responseUnionName = resolver.resolveResponseName(node)
|
|
148
|
+
|
|
149
|
+
// Collect all import names from response schemas to detect naming collisions.
|
|
150
|
+
// When a response is a $ref to a component schema whose resolved name matches
|
|
151
|
+
// the response union name, skip generation to avoid redeclaration errors.
|
|
152
|
+
const importedNames = new Set(
|
|
153
|
+
responsesWithSchema.flatMap((res) =>
|
|
154
|
+
res.schema
|
|
155
|
+
? adapter
|
|
156
|
+
.getImports(res.schema, (schemaName) => ({
|
|
157
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
158
|
+
path: '',
|
|
159
|
+
}))
|
|
160
|
+
.flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
|
|
161
|
+
: [],
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if (importedNames.has(responseUnionName)) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const members = responsesWithSchema.map((res) => ast.createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) }))
|
|
170
|
+
const unionNode = members.length === 1 ? members[0]! : ast.createSchema({ type: 'union', members })
|
|
171
|
+
|
|
172
|
+
return renderSchemaEntry({
|
|
173
|
+
schema: unionNode,
|
|
174
|
+
name: responseUnionName,
|
|
175
|
+
})
|
|
176
|
+
})()
|
|
177
|
+
: null
|
|
178
|
+
|
|
179
|
+
const requestSchema = node.requestBody?.content?.[0]?.schema
|
|
180
|
+
? renderSchemaEntry({
|
|
181
|
+
schema: {
|
|
182
|
+
...node.requestBody.content![0]!.schema!,
|
|
183
|
+
description: node.requestBody.description ?? node.requestBody.content![0]!.schema!.description,
|
|
184
|
+
},
|
|
185
|
+
name: resolver.resolveDataName(node),
|
|
186
|
+
keysToOmit: node.requestBody.content![0]!.keysToOmit,
|
|
187
|
+
})
|
|
188
|
+
: null
|
|
130
189
|
|
|
131
190
|
return (
|
|
132
191
|
<File
|
|
133
|
-
baseName={file.baseName}
|
|
134
|
-
path={file.path}
|
|
135
|
-
meta={file.meta}
|
|
136
|
-
banner={
|
|
137
|
-
footer={
|
|
192
|
+
baseName={meta.file.baseName}
|
|
193
|
+
path={meta.file.path}
|
|
194
|
+
meta={meta.file.meta}
|
|
195
|
+
banner={resolver.resolveBanner(inputNode, { output, config })}
|
|
196
|
+
footer={resolver.resolveFooter(inputNode, { output, config })}
|
|
138
197
|
>
|
|
139
|
-
<File.Import name={isZodImport ? 'z' : ['z']} path={
|
|
140
|
-
{
|
|
141
|
-
{
|
|
198
|
+
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
199
|
+
{paramSchemas}
|
|
200
|
+
{responseSchemas}
|
|
201
|
+
{responseUnionSchema}
|
|
202
|
+
{requestSchema}
|
|
142
203
|
</File>
|
|
143
204
|
)
|
|
144
205
|
},
|
|
145
|
-
|
|
146
|
-
const {
|
|
147
|
-
const {
|
|
148
|
-
options: { output, emptySchemaType, coercion, inferred, typed, mapper, importPath, wrapOutput, version, guidType, mini },
|
|
149
|
-
} = plugin
|
|
150
|
-
const driver = usePluginDriver()
|
|
151
|
-
const oas = useOas()
|
|
152
|
-
|
|
153
|
-
const imports = getImports(schema.tree)
|
|
154
|
-
|
|
155
|
-
const zod = {
|
|
156
|
-
name: getName(schema.name, { type: 'function' }),
|
|
157
|
-
inferTypeName: getName(schema.name, { type: 'type' }),
|
|
158
|
-
file: getFile(schema.name),
|
|
159
|
-
}
|
|
206
|
+
operations(nodes, ctx) {
|
|
207
|
+
const { config, resolver, root, inputNode } = ctx
|
|
208
|
+
const { output, importPath, group, operations, paramsCasing } = ctx.options
|
|
160
209
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
file: getFile(schema.name, { pluginName: pluginTsName }),
|
|
210
|
+
if (!operations) {
|
|
211
|
+
return
|
|
164
212
|
}
|
|
213
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
165
214
|
|
|
166
|
-
const
|
|
167
|
-
|
|
215
|
+
const meta = {
|
|
216
|
+
file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
|
|
217
|
+
} as const
|
|
218
|
+
|
|
219
|
+
const transformedOperations = nodes.map((node) => {
|
|
220
|
+
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
node,
|
|
224
|
+
data: buildSchemaNames(node, { params, resolver }),
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const imports = transformedOperations.flatMap(({ node, data }) => {
|
|
229
|
+
const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string[]
|
|
230
|
+
const opFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
|
|
231
|
+
|
|
232
|
+
return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
|
|
233
|
+
})
|
|
168
234
|
|
|
169
235
|
return (
|
|
170
236
|
<File
|
|
171
|
-
baseName={
|
|
172
|
-
path={
|
|
173
|
-
meta={
|
|
174
|
-
banner={
|
|
175
|
-
footer={
|
|
237
|
+
baseName={meta.file.baseName}
|
|
238
|
+
path={meta.file.path}
|
|
239
|
+
meta={meta.file.meta}
|
|
240
|
+
banner={resolver.resolveBanner(inputNode, { output, config })}
|
|
241
|
+
footer={resolver.resolveFooter(inputNode, { output, config })}
|
|
176
242
|
>
|
|
177
|
-
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
{imports.map((imp) => (
|
|
181
|
-
<File.Import key={[imp.path, imp.name, imp.isTypeOnly].join('-')} root={zod.file.path} path={imp.path} name={imp.name} />
|
|
182
|
-
))}
|
|
183
|
-
|
|
184
|
-
<Zod
|
|
185
|
-
name={zod.name}
|
|
186
|
-
typeName={typed ? type.name : undefined}
|
|
187
|
-
inferTypeName={inferred ? zod.inferTypeName : undefined}
|
|
188
|
-
description={schema.value.description}
|
|
189
|
-
tree={schema.tree}
|
|
190
|
-
schema={schema.value}
|
|
191
|
-
mapper={mapper}
|
|
192
|
-
coercion={coercion}
|
|
193
|
-
wrapOutput={wrapOutput}
|
|
194
|
-
version={version}
|
|
195
|
-
guidType={guidType}
|
|
196
|
-
emptySchemaType={emptySchemaType}
|
|
197
|
-
mini={mini}
|
|
198
|
-
/>
|
|
243
|
+
<File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
244
|
+
{imports}
|
|
245
|
+
<Operations name="operations" operations={transformedOperations} />
|
|
199
246
|
</File>
|
|
200
247
|
)
|
|
201
248
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
1
|
+
export { zodGenerator } from './generators/zodGenerator.tsx'
|
|
2
|
+
|
|
3
|
+
export { default, pluginZod, pluginZodName } from './plugin.ts'
|
|
4
|
+
export type { PrinterZodFactory, PrinterZodNodes, PrinterZodOptions } from './printers/printerZod.ts'
|
|
5
|
+
export { printerZod } from './printers/printerZod.ts'
|
|
6
|
+
export type { PrinterZodMiniFactory, PrinterZodMiniNodes, PrinterZodMiniOptions } from './printers/printerZodMini.ts'
|
|
7
|
+
export { printerZodMini } from './printers/printerZodMini.ts'
|
|
8
|
+
|
|
9
|
+
export { resolverZod } from './resolvers/resolverZod.ts'
|
|
10
|
+
|
|
11
|
+
export type { PluginZod, ResolverZod } from './types.ts'
|