fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: clean up validator plugins

Lubos e8b6becb fb9460b4

+7336 -4933
+2 -1
.vscode/settings.json
··· 3 3 "source.fixAll.eslint": "explicit" 4 4 }, 5 5 "editor.quickSuggestions": { 6 - "strings": true 6 + "strings": "on" 7 7 }, 8 8 "eslint.format.enable": true, 9 9 "eslint.nodePath": "./node_modules", 10 10 "eslint.workingDirectories": [{ "pattern": "./packages/*/" }], 11 11 "typescript.preferences.autoImportFileExcludePatterns": ["dist/**"], 12 + "typescript.preferences.autoImportSpecifierExcludeRegexes": ["^(node:)?os$"], 12 13 "typescript.tsdk": "node_modules/typescript/lib" 13 14 }
+2 -1
packages/config-vite-base/src/vitest.base.config.ts
··· 1 - import { platform } from 'os'; 1 + import { platform } from 'node:os'; 2 + 2 3 import type { ViteUserConfig } from 'vitest/config'; 3 4 import { configDefaults, defineConfig, mergeConfig } from 'vitest/config'; 4 5
+1
packages/openapi-ts-tests/main/package.json
··· 37 37 "@tanstack/svelte-query": "5.73.3", 38 38 "@tanstack/vue-query": "5.73.3", 39 39 "@types/cross-spawn": "6.0.6", 40 + "arktype": "2.1.23", 40 41 "axios": "1.8.2", 41 42 "cross-spawn": "7.0.6", 42 43 "eslint": "9.17.0",
+8 -2
packages/openapi-ts-tests/main/test/openapi-ts.config.ts
··· 300 300 }, 301 301 }, 302 302 { 303 + name: 'arktype', 304 + types: { 305 + infer: true, 306 + }, 307 + }, 308 + { 303 309 // case: 'SCREAMING_SNAKE_CASE', 304 310 // comments: false, 305 311 // definitions: 'z{{name}}Definition', 306 312 exportFromIndex: true, 307 313 // metadata: true, 308 - // name: 'valibot', 314 + name: 'valibot', 309 315 // requests: { 310 316 // case: 'PascalCase', 311 317 // name: '{{name}}Data', ··· 347 353 }, 348 354 exportFromIndex: true, 349 355 metadata: true, 350 - // name: 'zod', 356 + name: 'zod', 351 357 // requests: { 352 358 // // case: 'SCREAMING_SNAKE_CASE', 353 359 // // name: 'z{{name}}TestData',
+75
packages/openapi-ts/src/generate/__tests__/renderer.test.ts
··· 2 2 3 3 import { TypeScriptRenderer } from '../renderer'; 4 4 5 + // Minimal local BiMap for tests to avoid importing runtime-only class 6 + class LocalBiMap<Key, Value> { 7 + private map = new Map<Key, Value>(); 8 + private reverse = new Map<Value, Key>(); 9 + get(key: Key) { 10 + return this.map.get(key); 11 + } 12 + getKey(value: Value) { 13 + return this.reverse.get(value); 14 + } 15 + set(key: Key, value: Value) { 16 + this.map.set(key, value); 17 + this.reverse.set(value, key); 18 + return this; 19 + } 20 + hasValue(value: Value) { 21 + return this.reverse.has(value); 22 + } 23 + } 24 + 5 25 describe('TypeScriptRenderer', () => { 6 26 describe('default import placeholder replacement', () => { 7 27 it('should replace placeholders in default imports correctly', () => { ··· 42 62 43 63 // The import should use 'foo' not '_heyapi_95_' 44 64 expect(importLines).toEqual(["import foo from 'foo';"]); 65 + }); 66 + }); 67 + 68 + describe('replacer duplicate name handling', () => { 69 + it('allows duplicate names when kinds differ (type vs value)', () => { 70 + const renderer = new TypeScriptRenderer(); 71 + 72 + // Prepare a mock file and project 73 + const file: any = { 74 + resolvedNames: new LocalBiMap<number, string>(), 75 + symbols: { body: [], exports: [], imports: [] }, 76 + }; 77 + 78 + const project = { 79 + symbolIdToFiles: () => [file], 80 + symbols: new Map(), 81 + } as any; 82 + 83 + // Two symbols with the same name but different kinds 84 + const typeSymbolId = 1; 85 + const valueSymbolId = 2; 86 + 87 + const typeSymbol: any = { 88 + exportFrom: [], 89 + id: typeSymbolId, 90 + meta: { kind: 'type' }, 91 + name: 'Foo', 92 + placeholder: '_heyapi_1_', 93 + }; 94 + const valueSymbol: any = { 95 + exportFrom: [], 96 + id: valueSymbolId, 97 + meta: {}, 98 + name: 'Foo', 99 + placeholder: '_heyapi_2_', 100 + }; 101 + 102 + project.symbols.set(typeSymbolId, typeSymbol); 103 + project.symbols.set(valueSymbolId, valueSymbol); 104 + 105 + // First replacement should register the name 'Foo' 106 + const first = renderer['replacerFn']({ 107 + file, 108 + project, 109 + symbol: typeSymbol, 110 + }); 111 + expect(first).toEqual('Foo'); 112 + 113 + // Second replacement (different kind) should be allowed to also use 'Foo' 114 + const second = renderer['replacerFn']({ 115 + file, 116 + project, 117 + symbol: valueSymbol, 118 + }); 119 + expect(second).toEqual('Foo'); 45 120 }); 46 121 }); 47 122 });
+10 -2
packages/openapi-ts/src/generate/renderer.ts
··· 479 479 const [symbolFile] = project.symbolIdToFiles(symbol.id); 480 480 const symbolFileResolvedName = symbolFile?.resolvedNames.get(symbol.id); 481 481 let name = ensureValidIdentifier(symbolFileResolvedName ?? symbol.name); 482 - if (file.resolvedNames.hasValue(name)) { 483 - name = this.getUniqueName(name, file.resolvedNames); 482 + const conflictId = file.resolvedNames.getKey(name); 483 + if (conflictId !== undefined) { 484 + const conflictSymbol = project.symbols.get(conflictId); 485 + if ( 486 + (conflictSymbol?.meta?.kind === 'type' && 487 + symbol.meta?.kind === 'type') || 488 + (conflictSymbol?.meta?.kind !== 'type' && symbol.meta?.kind !== 'type') 489 + ) { 490 + name = this.getUniqueName(name, file.resolvedNames); 491 + } 484 492 } 485 493 file.resolvedNames.set(symbol.id, name); 486 494 return name;
+1 -1
packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts
··· 7 7 import { tsc } from '../../../tsc'; 8 8 import { refToName } from '../../../utils/ref'; 9 9 import { stringCase } from '../../../utils/stringCase'; 10 + import type { SchemaWithType } from '../../shared/types/schema'; 10 11 import { fieldName } from '../../shared/utils/case'; 11 12 import { createSchemaComment } from '../../shared/utils/schema'; 12 - import type { SchemaWithType } from '../../zod/shared/types'; 13 13 import { createClientOptions } from './clientOptions'; 14 14 import { exportType } from './export'; 15 15 import { operationToType } from './operation';
+55
packages/openapi-ts/src/plugins/arktype/api.ts
··· 1 + import type { Selector } from '@hey-api/codegen-core'; 2 + import type ts from 'typescript'; 3 + 4 + import type { Plugin } from '../types'; 5 + import type { ValidatorArgs } from './shared/types'; 6 + import { createRequestValidatorV2, createResponseValidatorV2 } from './v2/api'; 7 + 8 + type SelectorType = 9 + | 'data' 10 + | 'external' 11 + | 'ref' 12 + | 'responses' 13 + | 'type-infer-data' 14 + | 'type-infer-ref' 15 + | 'type-infer-responses' 16 + | 'type-infer-webhook-request' 17 + | 'webhook-request'; 18 + 19 + export type IApi = { 20 + createRequestValidator: (args: ValidatorArgs) => ts.ArrowFunction | undefined; 21 + createResponseValidator: ( 22 + args: ValidatorArgs, 23 + ) => ts.ArrowFunction | undefined; 24 + /** 25 + * @param type Selector type. 26 + * @param value Depends on `type`: 27 + * - `data`: `operation.id` string 28 + * - `external`: external modules 29 + * - `ref`: `$ref` JSON pointer 30 + * - `responses`: `operation.id` string 31 + * - `type-infer-data`: `operation.id` string 32 + * - `type-infer-ref`: `$ref` JSON pointer 33 + * - `type-infer-responses`: `operation.id` string 34 + * - `type-infer-webhook-request`: `operation.id` string 35 + * - `webhook-request`: `operation.id` string 36 + * @returns Selector array 37 + */ 38 + getSelector: (type: SelectorType, value?: string) => Selector; 39 + }; 40 + 41 + export class Api implements IApi { 42 + constructor(public meta: Plugin.Name<'arktype'>) {} 43 + 44 + createRequestValidator(args: ValidatorArgs): ts.ArrowFunction | undefined { 45 + return createRequestValidatorV2(args); 46 + } 47 + 48 + createResponseValidator(args: ValidatorArgs): ts.ArrowFunction | undefined { 49 + return createResponseValidatorV2(args); 50 + } 51 + 52 + getSelector(...args: ReadonlyArray<string | undefined>): Selector { 53 + return [this.meta.name, ...(args as Selector)]; 54 + } 55 + }
+271
packages/openapi-ts/src/plugins/arktype/config.ts
··· 1 + import { definePluginConfig, mappers } from '../shared/utils/config'; 2 + import { Api } from './api'; 3 + import { handler } from './plugin'; 4 + import type { ArktypePlugin } from './types'; 5 + 6 + export const defaultConfig: ArktypePlugin['Config'] = { 7 + api: new Api({ 8 + name: 'arktype', 9 + }), 10 + config: { 11 + case: 'PascalCase', 12 + comments: true, 13 + exportFromIndex: false, 14 + metadata: false, 15 + }, 16 + handler, 17 + name: 'arktype', 18 + resolveConfig: (plugin, context) => { 19 + plugin.config.types = context.valueToObject({ 20 + defaultValue: { 21 + infer: { 22 + case: 'PascalCase', 23 + enabled: false, 24 + }, 25 + }, 26 + mappers: { 27 + object: (fields, defaultValue) => ({ 28 + ...fields, 29 + infer: context.valueToObject({ 30 + defaultValue: { 31 + ...(defaultValue.infer as Extract< 32 + typeof defaultValue.infer, 33 + Record<string, unknown> 34 + >), 35 + enabled: 36 + fields.infer !== undefined 37 + ? Boolean(fields.infer) 38 + : ( 39 + defaultValue.infer as Extract< 40 + typeof defaultValue.infer, 41 + Record<string, unknown> 42 + > 43 + ).enabled, 44 + }, 45 + mappers, 46 + value: fields.infer, 47 + }), 48 + }), 49 + }, 50 + value: plugin.config.types, 51 + }); 52 + 53 + plugin.config.definitions = context.valueToObject({ 54 + defaultValue: { 55 + case: plugin.config.case ?? 'PascalCase', 56 + enabled: true, 57 + name: '{{name}}', 58 + types: { 59 + ...plugin.config.types, 60 + infer: { 61 + ...(plugin.config.types.infer as Extract< 62 + typeof plugin.config.types.infer, 63 + Record<string, unknown> 64 + >), 65 + name: '{{name}}', 66 + }, 67 + }, 68 + }, 69 + mappers: { 70 + ...mappers, 71 + object: (fields, defaultValue) => ({ 72 + ...fields, 73 + types: context.valueToObject({ 74 + defaultValue: defaultValue.types!, 75 + mappers: { 76 + object: (fields, defaultValue) => ({ 77 + ...fields, 78 + infer: context.valueToObject({ 79 + defaultValue: { 80 + ...(defaultValue.infer as Extract< 81 + typeof defaultValue.infer, 82 + Record<string, unknown> 83 + >), 84 + enabled: 85 + fields.infer !== undefined 86 + ? Boolean(fields.infer) 87 + : ( 88 + defaultValue.infer as Extract< 89 + typeof defaultValue.infer, 90 + Record<string, unknown> 91 + > 92 + ).enabled, 93 + }, 94 + mappers, 95 + value: fields.infer, 96 + }), 97 + }), 98 + }, 99 + value: fields.types, 100 + }), 101 + }), 102 + }, 103 + value: plugin.config.definitions, 104 + }); 105 + 106 + plugin.config.requests = context.valueToObject({ 107 + defaultValue: { 108 + case: plugin.config.case ?? 'PascalCase', 109 + enabled: true, 110 + name: '{{name}}Data', 111 + types: { 112 + ...plugin.config.types, 113 + infer: { 114 + ...(plugin.config.types.infer as Extract< 115 + typeof plugin.config.types.infer, 116 + Record<string, unknown> 117 + >), 118 + name: '{{name}}Data', 119 + }, 120 + }, 121 + }, 122 + mappers: { 123 + ...mappers, 124 + object: (fields, defaultValue) => ({ 125 + ...fields, 126 + types: context.valueToObject({ 127 + defaultValue: defaultValue.types!, 128 + mappers: { 129 + object: (fields, defaultValue) => ({ 130 + ...fields, 131 + infer: context.valueToObject({ 132 + defaultValue: { 133 + ...(defaultValue.infer as Extract< 134 + typeof defaultValue.infer, 135 + Record<string, unknown> 136 + >), 137 + enabled: 138 + fields.infer !== undefined 139 + ? Boolean(fields.infer) 140 + : ( 141 + defaultValue.infer as Extract< 142 + typeof defaultValue.infer, 143 + Record<string, unknown> 144 + > 145 + ).enabled, 146 + }, 147 + mappers, 148 + value: fields.infer, 149 + }), 150 + }), 151 + }, 152 + value: fields.types, 153 + }), 154 + }), 155 + }, 156 + value: plugin.config.requests, 157 + }); 158 + 159 + plugin.config.responses = context.valueToObject({ 160 + defaultValue: { 161 + case: plugin.config.case ?? 'PascalCase', 162 + enabled: true, 163 + name: '{{name}}Response', 164 + types: { 165 + ...plugin.config.types, 166 + infer: { 167 + ...(plugin.config.types.infer as Extract< 168 + typeof plugin.config.types.infer, 169 + Record<string, unknown> 170 + >), 171 + name: '{{name}}Response', 172 + }, 173 + }, 174 + }, 175 + mappers: { 176 + ...mappers, 177 + object: (fields, defaultValue) => ({ 178 + ...fields, 179 + types: context.valueToObject({ 180 + defaultValue: defaultValue.types!, 181 + mappers: { 182 + object: (fields, defaultValue) => ({ 183 + ...fields, 184 + infer: context.valueToObject({ 185 + defaultValue: { 186 + ...(defaultValue.infer as Extract< 187 + typeof defaultValue.infer, 188 + Record<string, unknown> 189 + >), 190 + enabled: 191 + fields.infer !== undefined 192 + ? Boolean(fields.infer) 193 + : ( 194 + defaultValue.infer as Extract< 195 + typeof defaultValue.infer, 196 + Record<string, unknown> 197 + > 198 + ).enabled, 199 + }, 200 + mappers, 201 + value: fields.infer, 202 + }), 203 + }), 204 + }, 205 + value: fields.types, 206 + }), 207 + }), 208 + }, 209 + value: plugin.config.responses, 210 + }); 211 + 212 + plugin.config.webhooks = context.valueToObject({ 213 + defaultValue: { 214 + case: plugin.config.case ?? 'PascalCase', 215 + enabled: true, 216 + name: '{{name}}WebhookRequest', 217 + types: { 218 + ...plugin.config.types, 219 + infer: { 220 + ...(plugin.config.types.infer as Extract< 221 + typeof plugin.config.types.infer, 222 + Record<string, unknown> 223 + >), 224 + name: '{{name}}WebhookRequest', 225 + }, 226 + }, 227 + }, 228 + mappers: { 229 + ...mappers, 230 + object: (fields, defaultValue) => ({ 231 + ...fields, 232 + types: context.valueToObject({ 233 + defaultValue: defaultValue.types!, 234 + mappers: { 235 + object: (fields, defaultValue) => ({ 236 + ...fields, 237 + infer: context.valueToObject({ 238 + defaultValue: { 239 + ...(defaultValue.infer as Extract< 240 + typeof defaultValue.infer, 241 + Record<string, unknown> 242 + >), 243 + enabled: 244 + fields.infer !== undefined 245 + ? Boolean(fields.infer) 246 + : ( 247 + defaultValue.infer as Extract< 248 + typeof defaultValue.infer, 249 + Record<string, unknown> 250 + > 251 + ).enabled, 252 + }, 253 + mappers, 254 + value: fields.infer, 255 + }), 256 + }), 257 + }, 258 + value: fields.types, 259 + }), 260 + }), 261 + }, 262 + value: plugin.config.webhooks, 263 + }); 264 + }, 265 + tags: ['validator'], 266 + }; 267 + 268 + /** 269 + * Type helper for Arktype plugin, returns {@link Plugin.Config} object 270 + */ 271 + export const defineConfig = definePluginConfig(defaultConfig);
+44
packages/openapi-ts/src/plugins/arktype/constants.ts
··· 1 + import { tsc } from '../../tsc'; 2 + 3 + export const identifiers = { 4 + /** 5 + * {@link https://arktype.io/docs/type-api Type API} 6 + */ 7 + type: { 8 + $: tsc.identifier({ text: '$' }), 9 + allows: tsc.identifier({ text: 'allows' }), 10 + and: tsc.identifier({ text: 'and' }), 11 + array: tsc.identifier({ text: 'array' }), 12 + as: tsc.identifier({ text: 'as' }), 13 + assert: tsc.identifier({ text: 'assert' }), 14 + brand: tsc.identifier({ text: 'brand' }), 15 + configure: tsc.identifier({ text: 'configure' }), 16 + default: tsc.identifier({ text: 'default' }), 17 + describe: tsc.identifier({ text: 'describe' }), 18 + description: tsc.identifier({ text: 'description' }), 19 + equals: tsc.identifier({ text: 'equals' }), 20 + exclude: tsc.identifier({ text: 'exclude' }), 21 + expression: tsc.identifier({ text: 'expression' }), 22 + extends: tsc.identifier({ text: 'extends' }), 23 + extract: tsc.identifier({ text: 'extract' }), 24 + filter: tsc.identifier({ text: 'filter' }), 25 + from: tsc.identifier({ text: 'from' }), 26 + ifEquals: tsc.identifier({ text: 'ifEquals' }), 27 + ifExtends: tsc.identifier({ text: 'ifExtends' }), 28 + infer: tsc.identifier({ text: 'infer' }), 29 + inferIn: tsc.identifier({ text: 'inferIn' }), 30 + intersect: tsc.identifier({ text: 'intersect' }), 31 + json: tsc.identifier({ text: 'json' }), 32 + meta: tsc.identifier({ text: 'meta' }), 33 + narrow: tsc.identifier({ text: 'narrow' }), 34 + onDeepUndeclaredKey: tsc.identifier({ text: 'onDeepUndeclaredKey' }), 35 + onUndeclaredKey: tsc.identifier({ text: 'onUndeclaredKey' }), 36 + optional: tsc.identifier({ text: 'optional' }), 37 + or: tsc.identifier({ text: 'or' }), 38 + overlaps: tsc.identifier({ text: 'overlaps' }), 39 + pipe: tsc.identifier({ text: 'pipe' }), 40 + select: tsc.identifier({ text: 'select' }), 41 + to: tsc.identifier({ text: 'to' }), 42 + toJsonSchema: tsc.identifier({ text: 'toJsonSchema' }), 43 + }, 44 + };
+2
packages/openapi-ts/src/plugins/arktype/index.ts
··· 1 + export { defaultConfig, defineConfig } from './config'; 2 + export type { ArktypePlugin } from './types';
+4
packages/openapi-ts/src/plugins/arktype/plugin.ts
··· 1 + import type { ArktypePlugin } from './types'; 2 + import { handlerV2 } from './v2/plugin'; 3 + 4 + export const handler: ArktypePlugin['Handler'] = (args) => handlerV2(args);
+53
packages/openapi-ts/src/plugins/arktype/shared/export.ts
··· 1 + import type { Symbol } from '@hey-api/codegen-core'; 2 + import ts from 'typescript'; 3 + 4 + import type { IR } from '../../../ir/types'; 5 + import { tsc } from '../../../tsc'; 6 + import { createSchemaComment } from '../../shared/utils/schema'; 7 + import { identifiers } from '../constants'; 8 + import type { ArktypePlugin } from '../types'; 9 + import type { Ast } from './types'; 10 + 11 + export const exportAst = ({ 12 + ast, 13 + plugin, 14 + schema, 15 + symbol, 16 + typeInferSymbol, 17 + }: { 18 + ast: Ast; 19 + plugin: ArktypePlugin['Instance']; 20 + schema: IR.SchemaObject; 21 + symbol: Symbol; 22 + typeInferSymbol: Symbol | undefined; 23 + }): void => { 24 + const statement = tsc.constVariable({ 25 + comment: plugin.config.comments 26 + ? createSchemaComment({ schema }) 27 + : undefined, 28 + exportConst: symbol.exported, 29 + expression: ast.expression, 30 + name: symbol.placeholder, 31 + // typeName: ast.typeName 32 + // ? (tsc.propertyAccessExpression({ 33 + // expression: z.placeholder, 34 + // name: ast.typeName, 35 + // }) as unknown as ts.TypeNode) 36 + // : undefined, 37 + }); 38 + plugin.setSymbolValue(symbol, statement); 39 + 40 + if (typeInferSymbol) { 41 + const inferType = tsc.typeAliasDeclaration({ 42 + exportType: typeInferSymbol.exported, 43 + name: typeInferSymbol.placeholder, 44 + type: ts.factory.createTypeQueryNode( 45 + ts.factory.createQualifiedName( 46 + ts.factory.createIdentifier(symbol.placeholder), 47 + identifiers.type.infer, 48 + ), 49 + ), 50 + }); 51 + plugin.setSymbolValue(typeInferSymbol, inferType); 52 + } 53 + };
+35
packages/openapi-ts/src/plugins/arktype/shared/types.d.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import type { IR } from '../../../ir/types'; 4 + import type { ArktypePlugin } from '../types'; 5 + 6 + export type Ast = { 7 + expression: ts.Expression; 8 + hasCircularReference?: boolean; 9 + typeName?: string | ts.Identifier; 10 + }; 11 + 12 + export type IrSchemaToAstOptions = { 13 + plugin: ArktypePlugin['Instance']; 14 + state: State; 15 + }; 16 + 17 + export type State = { 18 + /** 19 + * Path to the schema in the intermediary representation. 20 + */ 21 + _path: ReadonlyArray<string | number>; 22 + circularReferenceTracker: Array<string>; 23 + /** 24 + * Works the same as `circularReferenceTracker`, but it resets whenever we 25 + * walk inside another schema. This can be used to detect if a schema 26 + * directly references itself. 27 + */ 28 + currentReferenceTracker: Array<string>; 29 + hasCircularReference: boolean; 30 + }; 31 + 32 + export type ValidatorArgs = { 33 + operation: IR.OperationObject; 34 + plugin: ArktypePlugin['Instance']; 35 + };
+665
packages/openapi-ts/src/plugins/arktype/types.d.ts
··· 1 + import type { StringCase, StringName } from '../../types/case'; 2 + import type { DefinePlugin, Plugin } from '../types'; 3 + import type { IApi } from './api'; 4 + 5 + export type UserConfig = Plugin.Name<'arktype'> & 6 + Plugin.Hooks & { 7 + /** 8 + * The casing convention to use for generated names. 9 + * 10 + * @default 'PascalCase' 11 + */ 12 + case?: StringCase; 13 + /** 14 + * Add comments from input to the generated Arktype schemas? 15 + * 16 + * @default true 17 + */ 18 + comments?: boolean; 19 + /** 20 + * Configuration for reusable schema definitions. 21 + * 22 + * Controls generation of shared Arktype schemas that can be referenced 23 + * across requests and responses. 24 + * 25 + * Can be: 26 + * - `boolean`: Shorthand for `{ enabled: boolean }` 27 + * - `string` or `function`: Shorthand for `{ name: string | function }` 28 + * - `object`: Full configuration object 29 + */ 30 + definitions?: 31 + | boolean 32 + | StringName 33 + | { 34 + /** 35 + * The casing convention to use for generated names. 36 + * 37 + * @default 'PascalCase' 38 + */ 39 + case?: StringCase; 40 + /** 41 + * Whether to generate Arktype schemas for reusable definitions. 42 + * 43 + * @default true 44 + */ 45 + enabled?: boolean; 46 + /** 47 + * Custom naming pattern for generated schema names. The name variable 48 + * is obtained from the schema name. 49 + * 50 + * @default '{{name}}' 51 + */ 52 + name?: StringName; 53 + /** 54 + * Configuration for TypeScript type generation from Arktype schemas. 55 + * 56 + * Controls generation of TypeScript types based on the generated Arktype schemas. 57 + */ 58 + types?: { 59 + /** 60 + * Configuration for `infer` types. 61 + * 62 + * Can be: 63 + * - `boolean`: Shorthand for `{ enabled: boolean }` 64 + * - `string` or `function`: Shorthand for `{ name: string | function }` 65 + * - `object`: Full configuration object 66 + * 67 + * @default false 68 + */ 69 + infer?: 70 + | boolean 71 + | StringName 72 + | { 73 + /** 74 + * The casing convention to use for generated type names. 75 + * 76 + * @default 'PascalCase' 77 + */ 78 + case?: StringCase; 79 + /** 80 + * Whether to generate TypeScript types from Arktype schemas. 81 + * 82 + * @default true 83 + */ 84 + enabled?: boolean; 85 + /** 86 + * Custom naming pattern for generated type names. The name variable is 87 + * obtained from the Arktype schema name. 88 + * 89 + * @default '{{name}}' 90 + */ 91 + name?: StringName; 92 + }; 93 + }; 94 + }; 95 + /** 96 + * Should the exports from the generated files be re-exported in the index 97 + * barrel file? 98 + * 99 + * @default false 100 + */ 101 + exportFromIndex?: boolean; 102 + /** 103 + * Enable Arktype metadata support? It's often useful to associate a schema 104 + * with some additional metadata for documentation, code generation, AI 105 + * structured outputs, form validation, and other purposes. 106 + * 107 + * @default false 108 + */ 109 + metadata?: boolean; 110 + /** 111 + * Configuration for request-specific Arktype schemas. 112 + * 113 + * Controls generation of Arktype schemas for request bodies, query parameters, path 114 + * parameters, and headers. 115 + * 116 + * Can be: 117 + * - `boolean`: Shorthand for `{ enabled: boolean }` 118 + * - `string` or `function`: Shorthand for `{ name: string | function }` 119 + * - `object`: Full configuration object 120 + * 121 + * @default true 122 + */ 123 + requests?: 124 + | boolean 125 + | StringName 126 + | { 127 + /** 128 + * The casing convention to use for generated names. 129 + * 130 + * @default 'PascalCase' 131 + */ 132 + case?: StringCase; 133 + /** 134 + * Whether to generate Arktype schemas for request definitions. 135 + * 136 + * @default true 137 + */ 138 + enabled?: boolean; 139 + /** 140 + * Custom naming pattern for generated schema names. The name variable 141 + * is obtained from the operation name. 142 + * 143 + * @default '{{name}}Data' 144 + */ 145 + name?: StringName; 146 + /** 147 + * Configuration for TypeScript type generation from Arktype schemas. 148 + * 149 + * Controls generation of TypeScript types based on the generated Arktype schemas. 150 + */ 151 + types?: { 152 + /** 153 + * Configuration for `infer` types. 154 + * 155 + * Can be: 156 + * - `boolean`: Shorthand for `{ enabled: boolean }` 157 + * - `string` or `function`: Shorthand for `{ name: string | function }` 158 + * - `object`: Full configuration object 159 + * 160 + * @default false 161 + */ 162 + infer?: 163 + | boolean 164 + | StringName 165 + | { 166 + /** 167 + * The casing convention to use for generated type names. 168 + * 169 + * @default 'PascalCase' 170 + */ 171 + case?: StringCase; 172 + /** 173 + * Whether to generate TypeScript types from Arktype schemas. 174 + * 175 + * @default true 176 + */ 177 + enabled?: boolean; 178 + /** 179 + * Custom naming pattern for generated type names. The name variable is 180 + * obtained from the Arktype schema name. 181 + * 182 + * @default '{{name}}Data' 183 + */ 184 + name?: StringName; 185 + }; 186 + }; 187 + }; 188 + /** 189 + * Configuration for response-specific Arktype schemas. 190 + * 191 + * Controls generation of Arktype schemas for response bodies, error responses, 192 + * and status codes. 193 + * 194 + * Can be: 195 + * - `boolean`: Shorthand for `{ enabled: boolean }` 196 + * - `string` or `function`: Shorthand for `{ name: string | function }` 197 + * - `object`: Full configuration object 198 + * 199 + * @default true 200 + */ 201 + responses?: 202 + | boolean 203 + | StringName 204 + | { 205 + /** 206 + * The casing convention to use for generated names. 207 + * 208 + * @default 'PascalCase' 209 + */ 210 + case?: StringCase; 211 + /** 212 + * Whether to generate Arktype schemas for response definitions. 213 + * 214 + * @default true 215 + */ 216 + enabled?: boolean; 217 + /** 218 + * Custom naming pattern for generated schema names. The name variable 219 + * is obtained from the operation name. 220 + * 221 + * @default '{{name}}Response' 222 + */ 223 + name?: StringName; 224 + /** 225 + * Configuration for TypeScript type generation from Arktype schemas. 226 + * 227 + * Controls generation of TypeScript types based on the generated Arktype schemas. 228 + */ 229 + types?: { 230 + /** 231 + * Configuration for `infer` types. 232 + * 233 + * Can be: 234 + * - `boolean`: Shorthand for `{ enabled: boolean }` 235 + * - `string` or `function`: Shorthand for `{ name: string | function }` 236 + * - `object`: Full configuration object 237 + * 238 + * @default false 239 + */ 240 + infer?: 241 + | boolean 242 + | StringName 243 + | { 244 + /** 245 + * The casing convention to use for generated type names. 246 + * 247 + * @default 'PascalCase' 248 + */ 249 + case?: StringCase; 250 + /** 251 + * Whether to generate TypeScript types from Arktype schemas. 252 + * 253 + * @default true 254 + */ 255 + enabled?: boolean; 256 + /** 257 + * Custom naming pattern for generated type names. The name variable is 258 + * obtained from the Arktype schema name. 259 + * 260 + * @default '{{name}}Response' 261 + */ 262 + name?: StringName; 263 + }; 264 + }; 265 + }; 266 + /** 267 + * Configuration for TypeScript type generation from Arktype schemas. 268 + * 269 + * Controls generation of TypeScript types based on the generated Arktype schemas. 270 + */ 271 + types?: { 272 + /** 273 + * Configuration for `infer` types. 274 + * 275 + * Can be: 276 + * - `boolean`: Shorthand for `{ enabled: boolean }` 277 + * - `string` or `function`: Shorthand for `{ name: string | function }` 278 + * - `object`: Full configuration object 279 + * 280 + * @default false 281 + */ 282 + infer?: 283 + | boolean 284 + | StringName 285 + | { 286 + /** 287 + * The casing convention to use for generated type names. 288 + * 289 + * @default 'PascalCase' 290 + */ 291 + case?: StringCase; 292 + /** 293 + * Whether to generate TypeScript types from Arktype schemas. 294 + * 295 + * @default true 296 + */ 297 + enabled?: boolean; 298 + }; 299 + }; 300 + /** 301 + * Configuration for webhook-specific Arktype schemas. 302 + * 303 + * Controls generation of Arktype schemas for webhook payloads. 304 + * 305 + * Can be: 306 + * - `boolean`: Shorthand for `{ enabled: boolean }` 307 + * - `string` or `function`: Shorthand for `{ name: string | function }` 308 + * - `object`: Full configuration object 309 + * 310 + * @default true 311 + */ 312 + webhooks?: 313 + | boolean 314 + | StringName 315 + | { 316 + /** 317 + * The casing convention to use for generated names. 318 + * 319 + * @default 'PascalCase' 320 + */ 321 + case?: StringCase; 322 + /** 323 + * Whether to generate Arktype schemas for webhook definitions. 324 + * 325 + * @default true 326 + */ 327 + enabled?: boolean; 328 + /** 329 + * Custom naming pattern for generated schema names. The name variable 330 + * is obtained from the webhook key. 331 + * 332 + * @default '{{name}}WebhookRequest' 333 + */ 334 + name?: StringName; 335 + /** 336 + * Configuration for TypeScript type generation from Arktype schemas. 337 + * 338 + * Controls generation of TypeScript types based on the generated Arktype schemas. 339 + */ 340 + types?: { 341 + /** 342 + * Configuration for `infer` types. 343 + * 344 + * Can be: 345 + * - `boolean`: Shorthand for `{ enabled: boolean }` 346 + * - `string` or `function`: Shorthand for `{ name: string | function }` 347 + * - `object`: Full configuration object 348 + * 349 + * @default false 350 + */ 351 + infer?: 352 + | boolean 353 + | StringName 354 + | { 355 + /** 356 + * The casing convention to use for generated type names. 357 + * 358 + * @default 'PascalCase' 359 + */ 360 + case?: StringCase; 361 + /** 362 + * Whether to generate TypeScript types from Arktype schemas. 363 + * 364 + * @default true 365 + */ 366 + enabled?: boolean; 367 + /** 368 + * Custom naming pattern for generated type names. The name variable is 369 + * obtained from the Arktype schema name. 370 + * 371 + * @default '{{name}}WebhookRequest' 372 + */ 373 + name?: StringName; 374 + }; 375 + }; 376 + }; 377 + }; 378 + 379 + export type Config = Plugin.Name<'arktype'> & 380 + Plugin.Hooks & { 381 + /** 382 + * The casing convention to use for generated names. 383 + * 384 + * @default 'PascalCase' 385 + */ 386 + case: StringCase; 387 + /** 388 + * Add comments from input to the generated Arktype schemas? 389 + * 390 + * @default true 391 + */ 392 + comments: boolean; 393 + /** 394 + * Configuration for reusable schema definitions. 395 + * 396 + * Controls generation of shared Arktype schemas that can be referenced across 397 + * requests and responses. 398 + */ 399 + definitions: { 400 + /** 401 + * The casing convention to use for generated names. 402 + * 403 + * @default 'PascalCase' 404 + */ 405 + case: StringCase; 406 + /** 407 + * Whether to generate Arktype schemas for reusable definitions. 408 + * 409 + * @default true 410 + */ 411 + enabled: boolean; 412 + /** 413 + * Custom naming pattern for generated schema names. The name variable is 414 + * obtained from the schema name. 415 + * 416 + * @default '{{name}}' 417 + */ 418 + name: StringName; 419 + /** 420 + * Configuration for TypeScript type generation from Arktype schemas. 421 + * 422 + * Controls generation of TypeScript types based on the generated Arktype schemas. 423 + */ 424 + types: { 425 + /** 426 + * Configuration for `infer` types. 427 + */ 428 + infer: { 429 + /** 430 + * The casing convention to use for generated type names. 431 + * 432 + * @default 'PascalCase' 433 + */ 434 + case: StringCase; 435 + /** 436 + * Whether to generate TypeScript types from Arktype schemas. 437 + * 438 + * @default true 439 + */ 440 + enabled: boolean; 441 + /** 442 + * Custom naming pattern for generated type names. The name variable is 443 + * obtained from the Arktype schema name. 444 + * 445 + * @default '{{name}}' 446 + */ 447 + name: StringName; 448 + }; 449 + }; 450 + }; 451 + /** 452 + * Should the exports from the generated files be re-exported in the index 453 + * barrel file? 454 + * 455 + * @default false 456 + */ 457 + exportFromIndex: boolean; 458 + /** 459 + * Enable Arktype metadata support? It's often useful to associate a schema with 460 + * some additional metadata for documentation, code generation, AI 461 + * structured outputs, form validation, and other purposes. 462 + * 463 + * @default false 464 + */ 465 + metadata: boolean; 466 + /** 467 + * Configuration for request-specific Arktype schemas. 468 + * 469 + * Controls generation of Arktype schemas for request bodies, query parameters, path 470 + * parameters, and headers. 471 + */ 472 + requests: { 473 + /** 474 + * The casing convention to use for generated names. 475 + * 476 + * @default 'PascalCase' 477 + */ 478 + case: StringCase; 479 + /** 480 + * Whether to generate Arktype schemas for request definitions. 481 + * 482 + * @default true 483 + */ 484 + enabled: boolean; 485 + /** 486 + * Custom naming pattern for generated schema names. The name variable is 487 + * obtained from the operation name. 488 + * 489 + * @default '{{name}}Data' 490 + */ 491 + name: StringName; 492 + /** 493 + * Configuration for TypeScript type generation from Arktype schemas. 494 + * 495 + * Controls generation of TypeScript types based on the generated Arktype schemas. 496 + */ 497 + types: { 498 + /** 499 + * Configuration for `infer` types. 500 + */ 501 + infer: { 502 + /** 503 + * The casing convention to use for generated type names. 504 + * 505 + * @default 'PascalCase' 506 + */ 507 + case: StringCase; 508 + /** 509 + * Whether to generate TypeScript types from Arktype schemas. 510 + * 511 + * @default true 512 + */ 513 + enabled: boolean; 514 + /** 515 + * Custom naming pattern for generated type names. The name variable is 516 + * obtained from the Arktype schema name. 517 + * 518 + * @default '{{name}}Data' 519 + */ 520 + name: StringName; 521 + }; 522 + }; 523 + }; 524 + /** 525 + * Configuration for response-specific Arktype schemas. 526 + * 527 + * Controls generation of Arktype schemas for response bodies, error responses, 528 + * and status codes. 529 + */ 530 + responses: { 531 + /** 532 + * The casing convention to use for generated names. 533 + * 534 + * @default 'PascalCase' 535 + */ 536 + case: StringCase; 537 + /** 538 + * Whether to generate Arktype schemas for response definitions. 539 + * 540 + * @default true 541 + */ 542 + enabled: boolean; 543 + /** 544 + * Custom naming pattern for generated schema names. The name variable is 545 + * obtained from the operation name. 546 + * 547 + * @default '{{name}}Response' 548 + */ 549 + name: StringName; 550 + /** 551 + * Configuration for TypeScript type generation from Arktype schemas. 552 + * 553 + * Controls generation of TypeScript types based on the generated Arktype schemas. 554 + */ 555 + types: { 556 + /** 557 + * Configuration for `infer` types. 558 + */ 559 + infer: { 560 + /** 561 + * The casing convention to use for generated type names. 562 + * 563 + * @default 'PascalCase' 564 + */ 565 + case: StringCase; 566 + /** 567 + * Whether to generate TypeScript types from Arktype schemas. 568 + * 569 + * @default true 570 + */ 571 + enabled: boolean; 572 + /** 573 + * Custom naming pattern for generated type names. The name variable is 574 + * obtained from the Arktype schema name. 575 + * 576 + * @default '{{name}}Response' 577 + */ 578 + name: StringName; 579 + }; 580 + }; 581 + }; 582 + /** 583 + * Configuration for TypeScript type generation from Arktype schemas. 584 + * 585 + * Controls generation of TypeScript types based on the generated Arktype schemas. 586 + */ 587 + types: { 588 + /** 589 + * Configuration for `infer` types. 590 + */ 591 + infer: { 592 + /** 593 + * The casing convention to use for generated type names. 594 + * 595 + * @default 'PascalCase' 596 + */ 597 + case: StringCase; 598 + /** 599 + * Whether to generate TypeScript types from Arktype schemas. 600 + * 601 + * @default true 602 + */ 603 + enabled: boolean; 604 + }; 605 + }; 606 + /** 607 + * Configuration for webhook-specific Arktype schemas. 608 + * 609 + * Controls generation of Arktype schemas for webhook payloads. 610 + */ 611 + webhooks: { 612 + /** 613 + * The casing convention to use for generated names. 614 + * 615 + * @default 'PascalCase' 616 + */ 617 + case: StringCase; 618 + /** 619 + * Whether to generate Arktype schemas for webhook definitions. 620 + * 621 + * @default true 622 + */ 623 + enabled: boolean; 624 + /** 625 + * Custom naming pattern for generated schema names. The name variable is 626 + * is obtained from the webhook key. 627 + * 628 + * @default '{{name}}WebhookRequest' 629 + */ 630 + name: StringName; 631 + /** 632 + * Configuration for TypeScript type generation from Arktype schemas. 633 + * 634 + * Controls generation of TypeScript types based on the generated Arktype schemas. 635 + */ 636 + types: { 637 + /** 638 + * Configuration for `infer` types. 639 + */ 640 + infer: { 641 + /** 642 + * The casing convention to use for generated type names. 643 + * 644 + * @default 'PascalCase' 645 + */ 646 + case: StringCase; 647 + /** 648 + * Whether to generate TypeScript types from Arktype schemas. 649 + * 650 + * @default true 651 + */ 652 + enabled: boolean; 653 + /** 654 + * Custom naming pattern for generated type names. The name variable is 655 + * obtained from the Arktype schema name. 656 + * 657 + * @default '{{name}}WebhookRequest' 658 + */ 659 + name: StringName; 660 + }; 661 + }; 662 + }; 663 + }; 664 + 665 + export type ArktypePlugin = DefinePlugin<UserConfig, Config, IApi>;
+73
packages/openapi-ts/src/plugins/arktype/v2/api.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + // import { identifiers } from '../constants'; 5 + import type { ValidatorArgs } from '../shared/types'; 6 + 7 + export const createRequestValidatorV2 = ({ 8 + operation, 9 + plugin, 10 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 11 + const symbol = plugin.getSymbol(plugin.api.getSelector('data', operation.id)); 12 + if (!symbol) return; 13 + 14 + const dataParameterName = 'data'; 15 + 16 + return tsc.arrowFunction({ 17 + async: true, 18 + parameters: [ 19 + { 20 + name: dataParameterName, 21 + }, 22 + ], 23 + statements: [ 24 + tsc.returnStatement({ 25 + expression: tsc.awaitExpression({ 26 + expression: tsc.callExpression({ 27 + functionName: tsc.propertyAccessExpression({ 28 + expression: symbol.placeholder, 29 + name: 'parseAsync', 30 + // name: identifiers.parseAsync, 31 + }), 32 + parameters: [tsc.identifier({ text: dataParameterName })], 33 + }), 34 + }), 35 + }), 36 + ], 37 + }); 38 + }; 39 + 40 + export const createResponseValidatorV2 = ({ 41 + operation, 42 + plugin, 43 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 44 + const symbol = plugin.getSymbol( 45 + plugin.api.getSelector('responses', operation.id), 46 + ); 47 + if (!symbol) return; 48 + 49 + const dataParameterName = 'data'; 50 + 51 + return tsc.arrowFunction({ 52 + async: true, 53 + parameters: [ 54 + { 55 + name: dataParameterName, 56 + }, 57 + ], 58 + statements: [ 59 + tsc.returnStatement({ 60 + expression: tsc.awaitExpression({ 61 + expression: tsc.callExpression({ 62 + functionName: tsc.propertyAccessExpression({ 63 + expression: symbol.placeholder, 64 + name: 'parseAsync', 65 + // name: identifiers.parseAsync, 66 + }), 67 + parameters: [tsc.identifier({ text: dataParameterName })], 68 + }), 69 + }), 70 + }), 71 + ], 72 + }); 73 + };
+454
packages/openapi-ts/src/plugins/arktype/v2/plugin.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import type { IR } from '../../../ir/types'; 4 + import { buildName } from '../../../openApi/shared/utils/name'; 5 + import { refToName } from '../../../utils/ref'; 6 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 7 + import { exportAst } from '../shared/export'; 8 + import type { Ast, IrSchemaToAstOptions } from '../shared/types'; 9 + import type { ArktypePlugin } from '../types'; 10 + import { irSchemaWithTypeToAst } from './toAst'; 11 + 12 + export const irSchemaToAst = ({ 13 + // optional, 14 + plugin, 15 + schema, 16 + state, 17 + }: IrSchemaToAstOptions & { 18 + /** 19 + * Accept `optional` to handle optional object properties. We can't handle 20 + * this inside the object function because `.optional()` must come before 21 + * `.default()` which is handled in this function. 22 + */ 23 + optional?: boolean; 24 + schema: IR.SchemaObject; 25 + }): Ast => { 26 + let ast: Partial<Ast> = {}; 27 + 28 + // const z = plugin.referenceSymbol( 29 + // plugin.api.getSelector('external', 'zod.z'), 30 + // ); 31 + 32 + if (schema.$ref) { 33 + // const isCircularReference = state.circularReferenceTracker.includes( 34 + // schema.$ref, 35 + // ); 36 + // const isSelfReference = state.currentReferenceTracker.includes(schema.$ref); 37 + // state.circularReferenceTracker.push(schema.$ref); 38 + // state.currentReferenceTracker.push(schema.$ref); 39 + // const selector = plugin.api.getSelector('ref', schema.$ref); 40 + // let symbol = plugin.getSymbol(selector); 41 + // if (isCircularReference) { 42 + // if (!symbol) { 43 + // symbol = plugin.referenceSymbol(selector); 44 + // } 45 + // if (isSelfReference) { 46 + // ast.expression = tsc.callExpression({ 47 + // functionName: tsc.propertyAccessExpression({ 48 + // expression: z.placeholder, 49 + // name: identifiers.lazy, 50 + // }), 51 + // parameters: [ 52 + // tsc.arrowFunction({ 53 + // returnType: tsc.keywordTypeNode({ keyword: 'any' }), 54 + // statements: [ 55 + // tsc.returnStatement({ 56 + // expression: tsc.identifier({ text: symbol.placeholder }), 57 + // }), 58 + // ], 59 + // }), 60 + // ], 61 + // }); 62 + // } else { 63 + // ast.expression = tsc.identifier({ text: symbol.placeholder }); 64 + // } 65 + // ast.hasCircularReference = schema.circular; 66 + // } else { 67 + // if (!symbol) { 68 + // // if $ref hasn't been processed yet, inline it to avoid the 69 + // // "Block-scoped variable used before its declaration." error 70 + // // this could be (maybe?) fixed by reshuffling the generation order 71 + // const ref = plugin.context.resolveIrRef<IR.SchemaObject>(schema.$ref); 72 + // handleComponent({ 73 + // id: schema.$ref, 74 + // plugin, 75 + // schema: ref, 76 + // state: { 77 + // ...state, 78 + // _path: jsonPointerToPath(schema.$ref), 79 + // }, 80 + // }); 81 + // } else { 82 + // ast.hasCircularReference = schema.circular; 83 + // } 84 + // const refSymbol = plugin.referenceSymbol(selector); 85 + // ast.expression = tsc.identifier({ text: refSymbol.placeholder }); 86 + // } 87 + // state.circularReferenceTracker.pop(); 88 + // state.currentReferenceTracker.pop(); 89 + } else if (schema.type) { 90 + // const zSchema = irSchemaWithTypeToAst({ 91 + // plugin, 92 + // schema: schema as SchemaWithType, 93 + // state, 94 + // }); 95 + // ast.expression = zSchema.expression; 96 + // ast.hasCircularReference = zSchema.hasCircularReference; 97 + // if (plugin.config.metadata && schema.description) { 98 + // ast.expression = tsc.callExpression({ 99 + // functionName: tsc.propertyAccessExpression({ 100 + // expression: ast.expression, 101 + // name: identifiers.register, 102 + // }), 103 + // parameters: [ 104 + // tsc.propertyAccessExpression({ 105 + // expression: z.placeholder, 106 + // name: identifiers.globalRegistry, 107 + // }), 108 + // tsc.objectExpression({ 109 + // obj: [ 110 + // { 111 + // key: 'description', 112 + // value: tsc.stringLiteral({ text: schema.description }), 113 + // }, 114 + // ], 115 + // }), 116 + // ], 117 + // }); 118 + // } 119 + } else if (schema.items) { 120 + // schema = deduplicateSchema({ schema }); 121 + 122 + if (schema.items) { 123 + // const itemSchemas = schema.items.map((item, index) => 124 + // irSchemaToAst({ 125 + // plugin, 126 + // schema: item, 127 + // state: { 128 + // ...state, 129 + // _path: [...state._path, 'items', index], 130 + // }, 131 + // }), 132 + // ); 133 + // if (schema.logicalOperator === 'and') { 134 + // const firstSchema = schema.items[0]!; 135 + // // we want to add an intersection, but not every schema can use the same API. 136 + // // if the first item contains another array or not an object, we cannot use 137 + // // `.merge()` as that does not exist on `.union()` and non-object schemas. 138 + // if ( 139 + // firstSchema.logicalOperator === 'or' || 140 + // (firstSchema.type && firstSchema.type !== 'object') 141 + // ) { 142 + // ast.expression = tsc.callExpression({ 143 + // functionName: tsc.propertyAccessExpression({ 144 + // expression: z.placeholder, 145 + // name: identifiers.intersection, 146 + // }), 147 + // parameters: itemSchemas.map((schema) => schema.expression), 148 + // }); 149 + // } else { 150 + // ast.expression = itemSchemas[0]!.expression; 151 + // itemSchemas.slice(1).forEach((schema) => { 152 + // ast.expression = tsc.callExpression({ 153 + // functionName: tsc.propertyAccessExpression({ 154 + // expression: ast.expression!, 155 + // name: identifiers.and, 156 + // }), 157 + // parameters: [ 158 + // schema.hasCircularReference 159 + // ? tsc.callExpression({ 160 + // functionName: tsc.propertyAccessExpression({ 161 + // expression: z.placeholder, 162 + // name: identifiers.lazy, 163 + // }), 164 + // parameters: [ 165 + // tsc.arrowFunction({ 166 + // statements: [ 167 + // tsc.returnStatement({ 168 + // expression: schema.expression, 169 + // }), 170 + // ], 171 + // }), 172 + // ], 173 + // }) 174 + // : schema.expression, 175 + // ], 176 + // }); 177 + // }); 178 + // } 179 + // } else { 180 + // ast.expression = tsc.callExpression({ 181 + // functionName: tsc.propertyAccessExpression({ 182 + // expression: z.placeholder, 183 + // name: identifiers.union, 184 + // }), 185 + // parameters: [ 186 + // tsc.arrayLiteralExpression({ 187 + // elements: itemSchemas.map((schema) => schema.expression), 188 + // }), 189 + // ], 190 + // }); 191 + // } 192 + } else { 193 + ast = irSchemaToAst({ 194 + plugin, 195 + schema, 196 + state, 197 + }); 198 + } 199 + } else { 200 + // // catch-all fallback for failed schemas 201 + // const zSchema = irSchemaWithTypeToAst({ 202 + // plugin, 203 + // schema: { 204 + // type: 'unknown', 205 + // }, 206 + // state, 207 + // }); 208 + // ast.expression = zSchema.expression; 209 + } 210 + 211 + // TODO: remove later 212 + const zSchema = irSchemaWithTypeToAst({ 213 + plugin, 214 + schema: { 215 + type: 'unknown', 216 + }, 217 + state, 218 + }); 219 + ast.expression = zSchema.expression; 220 + // END TODO: remove later 221 + 222 + // if (ast.expression) { 223 + // if (schema.accessScope === 'read') { 224 + // ast.expression = tsc.callExpression({ 225 + // functionName: tsc.propertyAccessExpression({ 226 + // expression: ast.expression, 227 + // name: identifiers.readonly, 228 + // }), 229 + // }); 230 + // } 231 + 232 + // if (optional) { 233 + // ast.expression = tsc.callExpression({ 234 + // functionName: tsc.propertyAccessExpression({ 235 + // expression: z.placeholder, 236 + // name: identifiers.optional, 237 + // }), 238 + // parameters: [ast.expression], 239 + // }); 240 + // ast.typeName = identifiers.ZodOptional; 241 + // } 242 + 243 + // if (schema.default !== undefined) { 244 + // const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 245 + // const callParameter = numberParameter({ 246 + // isBigInt, 247 + // value: schema.default, 248 + // }); 249 + // if (callParameter) { 250 + // ast.expression = tsc.callExpression({ 251 + // functionName: tsc.propertyAccessExpression({ 252 + // expression: ast.expression, 253 + // name: identifiers.default, 254 + // }), 255 + // parameters: [callParameter], 256 + // }); 257 + // } 258 + // } 259 + // } 260 + 261 + return ast as Ast; 262 + }; 263 + 264 + const handleComponent = ({ 265 + id, 266 + plugin, 267 + schema, 268 + state: _state, 269 + }: Omit<IrSchemaToAstOptions, 'state'> & { 270 + id: string; 271 + schema: IR.SchemaObject; 272 + state?: Partial<IrSchemaToAstOptions['state']>; 273 + }): void => { 274 + const state: IrSchemaToAstOptions['state'] = { 275 + _path: _state?._path ?? [], 276 + circularReferenceTracker: _state?.circularReferenceTracker ?? [id], 277 + currentReferenceTracker: _state?.currentReferenceTracker ?? [id], 278 + hasCircularReference: _state?.hasCircularReference ?? false, 279 + }; 280 + 281 + const selector = plugin.api.getSelector('ref', id); 282 + let symbol = plugin.getSymbol(selector); 283 + if (symbol && !plugin.getSymbolValue(symbol)) return; 284 + 285 + const ast = irSchemaToAst({ plugin, schema, state }); 286 + const baseName = refToName(id); 287 + const resourceType = pathToSymbolResourceType(state._path); 288 + symbol = plugin.registerSymbol({ 289 + exported: true, 290 + meta: { 291 + resourceType, 292 + }, 293 + name: buildName({ 294 + config: plugin.config.definitions, 295 + name: baseName, 296 + }), 297 + selector, 298 + }); 299 + const typeInferSymbol = plugin.config.definitions.types.infer.enabled 300 + ? plugin.registerSymbol({ 301 + exported: true, 302 + meta: { 303 + kind: 'type', 304 + resourceType, 305 + }, 306 + name: buildName({ 307 + config: plugin.config.definitions.types.infer, 308 + name: baseName, 309 + }), 310 + selector: plugin.api.getSelector('type-infer-ref', id), 311 + }) 312 + : undefined; 313 + exportAst({ 314 + ast, 315 + plugin, 316 + schema, 317 + symbol, 318 + typeInferSymbol, 319 + }); 320 + }; 321 + 322 + export const handlerV2: ArktypePlugin['Handler'] = ({ plugin }) => { 323 + const typeSymbol = plugin.registerSymbol({ 324 + external: 'arktype', 325 + name: 'type', 326 + selector: plugin.api.getSelector('external', 'arktype.type'), 327 + }); 328 + 329 + plugin.forEach( 330 + 'operation', 331 + 'parameter', 332 + 'requestBody', 333 + 'schema', 334 + 'webhook', 335 + (event) => { 336 + if (event.type === 'schema') { 337 + const symbol = plugin.registerSymbol({ 338 + exported: true, 339 + meta: { 340 + resourceType: pathToSymbolResourceType([ 341 + 'components', 342 + 'schemas', 343 + event.name, 344 + ]), 345 + }, 346 + name: event.name, 347 + selector: plugin.api.getSelector('ref', event.$ref), 348 + }); 349 + 350 + const userNode = ts.factory.createVariableStatement( 351 + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], 352 + ts.factory.createVariableDeclarationList( 353 + [ 354 + ts.factory.createVariableDeclaration( 355 + symbol.placeholder, 356 + undefined, 357 + undefined, 358 + ts.factory.createCallExpression( 359 + ts.factory.createIdentifier(typeSymbol.placeholder), 360 + undefined, 361 + [ 362 + ts.factory.createObjectLiteralExpression( 363 + [ 364 + ts.factory.createPropertyAssignment( 365 + 'name', 366 + ts.factory.createStringLiteral('string'), 367 + ), 368 + ts.factory.createPropertyAssignment( 369 + 'platform', 370 + ts.factory.createStringLiteral("'android' | 'ios'"), 371 + ), 372 + ts.factory.createPropertyAssignment( 373 + ts.factory.createComputedPropertyName( 374 + ts.factory.createStringLiteral('versions?'), 375 + ), 376 + ts.factory.createStringLiteral('(number | string)[]'), 377 + ), 378 + ], 379 + true, 380 + ), 381 + ], 382 + ), 383 + ), 384 + ], 385 + ts.NodeFlags.Const, 386 + ), 387 + ); 388 + 389 + plugin.setSymbolValue(symbol, userNode); 390 + } 391 + 392 + switch (event.type) { 393 + // case 'operation': 394 + // operationToZodSchema({ 395 + // getZodSchema: (schema) => { 396 + // const state: State = { 397 + // circularReferenceTracker: [], 398 + // currentReferenceTracker: [], 399 + // hasCircularReference: false, 400 + // }; 401 + // return schemaToZodSchema({ plugin, schema, state }); 402 + // }, 403 + // operation: event.operation, 404 + // plugin, 405 + // }); 406 + // break; 407 + case 'parameter': 408 + handleComponent({ 409 + id: event.$ref, 410 + plugin, 411 + schema: event.parameter.schema, 412 + state: { 413 + _path: event._path, 414 + }, 415 + }); 416 + break; 417 + case 'requestBody': 418 + handleComponent({ 419 + id: event.$ref, 420 + plugin, 421 + schema: event.requestBody.schema, 422 + state: { 423 + _path: event._path, 424 + }, 425 + }); 426 + break; 427 + case 'schema': 428 + handleComponent({ 429 + id: event.$ref, 430 + plugin, 431 + schema: event.schema, 432 + state: { 433 + _path: event._path, 434 + }, 435 + }); 436 + break; 437 + // case 'webhook': 438 + // webhookToZodSchema({ 439 + // getZodSchema: (schema) => { 440 + // const state: State = { 441 + // circularReferenceTracker: [], 442 + // currentReferenceTracker: [], 443 + // hasCircularReference: false, 444 + // }; 445 + // return schemaToZodSchema({ plugin, schema, state }); 446 + // }, 447 + // operation: event.operation, 448 + // plugin, 449 + // }); 450 + // break; 451 + } 452 + }, 453 + ); 454 + };
+122
packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import type { SchemaWithType } from '../../../shared/types/schema'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + // import { arrayToAst } from "./array"; 6 + // import { booleanToAst } from "./boolean"; 7 + // import { enumToAst } from "./enum"; 8 + // import { neverToAst } from "./never"; 9 + // import { nullToAst } from "./null"; 10 + // import { numberToAst } from "./number"; 11 + // import { objectToAst } from "./object"; 12 + // import { stringToAst } from "./string"; 13 + // import { tupleToAst } from "./tuple"; 14 + // import { undefinedToAst } from "./undefined"; 15 + // import { unknownToAst } from "./unknown"; 16 + // import { voidToAst } from "./void"; 17 + 18 + export const irSchemaWithTypeToAst = ({ 19 + schema, 20 + ...args 21 + }: IrSchemaToAstOptions & { 22 + schema: SchemaWithType; 23 + }): Omit<Ast, 'typeName'> => { 24 + const type = args.plugin.referenceSymbol( 25 + args.plugin.api.getSelector('external', 'arktype.type'), 26 + ); 27 + 28 + const expression = ts.factory.createCallExpression( 29 + ts.factory.createIdentifier(type.placeholder), 30 + undefined, 31 + [ 32 + ts.factory.createObjectLiteralExpression( 33 + [ 34 + ts.factory.createPropertyAssignment( 35 + 'name', 36 + ts.factory.createStringLiteral('string'), 37 + ), 38 + ts.factory.createPropertyAssignment( 39 + 'platform', 40 + ts.factory.createStringLiteral("'android' | 'ios'"), 41 + ), 42 + ts.factory.createPropertyAssignment( 43 + ts.factory.createComputedPropertyName( 44 + ts.factory.createStringLiteral('versions?'), 45 + ), 46 + ts.factory.createStringLiteral('(number | string)[]'), 47 + ), 48 + ], 49 + true, 50 + ), 51 + ], 52 + ); 53 + 54 + switch (schema.type) { 55 + // case 'array': 56 + // return arrayToAst({ 57 + // ...args, 58 + // schema: schema as SchemaWithType<'array'>, 59 + // }); 60 + // case 'boolean': 61 + // return booleanToAst({ 62 + // ...args, 63 + // schema: schema as SchemaWithType<'boolean'>, 64 + // }); 65 + // case 'enum': 66 + // return enumToAst({ 67 + // ...args, 68 + // schema: schema as SchemaWithType<'enum'>, 69 + // }); 70 + // case 'integer': 71 + // case 'number': 72 + // return numberToAst({ 73 + // ...args, 74 + // schema: schema as SchemaWithType<'integer' | 'number'>, 75 + // }); 76 + // case 'never': 77 + // return neverToAst({ 78 + // ...args, 79 + // schema: schema as SchemaWithType<'never'>, 80 + // }); 81 + // case 'null': 82 + // return nullToAst({ 83 + // ...args, 84 + // schema: schema as SchemaWithType<'null'>, 85 + // }); 86 + // case 'object': 87 + // return objectToAst({ 88 + // ...args, 89 + // schema: schema as SchemaWithType<'object'>, 90 + // }); 91 + // case 'string': 92 + // return stringToAst({ 93 + // ...args, 94 + // schema: schema as SchemaWithType<'string'>, 95 + // }); 96 + // case 'tuple': 97 + // return tupleToAst({ 98 + // ...args, 99 + // schema: schema as SchemaWithType<'tuple'>, 100 + // }); 101 + // case 'undefined': 102 + // return undefinedToAst({ 103 + // ...args, 104 + // schema: schema as SchemaWithType<'undefined'>, 105 + // }); 106 + // case 'unknown': 107 + // return unknownToAst({ 108 + // ...args, 109 + // schema: schema as SchemaWithType<'unknown'>, 110 + // }); 111 + // case 'void': 112 + // return voidToAst({ 113 + // ...args, 114 + // schema: schema as SchemaWithType<'void'>, 115 + // }); 116 + default: 117 + return { 118 + expression, 119 + hasCircularReference: false, 120 + }; 121 + } 122 + };
+4
packages/openapi-ts/src/plugins/config.ts
··· 42 42 import { defaultConfig as tanStackSvelteQuery } from './@tanstack/svelte-query'; 43 43 import type { TanStackVueQueryPlugin } from './@tanstack/vue-query'; 44 44 import { defaultConfig as tanStackVueQuery } from './@tanstack/vue-query'; 45 + import type { ArktypePlugin } from './arktype'; 46 + import { defaultConfig as arktype } from './arktype'; 45 47 import type { FastifyPlugin } from './fastify'; 46 48 import { defaultConfig as fastify } from './fastify'; 47 49 import type { Plugin, PluginNames } from './types'; ··· 68 70 '@tanstack/solid-query': TanStackSolidQueryPlugin['Types']; 69 71 '@tanstack/svelte-query': TanStackSvelteQueryPlugin['Types']; 70 72 '@tanstack/vue-query': TanStackVueQueryPlugin['Types']; 73 + arktype: ArktypePlugin['Types']; 71 74 fastify: FastifyPlugin['Types']; 72 75 'legacy/angular': HeyApiClientLegacyAngularPlugin['Types']; 73 76 'legacy/axios': HeyApiClientLegacyAxiosPlugin['Types']; ··· 98 101 '@tanstack/solid-query': tanStackSolidQuery, 99 102 '@tanstack/svelte-query': tanStackSvelteQuery, 100 103 '@tanstack/vue-query': tanStackVueQuery, 104 + arktype, 101 105 fastify, 102 106 'legacy/angular': heyApiLegacyAngular, 103 107 'legacy/axios': heyApiLegacyAxios,
+8
packages/openapi-ts/src/plugins/shared/types/schema.d.ts
··· 1 + import type { IR } from '../../../ir/types'; 2 + 3 + export interface SchemaWithType< 4 + T extends 5 + Required<IR.SchemaObject>['type'] = Required<IR.SchemaObject>['type'], 6 + > extends Omit<IR.SchemaObject, 'type'> { 7 + type: Extract<Required<IR.SchemaObject>['type'], T>; 8 + }
+1 -1
packages/openapi-ts/src/plugins/types.d.ts
··· 19 19 | 'legacy/node' 20 20 | 'legacy/xhr'; 21 21 22 - export type PluginValidatorNames = 'valibot' | 'zod'; 22 + export type PluginValidatorNames = 'arktype' | 'valibot' | 'zod'; 23 23 24 24 export type PluginNames = 25 25 | PluginClientNames
+13 -89
packages/openapi-ts/src/plugins/valibot/api.ts
··· 1 1 import type { Selector } from '@hey-api/codegen-core'; 2 2 import type ts from 'typescript'; 3 3 4 - import type { IR } from '../../ir/types'; 5 - import { tsc } from '../../tsc'; 6 4 import type { Plugin } from '../types'; 7 - import { identifiers } from './constants'; 8 - import type { ValibotPlugin } from './types'; 5 + import type { ValidatorArgs } from './shared/types'; 6 + import { createRequestValidatorV1, createResponseValidatorV1 } from './v1/api'; 9 7 10 - type SelectorType = 'data' | 'import' | 'ref' | 'responses' | 'webhook-request'; 11 - 12 - type ValidatorArgs = { 13 - operation: IR.OperationObject; 14 - plugin: ValibotPlugin['Instance']; 15 - }; 8 + type SelectorType = 9 + | 'data' 10 + | 'external' 11 + | 'ref' 12 + | 'responses' 13 + | 'webhook-request'; 16 14 17 15 export type IApi = { 18 16 createRequestValidator: (args: ValidatorArgs) => ts.ArrowFunction | undefined; ··· 23 21 * @param type Selector type. 24 22 * @param value Depends on `type`: 25 23 * - `data`: `operation.id` string 26 - * - `import`: headless symbols representing module imports 24 + * - `external`: external modules 27 25 * - `ref`: `$ref` JSON pointer 28 26 * - `responses`: `operation.id` string 29 27 * - `webhook-request`: `operation.id` string ··· 35 33 export class Api implements IApi { 36 34 constructor(public meta: Plugin.Name<'valibot'>) {} 37 35 38 - createRequestValidator({ 39 - operation, 40 - plugin, 41 - }: ValidatorArgs): ts.ArrowFunction | undefined { 42 - const symbol = plugin.getSymbol( 43 - plugin.api.getSelector('data', operation.id), 44 - ); 45 - if (!symbol) return; 46 - 47 - const vSymbol = plugin.referenceSymbol( 48 - plugin.api.getSelector('import', 'valibot'), 49 - ); 50 - 51 - const dataParameterName = 'data'; 52 - 53 - return tsc.arrowFunction({ 54 - async: true, 55 - parameters: [ 56 - { 57 - name: dataParameterName, 58 - }, 59 - ], 60 - statements: [ 61 - tsc.returnStatement({ 62 - expression: tsc.awaitExpression({ 63 - expression: tsc.callExpression({ 64 - functionName: tsc.propertyAccessExpression({ 65 - expression: vSymbol.placeholder, 66 - name: identifiers.async.parseAsync, 67 - }), 68 - parameters: [ 69 - tsc.identifier({ text: symbol.placeholder }), 70 - tsc.identifier({ text: dataParameterName }), 71 - ], 72 - }), 73 - }), 74 - }), 75 - ], 76 - }); 36 + createRequestValidator(args: ValidatorArgs): ts.ArrowFunction | undefined { 37 + return createRequestValidatorV1(args); 77 38 } 78 39 79 - createResponseValidator({ 80 - operation, 81 - plugin, 82 - }: ValidatorArgs): ts.ArrowFunction | undefined { 83 - const symbol = plugin.getSymbol( 84 - plugin.api.getSelector('responses', operation.id), 85 - ); 86 - if (!symbol) return; 87 - 88 - const vSymbol = plugin.referenceSymbol( 89 - plugin.api.getSelector('import', 'valibot'), 90 - ); 91 - 92 - const dataParameterName = 'data'; 93 - 94 - return tsc.arrowFunction({ 95 - async: true, 96 - parameters: [ 97 - { 98 - name: dataParameterName, 99 - }, 100 - ], 101 - statements: [ 102 - tsc.returnStatement({ 103 - expression: tsc.awaitExpression({ 104 - expression: tsc.callExpression({ 105 - functionName: tsc.propertyAccessExpression({ 106 - expression: vSymbol.placeholder, 107 - name: identifiers.async.parseAsync, 108 - }), 109 - parameters: [ 110 - tsc.identifier({ text: symbol.placeholder }), 111 - tsc.identifier({ text: dataParameterName }), 112 - ], 113 - }), 114 - }), 115 - }), 116 - ], 117 - }); 40 + createResponseValidator(args: ValidatorArgs): ts.ArrowFunction | undefined { 41 + return createResponseValidatorV1(args); 118 42 } 119 43 120 44 getSelector(...args: ReadonlyArray<string | undefined>): Selector {
+1 -1
packages/openapi-ts/src/plugins/valibot/constants.ts packages/openapi-ts/src/plugins/valibot/v1/constants.ts
··· 1 - import { tsc } from '../../tsc'; 1 + import { tsc } from '../../../tsc'; 2 2 3 3 export const identifiers = { 4 4 /**
+1 -2
packages/openapi-ts/src/plugins/valibot/number-helpers.ts packages/openapi-ts/src/plugins/valibot/shared/numbers.ts
··· 1 - import { tsc } from '../../tsc'; 1 + import { tsc } from '../../../tsc'; 2 2 3 - // Integer format ranges and properties 4 3 export const INTEGER_FORMATS = { 5 4 int16: { 6 5 max: 32767,
+16 -19
packages/openapi-ts/src/plugins/valibot/operation.ts packages/openapi-ts/src/plugins/valibot/v1/operation.ts
··· 1 - import { operationResponsesMap } from '../../ir/operation'; 2 - import type { IR } from '../../ir/types'; 3 - import { buildName } from '../../openApi/shared/utils/name'; 4 - import { pathToSymbolResourceType } from '../shared/utils/meta'; 5 - import { schemaToValibotSchema, type State } from './plugin'; 6 - import type { ValibotPlugin } from './types'; 1 + import { operationResponsesMap } from '../../../ir/operation'; 2 + import type { IR } from '../../../ir/types'; 3 + import { buildName } from '../../../openApi/shared/utils/name'; 4 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 5 + import type { IrSchemaToAstOptions } from '../shared/types'; 6 + import { irSchemaToAst } from './plugin'; 7 7 8 - export const operationToValibotSchema = ({ 9 - _path, 8 + export const irOperationToAst = ({ 10 9 operation, 11 10 plugin, 12 11 state, 13 - }: { 14 - _path: ReadonlyArray<string | number>; 12 + }: IrSchemaToAstOptions & { 15 13 operation: IR.OperationObject; 16 - plugin: ValibotPlugin['Instance']; 17 - state: State; 18 14 }) => { 19 15 if (plugin.config.requests.enabled) { 20 16 const requiredProperties = new Set<string>(); ··· 117 113 const symbol = plugin.registerSymbol({ 118 114 exported: true, 119 115 meta: { 120 - resourceType: pathToSymbolResourceType(_path), 116 + resourceType: pathToSymbolResourceType(state._path), 121 117 }, 122 118 name: buildName({ 123 119 config: plugin.config.requests, ··· 125 121 }), 126 122 selector: plugin.api.getSelector('data', operation.id), 127 123 }); 128 - schemaToValibotSchema({ 129 - _path, 124 + irSchemaToAst({ 130 125 plugin, 131 126 schema: schemaData, 132 127 state, ··· 139 134 const { response } = operationResponsesMap(operation); 140 135 141 136 if (response) { 142 - const path = [..._path, 'responses']; 137 + const path = [...state._path, 'responses']; 143 138 const symbol = plugin.registerSymbol({ 144 139 exported: true, 145 140 meta: { ··· 151 146 }), 152 147 selector: plugin.api.getSelector('responses', operation.id), 153 148 }); 154 - schemaToValibotSchema({ 155 - _path: path, 149 + irSchemaToAst({ 156 150 plugin, 157 151 schema: response, 158 - state, 152 + state: { 153 + ...state, 154 + _path: path, 155 + }, 159 156 symbol, 160 157 }); 161 158 }
+2 -1373
packages/openapi-ts/src/plugins/valibot/plugin.ts
··· 1 - import type { Symbol } from '@hey-api/codegen-core'; 2 - import ts from 'typescript'; 3 - 4 - import { deduplicateSchema } from '../../ir/schema'; 5 - import type { IR } from '../../ir/types'; 6 - import { buildName } from '../../openApi/shared/utils/name'; 7 - import { tsc } from '../../tsc'; 8 - import type { StringCase, StringName } from '../../types/case'; 9 - import { jsonPointerToPath, refToName } from '../../utils/ref'; 10 - import { numberRegExp } from '../../utils/regexp'; 11 - import { pathToSymbolResourceType } from '../shared/utils/meta'; 12 - import { createSchemaComment } from '../shared/utils/schema'; 13 - import type { SchemaWithType } from '../zod/shared/types'; 14 - import { identifiers } from './constants'; 15 - import { 16 - INTEGER_FORMATS, 17 - isIntegerFormat, 18 - needsBigIntForFormat, 19 - numberParameter, 20 - } from './number-helpers'; 21 - import { operationToValibotSchema } from './operation'; 22 1 import type { ValibotPlugin } from './types'; 23 - import { webhookToValibotSchema } from './webhook'; 24 - 25 - export interface State { 26 - circularReferenceTracker: Set<string>; 27 - hasCircularReference: boolean; 28 - nameCase: StringCase; 29 - nameTransformer: StringName; 30 - } 31 - 32 - type SchemaToValibotSchemaOptions = { 33 - /** 34 - * Path to the schema in the intermediary representation. 35 - */ 36 - _path: ReadonlyArray<string | number>; 37 - plugin: ValibotPlugin['Instance']; 38 - }; 39 - 40 - const pipesToExpression = ({ 41 - pipes, 42 - plugin, 43 - }: { 44 - pipes: Array<ts.Expression>; 45 - plugin: ValibotPlugin['Instance']; 46 - }) => { 47 - if (pipes.length === 1) { 48 - return pipes[0]!; 49 - } 50 - 51 - const vSymbol = plugin.referenceSymbol( 52 - plugin.api.getSelector('import', 'valibot'), 53 - ); 54 - const expression = tsc.callExpression({ 55 - functionName: tsc.propertyAccessExpression({ 56 - expression: vSymbol.placeholder, 57 - name: identifiers.methods.pipe, 58 - }), 59 - parameters: pipes, 60 - }); 61 - return expression; 62 - }; 63 - 64 - const arrayTypeToValibotSchema = ({ 65 - _path, 66 - plugin, 67 - schema, 68 - state, 69 - }: SchemaToValibotSchemaOptions & { 70 - schema: SchemaWithType<'array'>; 71 - state: State; 72 - }): ts.Expression => { 73 - const vSymbol = plugin.referenceSymbol( 74 - plugin.api.getSelector('import', 'valibot'), 75 - ); 76 - const functionName = tsc.propertyAccessExpression({ 77 - expression: vSymbol.placeholder, 78 - name: identifiers.schemas.array, 79 - }); 80 - 81 - const pipes: Array<ts.CallExpression> = []; 82 - 83 - if (!schema.items) { 84 - const expression = tsc.callExpression({ 85 - functionName, 86 - parameters: [ 87 - unknownTypeToValibotSchema({ 88 - _path, 89 - plugin, 90 - schema: { 91 - type: 'unknown', 92 - }, 93 - }), 94 - ], 95 - }); 96 - pipes.push(expression); 97 - } else { 98 - schema = deduplicateSchema({ schema }); 99 - 100 - // at least one item is guaranteed 101 - const itemExpressions = schema.items!.map((item, index) => { 102 - const schemaPipes = schemaToValibotSchema({ 103 - _path: [..._path, 'items', index], 104 - plugin, 105 - schema: item, 106 - state, 107 - }); 108 - return pipesToExpression({ pipes: schemaPipes, plugin }); 109 - }); 110 - 111 - if (itemExpressions.length === 1) { 112 - const expression = tsc.callExpression({ 113 - functionName, 114 - parameters: itemExpressions, 115 - }); 116 - pipes.push(expression); 117 - } else { 118 - if (schema.logicalOperator === 'and') { 119 - // TODO: parser - handle intersection 120 - // return tsc.typeArrayNode( 121 - // tsc.typeIntersectionNode({ types: itemExpressions }), 122 - // ); 123 - } 124 - 125 - // TODO: parser - handle union 126 - // return tsc.typeArrayNode(tsc.typeUnionNode({ types: itemExpressions })); 127 - 128 - const expression = tsc.callExpression({ 129 - functionName, 130 - parameters: [ 131 - unknownTypeToValibotSchema({ 132 - _path, 133 - plugin, 134 - schema: { 135 - type: 'unknown', 136 - }, 137 - }), 138 - ], 139 - }); 140 - pipes.push(expression); 141 - } 142 - } 143 - 144 - if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 145 - const expression = tsc.callExpression({ 146 - functionName: tsc.propertyAccessExpression({ 147 - expression: vSymbol.placeholder, 148 - name: identifiers.actions.length, 149 - }), 150 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 151 - }); 152 - pipes.push(expression); 153 - } else { 154 - if (schema.minItems !== undefined) { 155 - const expression = tsc.callExpression({ 156 - functionName: tsc.propertyAccessExpression({ 157 - expression: vSymbol.placeholder, 158 - name: identifiers.actions.minLength, 159 - }), 160 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 161 - }); 162 - pipes.push(expression); 163 - } 164 - 165 - if (schema.maxItems !== undefined) { 166 - const expression = tsc.callExpression({ 167 - functionName: tsc.propertyAccessExpression({ 168 - expression: vSymbol.placeholder, 169 - name: identifiers.actions.maxLength, 170 - }), 171 - parameters: [tsc.valueToExpression({ value: schema.maxItems })], 172 - }); 173 - pipes.push(expression); 174 - } 175 - } 176 - 177 - return pipesToExpression({ pipes, plugin }); 178 - }; 179 - 180 - const booleanTypeToValibotSchema = ({ 181 - plugin, 182 - schema, 183 - }: SchemaToValibotSchemaOptions & { 184 - schema: SchemaWithType<'boolean'>; 185 - }) => { 186 - const vSymbol = plugin.referenceSymbol( 187 - plugin.api.getSelector('import', 'valibot'), 188 - ); 189 - 190 - if (typeof schema.const === 'boolean') { 191 - const expression = tsc.callExpression({ 192 - functionName: tsc.propertyAccessExpression({ 193 - expression: vSymbol.placeholder, 194 - name: identifiers.schemas.literal, 195 - }), 196 - parameters: [tsc.ots.boolean(schema.const)], 197 - }); 198 - return expression; 199 - } 200 - 201 - const expression = tsc.callExpression({ 202 - functionName: tsc.propertyAccessExpression({ 203 - expression: vSymbol.placeholder, 204 - name: identifiers.schemas.boolean, 205 - }), 206 - }); 207 - return expression; 208 - }; 209 - 210 - const enumTypeToValibotSchema = ({ 211 - _path, 212 - plugin, 213 - schema, 214 - }: SchemaToValibotSchemaOptions & { 215 - schema: SchemaWithType<'enum'>; 216 - }): ts.CallExpression => { 217 - const enumMembers: Array<ts.LiteralExpression> = []; 218 - 219 - let isNullable = false; 220 - 221 - for (const item of schema.items ?? []) { 222 - // Zod supports only string enums 223 - if (item.type === 'string' && typeof item.const === 'string') { 224 - enumMembers.push( 225 - tsc.stringLiteral({ 226 - text: item.const, 227 - }), 228 - ); 229 - } else if (item.type === 'null' || item.const === null) { 230 - isNullable = true; 231 - } 232 - } 233 - 234 - if (!enumMembers.length) { 235 - return unknownTypeToValibotSchema({ 236 - _path, 237 - plugin, 238 - schema: { 239 - type: 'unknown', 240 - }, 241 - }); 242 - } 243 - 244 - const vSymbol = plugin.referenceSymbol( 245 - plugin.api.getSelector('import', 'valibot'), 246 - ); 247 - 248 - let resultExpression = tsc.callExpression({ 249 - functionName: tsc.propertyAccessExpression({ 250 - expression: vSymbol.placeholder, 251 - name: identifiers.schemas.picklist, 252 - }), 253 - parameters: [ 254 - tsc.arrayLiteralExpression({ 255 - elements: enumMembers, 256 - multiLine: false, 257 - }), 258 - ], 259 - }); 260 - 261 - if (isNullable) { 262 - resultExpression = tsc.callExpression({ 263 - functionName: tsc.propertyAccessExpression({ 264 - expression: vSymbol.placeholder, 265 - name: identifiers.schemas.nullable, 266 - }), 267 - parameters: [resultExpression], 268 - }); 269 - } 270 - 271 - return resultExpression; 272 - }; 273 - 274 - const neverTypeToValibotSchema = ({ 275 - plugin, 276 - }: SchemaToValibotSchemaOptions & { 277 - schema: SchemaWithType<'never'>; 278 - }) => { 279 - const vSymbol = plugin.referenceSymbol( 280 - plugin.api.getSelector('import', 'valibot'), 281 - ); 282 - const expression = tsc.callExpression({ 283 - functionName: tsc.propertyAccessExpression({ 284 - expression: vSymbol.placeholder, 285 - name: identifiers.schemas.never, 286 - }), 287 - }); 288 - return expression; 289 - }; 290 - 291 - const nullTypeToValibotSchema = ({ 292 - plugin, 293 - }: SchemaToValibotSchemaOptions & { 294 - schema: SchemaWithType<'null'>; 295 - }) => { 296 - const vSymbol = plugin.referenceSymbol( 297 - plugin.api.getSelector('import', 'valibot'), 298 - ); 299 - const expression = tsc.callExpression({ 300 - functionName: tsc.propertyAccessExpression({ 301 - expression: vSymbol.placeholder, 302 - name: identifiers.schemas.null, 303 - }), 304 - }); 305 - return expression; 306 - }; 307 - 308 - const numberTypeToValibotSchema = ({ 309 - plugin, 310 - schema, 311 - }: SchemaToValibotSchemaOptions & { 312 - schema: SchemaWithType<'integer' | 'number'>; 313 - }) => { 314 - const format = schema.format; 315 - const isInteger = schema.type === 'integer'; 316 - const isBigInt = needsBigIntForFormat(format); 317 - const formatInfo = isIntegerFormat(format) ? INTEGER_FORMATS[format] : null; 318 - 319 - const vSymbol = plugin.referenceSymbol( 320 - plugin.api.getSelector('import', 'valibot'), 321 - ); 322 - 323 - // Return early if const is defined since we can create a literal type directly without additional validation 324 - if (schema.const !== undefined && schema.const !== null) { 325 - const constValue = schema.const; 326 - let literalValue; 327 - 328 - // Case 1: Number with no format -> generate literal with the number 329 - if (typeof constValue === 'number' && !format) { 330 - literalValue = tsc.ots.number(constValue); 331 - } 332 - // Case 2: Number with format -> check if format needs BigInt, generate appropriate literal 333 - else if (typeof constValue === 'number' && format) { 334 - if (isBigInt) { 335 - // Format requires BigInt, convert number to BigInt 336 - literalValue = tsc.callExpression({ 337 - functionName: 'BigInt', 338 - parameters: [tsc.ots.string(constValue.toString())], 339 - }); 340 - } else { 341 - // Regular format, use number as-is 342 - literalValue = tsc.ots.number(constValue); 343 - } 344 - } 345 - // Case 3: Format that allows string -> generate BigInt literal (for int64/uint64 formats) 346 - else if (typeof constValue === 'string' && isBigInt) { 347 - // Remove 'n' suffix if present in string 348 - const cleanString = constValue.endsWith('n') 349 - ? constValue.slice(0, -1) 350 - : constValue; 351 - literalValue = tsc.callExpression({ 352 - functionName: 'BigInt', 353 - parameters: [tsc.ots.string(cleanString)], 354 - }); 355 - } 356 - // Case 4: Const is typeof bigint (literal) -> transform from literal to BigInt() 357 - else if (typeof constValue === 'bigint') { 358 - // Convert BigInt to string and remove 'n' suffix that toString() adds 359 - const bigintString = constValue.toString(); 360 - const cleanString = bigintString.endsWith('n') 361 - ? bigintString.slice(0, -1) 362 - : bigintString; 363 - literalValue = tsc.callExpression({ 364 - functionName: 'BigInt', 365 - parameters: [tsc.ots.string(cleanString)], 366 - }); 367 - } 368 - // Default case: use value as-is for other types 369 - else { 370 - literalValue = tsc.valueToExpression({ value: constValue }); 371 - } 372 - 373 - return tsc.callExpression({ 374 - functionName: tsc.propertyAccessExpression({ 375 - expression: vSymbol.placeholder, 376 - name: identifiers.schemas.literal, 377 - }), 378 - parameters: [literalValue], 379 - }); 380 - } 381 - 382 - const pipes: Array<ts.CallExpression> = []; 383 - 384 - // For bigint formats (int64, uint64), create union of number, string, and bigint with transform 385 - if (isBigInt) { 386 - const unionExpression = tsc.callExpression({ 387 - functionName: tsc.propertyAccessExpression({ 388 - expression: vSymbol.placeholder, 389 - name: identifiers.schemas.union, 390 - }), 391 - parameters: [ 392 - tsc.arrayLiteralExpression({ 393 - elements: [ 394 - tsc.callExpression({ 395 - functionName: tsc.propertyAccessExpression({ 396 - expression: vSymbol.placeholder, 397 - name: identifiers.schemas.number, 398 - }), 399 - }), 400 - tsc.callExpression({ 401 - functionName: tsc.propertyAccessExpression({ 402 - expression: vSymbol.placeholder, 403 - name: identifiers.schemas.string, 404 - }), 405 - }), 406 - tsc.callExpression({ 407 - functionName: tsc.propertyAccessExpression({ 408 - expression: vSymbol.placeholder, 409 - name: identifiers.schemas.bigInt, 410 - }), 411 - }), 412 - ], 413 - multiLine: false, 414 - }), 415 - ], 416 - }); 417 - pipes.push(unionExpression); 418 - 419 - // Add transform to convert to BigInt 420 - const transformExpression = tsc.callExpression({ 421 - functionName: tsc.propertyAccessExpression({ 422 - expression: vSymbol.placeholder, 423 - name: identifiers.actions.transform, 424 - }), 425 - parameters: [ 426 - tsc.arrowFunction({ 427 - parameters: [{ name: 'x' }], 428 - statements: tsc.callExpression({ 429 - functionName: 'BigInt', 430 - parameters: [tsc.identifier({ text: 'x' })], 431 - }), 432 - }), 433 - ], 434 - }); 435 - pipes.push(transformExpression); 436 - } else { 437 - // For regular number formats, use number schema 438 - const expression = tsc.callExpression({ 439 - functionName: tsc.propertyAccessExpression({ 440 - expression: vSymbol.placeholder, 441 - name: identifiers.schemas.number, 442 - }), 443 - }); 444 - pipes.push(expression); 445 - } 446 - 447 - // Add integer validation for integer types (except when using bigint union) 448 - if (!isBigInt && isInteger) { 449 - const expression = tsc.callExpression({ 450 - functionName: tsc.propertyAccessExpression({ 451 - expression: vSymbol.placeholder, 452 - name: identifiers.actions.integer, 453 - }), 454 - }); 455 - pipes.push(expression); 456 - } 457 - 458 - // Add format-specific range validations 459 - if (formatInfo) { 460 - const minValue = formatInfo.min; 461 - const maxValue = formatInfo.max; 462 - const minErrorMessage = formatInfo.minError; 463 - const maxErrorMessage = formatInfo.maxError; 464 - 465 - // Add minimum value validation 466 - const minExpression = tsc.callExpression({ 467 - functionName: tsc.propertyAccessExpression({ 468 - expression: vSymbol.placeholder, 469 - name: identifiers.actions.minValue, 470 - }), 471 - parameters: [ 472 - isBigInt 473 - ? tsc.callExpression({ 474 - functionName: 'BigInt', 475 - parameters: [tsc.ots.string(minValue.toString())], 476 - }) 477 - : tsc.ots.number(minValue as number), 478 - tsc.ots.string(minErrorMessage), 479 - ], 480 - }); 481 - pipes.push(minExpression); 482 - 483 - // Add maximum value validation 484 - const maxExpression = tsc.callExpression({ 485 - functionName: tsc.propertyAccessExpression({ 486 - expression: vSymbol.placeholder, 487 - name: identifiers.actions.maxValue, 488 - }), 489 - parameters: [ 490 - isBigInt 491 - ? tsc.callExpression({ 492 - functionName: 'BigInt', 493 - parameters: [tsc.ots.string(maxValue.toString())], 494 - }) 495 - : tsc.ots.number(maxValue as number), 496 - tsc.ots.string(maxErrorMessage), 497 - ], 498 - }); 499 - pipes.push(maxExpression); 500 - } 501 - 502 - if (schema.exclusiveMinimum !== undefined) { 503 - const expression = tsc.callExpression({ 504 - functionName: tsc.propertyAccessExpression({ 505 - expression: vSymbol.placeholder, 506 - name: identifiers.actions.gtValue, 507 - }), 508 - parameters: [ 509 - numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 510 - ], 511 - }); 512 - pipes.push(expression); 513 - } else if (schema.minimum !== undefined) { 514 - const expression = tsc.callExpression({ 515 - functionName: tsc.propertyAccessExpression({ 516 - expression: vSymbol.placeholder, 517 - name: identifiers.actions.minValue, 518 - }), 519 - parameters: [numberParameter({ isBigInt, value: schema.minimum })], 520 - }); 521 - pipes.push(expression); 522 - } 523 - 524 - if (schema.exclusiveMaximum !== undefined) { 525 - const expression = tsc.callExpression({ 526 - functionName: tsc.propertyAccessExpression({ 527 - expression: vSymbol.placeholder, 528 - name: identifiers.actions.ltValue, 529 - }), 530 - parameters: [ 531 - numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 532 - ], 533 - }); 534 - pipes.push(expression); 535 - } else if (schema.maximum !== undefined) { 536 - const expression = tsc.callExpression({ 537 - functionName: tsc.propertyAccessExpression({ 538 - expression: vSymbol.placeholder, 539 - name: identifiers.actions.maxValue, 540 - }), 541 - parameters: [numberParameter({ isBigInt, value: schema.maximum })], 542 - }); 543 - pipes.push(expression); 544 - } 545 - 546 - return pipesToExpression({ pipes, plugin }); 547 - }; 548 - 549 - const objectTypeToValibotSchema = ({ 550 - _path, 551 - plugin, 552 - schema, 553 - state, 554 - }: SchemaToValibotSchemaOptions & { 555 - schema: SchemaWithType<'object'>; 556 - state: State; 557 - }): { 558 - anyType: string; 559 - expression: ts.CallExpression; 560 - } => { 561 - // TODO: parser - handle constants 562 - const properties: Array<ts.PropertyAssignment> = []; 563 - 564 - const required = schema.required ?? []; 565 - 566 - for (const name in schema.properties) { 567 - const property = schema.properties[name]!; 568 - const isRequired = required.includes(name); 569 - 570 - const schemaPipes = schemaToValibotSchema({ 571 - _path: [..._path, 'properties', name], 572 - optional: !isRequired, 573 - plugin, 574 - schema: property, 575 - state, 576 - }); 577 - 578 - numberRegExp.lastIndex = 0; 579 - let propertyName; 580 - if (numberRegExp.test(name)) { 581 - // For numeric literals, we'll handle negative numbers by using a string literal 582 - // instead of trying to use a PrefixUnaryExpression 583 - propertyName = name.startsWith('-') 584 - ? ts.factory.createStringLiteral(name) 585 - : ts.factory.createNumericLiteral(name); 586 - } else { 587 - propertyName = name; 588 - } 589 - // TODO: parser - abstract safe property name logic 590 - if ( 591 - ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 592 - !name.startsWith("'") && 593 - !name.endsWith("'") 594 - ) { 595 - propertyName = `'${name}'`; 596 - } 597 - properties.push( 598 - tsc.propertyAssignment({ 599 - initializer: pipesToExpression({ pipes: schemaPipes, plugin }), 600 - name: propertyName, 601 - }), 602 - ); 603 - } 604 - 605 - const vSymbol = plugin.referenceSymbol( 606 - plugin.api.getSelector('import', 'valibot'), 607 - ); 608 - 609 - if ( 610 - schema.additionalProperties && 611 - schema.additionalProperties.type === 'object' && 612 - !Object.keys(properties).length 613 - ) { 614 - const pipes = schemaToValibotSchema({ 615 - _path: [..._path, 'additionalProperties'], 616 - plugin, 617 - schema: schema.additionalProperties, 618 - state, 619 - }); 620 - const expression = tsc.callExpression({ 621 - functionName: tsc.propertyAccessExpression({ 622 - expression: vSymbol.placeholder, 623 - name: identifiers.schemas.record, 624 - }), 625 - parameters: [ 626 - tsc.callExpression({ 627 - functionName: tsc.propertyAccessExpression({ 628 - expression: vSymbol.placeholder, 629 - name: identifiers.schemas.string, 630 - }), 631 - parameters: [], 632 - }), 633 - pipesToExpression({ pipes, plugin }), 634 - ], 635 - }); 636 - return { 637 - anyType: 'AnyZodObject', 638 - expression, 639 - }; 640 - } 641 - 642 - const expression = tsc.callExpression({ 643 - functionName: tsc.propertyAccessExpression({ 644 - expression: vSymbol.placeholder, 645 - name: identifiers.schemas.object, 646 - }), 647 - parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 648 - }); 649 - return { 650 - // Zod uses AnyZodObject here, maybe we want to be more specific too 651 - anyType: identifiers.types.GenericSchema.text, 652 - expression, 653 - }; 654 - }; 655 - 656 - const stringTypeToValibotSchema = ({ 657 - plugin, 658 - schema, 659 - }: SchemaToValibotSchemaOptions & { 660 - schema: SchemaWithType<'string'>; 661 - }) => { 662 - const vSymbol = plugin.referenceSymbol( 663 - plugin.api.getSelector('import', 'valibot'), 664 - ); 2 + import { handlerV1 } from './v1/plugin'; 665 3 666 - if (typeof schema.const === 'string') { 667 - const expression = tsc.callExpression({ 668 - functionName: tsc.propertyAccessExpression({ 669 - expression: vSymbol.placeholder, 670 - name: identifiers.schemas.literal, 671 - }), 672 - parameters: [tsc.ots.string(schema.const)], 673 - }); 674 - return expression; 675 - } 676 - 677 - const pipes: Array<ts.CallExpression> = []; 678 - 679 - const expression = tsc.callExpression({ 680 - functionName: tsc.propertyAccessExpression({ 681 - expression: vSymbol.placeholder, 682 - name: identifiers.schemas.string, 683 - }), 684 - }); 685 - pipes.push(expression); 686 - 687 - if (schema.format) { 688 - switch (schema.format) { 689 - case 'date': 690 - pipes.push( 691 - tsc.callExpression({ 692 - functionName: tsc.propertyAccessExpression({ 693 - expression: vSymbol.placeholder, 694 - name: identifiers.actions.isoDate, 695 - }), 696 - }), 697 - ); 698 - break; 699 - case 'date-time': 700 - pipes.push( 701 - tsc.callExpression({ 702 - functionName: tsc.propertyAccessExpression({ 703 - expression: vSymbol.placeholder, 704 - name: identifiers.actions.isoTimestamp, 705 - }), 706 - }), 707 - ); 708 - break; 709 - case 'ipv4': 710 - case 'ipv6': 711 - pipes.push( 712 - tsc.callExpression({ 713 - functionName: tsc.propertyAccessExpression({ 714 - expression: vSymbol.placeholder, 715 - name: identifiers.actions.ip, 716 - }), 717 - }), 718 - ); 719 - break; 720 - case 'uri': 721 - pipes.push( 722 - tsc.callExpression({ 723 - functionName: tsc.propertyAccessExpression({ 724 - expression: vSymbol.placeholder, 725 - name: identifiers.actions.url, 726 - }), 727 - }), 728 - ); 729 - break; 730 - case 'email': 731 - case 'time': 732 - case 'uuid': 733 - pipes.push( 734 - tsc.callExpression({ 735 - functionName: tsc.propertyAccessExpression({ 736 - expression: vSymbol.placeholder, 737 - name: tsc.identifier({ text: schema.format }), 738 - }), 739 - }), 740 - ); 741 - break; 742 - } 743 - } 744 - 745 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 746 - const expression = tsc.callExpression({ 747 - functionName: tsc.propertyAccessExpression({ 748 - expression: vSymbol.placeholder, 749 - name: identifiers.actions.length, 750 - }), 751 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 752 - }); 753 - pipes.push(expression); 754 - } else { 755 - if (schema.minLength !== undefined) { 756 - const expression = tsc.callExpression({ 757 - functionName: tsc.propertyAccessExpression({ 758 - expression: vSymbol.placeholder, 759 - name: identifiers.actions.minLength, 760 - }), 761 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 762 - }); 763 - pipes.push(expression); 764 - } 765 - 766 - if (schema.maxLength !== undefined) { 767 - const expression = tsc.callExpression({ 768 - functionName: tsc.propertyAccessExpression({ 769 - expression: vSymbol.placeholder, 770 - name: identifiers.actions.maxLength, 771 - }), 772 - parameters: [tsc.valueToExpression({ value: schema.maxLength })], 773 - }); 774 - pipes.push(expression); 775 - } 776 - } 777 - 778 - if (schema.pattern) { 779 - const expression = tsc.callExpression({ 780 - functionName: tsc.propertyAccessExpression({ 781 - expression: vSymbol.placeholder, 782 - name: identifiers.actions.regex, 783 - }), 784 - parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 785 - }); 786 - pipes.push(expression); 787 - } 788 - 789 - return pipesToExpression({ pipes, plugin }); 790 - }; 791 - 792 - const tupleTypeToValibotSchema = ({ 793 - _path, 794 - plugin, 795 - schema, 796 - state, 797 - }: SchemaToValibotSchemaOptions & { 798 - schema: SchemaWithType<'tuple'>; 799 - state: State; 800 - }) => { 801 - const vSymbol = plugin.referenceSymbol( 802 - plugin.api.getSelector('import', 'valibot'), 803 - ); 804 - 805 - if (schema.const && Array.isArray(schema.const)) { 806 - const tupleElements = schema.const.map((value) => 807 - tsc.callExpression({ 808 - functionName: tsc.propertyAccessExpression({ 809 - expression: vSymbol.placeholder, 810 - name: identifiers.schemas.literal, 811 - }), 812 - parameters: [tsc.valueToExpression({ value })], 813 - }), 814 - ); 815 - const expression = tsc.callExpression({ 816 - functionName: tsc.propertyAccessExpression({ 817 - expression: vSymbol.placeholder, 818 - name: identifiers.schemas.tuple, 819 - }), 820 - parameters: [ 821 - tsc.arrayLiteralExpression({ 822 - elements: tupleElements, 823 - }), 824 - ], 825 - }); 826 - return expression; 827 - } 828 - 829 - if (schema.items) { 830 - const tupleElements = schema.items.map((item, index) => { 831 - const schemaPipes = schemaToValibotSchema({ 832 - _path: [..._path, 'items', index], 833 - plugin, 834 - schema: item, 835 - state, 836 - }); 837 - return pipesToExpression({ pipes: schemaPipes, plugin }); 838 - }); 839 - const expression = tsc.callExpression({ 840 - functionName: tsc.propertyAccessExpression({ 841 - expression: vSymbol.placeholder, 842 - name: identifiers.schemas.tuple, 843 - }), 844 - parameters: [ 845 - tsc.arrayLiteralExpression({ 846 - elements: tupleElements, 847 - }), 848 - ], 849 - }); 850 - return expression; 851 - } 852 - 853 - return unknownTypeToValibotSchema({ 854 - _path, 855 - plugin, 856 - schema: { 857 - type: 'unknown', 858 - }, 859 - }); 860 - }; 861 - 862 - const undefinedTypeToValibotSchema = ({ 863 - plugin, 864 - }: SchemaToValibotSchemaOptions & { 865 - schema: SchemaWithType<'undefined'>; 866 - }) => { 867 - const vSymbol = plugin.referenceSymbol( 868 - plugin.api.getSelector('import', 'valibot'), 869 - ); 870 - 871 - const expression = tsc.callExpression({ 872 - functionName: tsc.propertyAccessExpression({ 873 - expression: vSymbol.placeholder, 874 - name: identifiers.schemas.undefined, 875 - }), 876 - }); 877 - return expression; 878 - }; 879 - 880 - const unknownTypeToValibotSchema = ({ 881 - plugin, 882 - }: SchemaToValibotSchemaOptions & { 883 - schema: SchemaWithType<'unknown'>; 884 - }) => { 885 - const vSymbol = plugin.referenceSymbol( 886 - plugin.api.getSelector('import', 'valibot'), 887 - ); 888 - 889 - const expression = tsc.callExpression({ 890 - functionName: tsc.propertyAccessExpression({ 891 - expression: vSymbol.placeholder, 892 - name: identifiers.schemas.unknown, 893 - }), 894 - }); 895 - return expression; 896 - }; 897 - 898 - const voidTypeToValibotSchema = ({ 899 - plugin, 900 - }: SchemaToValibotSchemaOptions & { 901 - schema: SchemaWithType<'void'>; 902 - }) => { 903 - const vSymbol = plugin.referenceSymbol( 904 - plugin.api.getSelector('import', 'valibot'), 905 - ); 906 - 907 - const expression = tsc.callExpression({ 908 - functionName: tsc.propertyAccessExpression({ 909 - expression: vSymbol.placeholder, 910 - name: identifiers.schemas.void, 911 - }), 912 - }); 913 - return expression; 914 - }; 915 - 916 - const schemaTypeToValibotSchema = ({ 917 - _path, 918 - plugin, 919 - schema, 920 - state, 921 - }: SchemaToValibotSchemaOptions & { 922 - schema: IR.SchemaObject; 923 - state: State; 924 - }): { 925 - anyType?: string; 926 - expression: ts.Expression; 927 - } => { 928 - switch (schema.type as Required<IR.SchemaObject>['type']) { 929 - case 'array': 930 - return { 931 - expression: arrayTypeToValibotSchema({ 932 - _path, 933 - plugin, 934 - schema: schema as SchemaWithType<'array'>, 935 - state, 936 - }), 937 - }; 938 - case 'boolean': 939 - return { 940 - expression: booleanTypeToValibotSchema({ 941 - _path, 942 - plugin, 943 - schema: schema as SchemaWithType<'boolean'>, 944 - }), 945 - }; 946 - case 'enum': 947 - return { 948 - expression: enumTypeToValibotSchema({ 949 - _path, 950 - plugin, 951 - schema: schema as SchemaWithType<'enum'>, 952 - }), 953 - }; 954 - case 'integer': 955 - case 'number': 956 - return { 957 - expression: numberTypeToValibotSchema({ 958 - _path, 959 - plugin, 960 - schema: schema as SchemaWithType<'integer' | 'number'>, 961 - }), 962 - }; 963 - case 'never': 964 - return { 965 - expression: neverTypeToValibotSchema({ 966 - _path, 967 - plugin, 968 - schema: schema as SchemaWithType<'never'>, 969 - }), 970 - }; 971 - case 'null': 972 - return { 973 - expression: nullTypeToValibotSchema({ 974 - _path, 975 - plugin, 976 - schema: schema as SchemaWithType<'null'>, 977 - }), 978 - }; 979 - case 'object': 980 - return objectTypeToValibotSchema({ 981 - _path, 982 - plugin, 983 - schema: schema as SchemaWithType<'object'>, 984 - state, 985 - }); 986 - case 'string': 987 - // For string schemas with int64/uint64 formats, use number handler to generate union with transform 988 - if (schema.format === 'int64' || schema.format === 'uint64') { 989 - return { 990 - expression: numberTypeToValibotSchema({ 991 - _path, 992 - plugin, 993 - schema: schema as SchemaWithType<'integer' | 'number'>, 994 - }), 995 - }; 996 - } 997 - return { 998 - expression: stringTypeToValibotSchema({ 999 - _path, 1000 - plugin, 1001 - schema: schema as SchemaWithType<'string'>, 1002 - }), 1003 - }; 1004 - case 'tuple': 1005 - return { 1006 - expression: tupleTypeToValibotSchema({ 1007 - _path, 1008 - plugin, 1009 - schema: schema as SchemaWithType<'tuple'>, 1010 - state, 1011 - }), 1012 - }; 1013 - case 'undefined': 1014 - return { 1015 - expression: undefinedTypeToValibotSchema({ 1016 - _path, 1017 - plugin, 1018 - schema: schema as SchemaWithType<'undefined'>, 1019 - }), 1020 - }; 1021 - case 'unknown': 1022 - return { 1023 - expression: unknownTypeToValibotSchema({ 1024 - _path, 1025 - plugin, 1026 - schema: schema as SchemaWithType<'unknown'>, 1027 - }), 1028 - }; 1029 - case 'void': 1030 - return { 1031 - expression: voidTypeToValibotSchema({ 1032 - _path, 1033 - plugin, 1034 - schema: schema as SchemaWithType<'void'>, 1035 - }), 1036 - }; 1037 - } 1038 - }; 1039 - 1040 - export const schemaToValibotSchema = ({ 1041 - $ref, 1042 - _path = [], 1043 - optional, 1044 - plugin, 1045 - schema, 1046 - state, 1047 - symbol, 1048 - }: SchemaToValibotSchemaOptions & { 1049 - /** 1050 - * When $ref is supplied, a node will be emitted to the file. 1051 - */ 1052 - $ref?: string; 1053 - /** 1054 - * Accept `optional` to handle optional object properties. We can't handle 1055 - * this inside the object function because `.optional()` must come before 1056 - * `.default()` which is handled in this function. 1057 - */ 1058 - optional?: boolean; 1059 - schema: IR.SchemaObject; 1060 - state: State; 1061 - symbol?: Symbol; 1062 - }): Array<ts.Expression> => { 1063 - let anyType: string | undefined; 1064 - let pipes: Array<ts.Expression> = []; 1065 - 1066 - if ($ref) { 1067 - state.circularReferenceTracker.add($ref); 1068 - 1069 - if (!symbol) { 1070 - const selector = plugin.api.getSelector('ref', $ref); 1071 - if (!plugin.getSymbol(selector)) { 1072 - symbol = plugin.referenceSymbol(selector); 1073 - } 1074 - } 1075 - } 1076 - 1077 - const vSymbol = plugin.referenceSymbol( 1078 - plugin.api.getSelector('import', 'valibot'), 1079 - ); 1080 - 1081 - if (schema.$ref) { 1082 - const isCircularReference = state.circularReferenceTracker.has(schema.$ref); 1083 - 1084 - // if $ref hasn't been processed yet, inline it to avoid the 1085 - // "Block-scoped variable used before its declaration." error 1086 - // this could be (maybe?) fixed by reshuffling the generation order 1087 - const selector = plugin.api.getSelector('ref', schema.$ref); 1088 - let refSymbol = plugin.getSymbol(selector); 1089 - if (!refSymbol) { 1090 - const ref = plugin.context.resolveIrRef<IR.SchemaObject>(schema.$ref); 1091 - const schemaPipes = schemaToValibotSchema({ 1092 - $ref: schema.$ref, 1093 - _path: jsonPointerToPath(schema.$ref), 1094 - plugin, 1095 - schema: ref, 1096 - state, 1097 - }); 1098 - pipes.push(...schemaPipes); 1099 - 1100 - refSymbol = plugin.getSymbol(selector); 1101 - } 1102 - 1103 - if (refSymbol) { 1104 - const refIdentifier = tsc.identifier({ text: refSymbol.placeholder }); 1105 - if (isCircularReference) { 1106 - const lazyExpression = tsc.callExpression({ 1107 - functionName: tsc.propertyAccessExpression({ 1108 - expression: vSymbol.placeholder, 1109 - name: identifiers.schemas.lazy, 1110 - }), 1111 - parameters: [ 1112 - tsc.arrowFunction({ 1113 - statements: [ 1114 - tsc.returnStatement({ 1115 - expression: refIdentifier, 1116 - }), 1117 - ], 1118 - }), 1119 - ], 1120 - }); 1121 - pipes.push(lazyExpression); 1122 - state.hasCircularReference = true; 1123 - } else { 1124 - pipes.push(refIdentifier); 1125 - } 1126 - } 1127 - } else if (schema.type) { 1128 - const valibotSchema = schemaTypeToValibotSchema({ 1129 - _path, 1130 - plugin, 1131 - schema, 1132 - state, 1133 - }); 1134 - anyType = valibotSchema.anyType; 1135 - pipes.push(valibotSchema.expression); 1136 - 1137 - if (plugin.config.metadata && schema.description) { 1138 - const expression = tsc.callExpression({ 1139 - functionName: tsc.propertyAccessExpression({ 1140 - expression: vSymbol.placeholder, 1141 - name: identifiers.actions.metadata, 1142 - }), 1143 - parameters: [ 1144 - tsc.objectExpression({ 1145 - obj: [ 1146 - { 1147 - key: 'description', 1148 - value: tsc.stringLiteral({ text: schema.description }), 1149 - }, 1150 - ], 1151 - }), 1152 - ], 1153 - }); 1154 - pipes.push(expression); 1155 - } 1156 - } else if (schema.items) { 1157 - schema = deduplicateSchema({ schema }); 1158 - 1159 - if (schema.items) { 1160 - const itemTypes = schema.items.map((item, index) => { 1161 - const schemaPipes = schemaToValibotSchema({ 1162 - _path: [..._path, 'items', index], 1163 - plugin, 1164 - schema: item, 1165 - state, 1166 - }); 1167 - return pipesToExpression({ pipes: schemaPipes, plugin }); 1168 - }); 1169 - 1170 - if (schema.logicalOperator === 'and') { 1171 - const intersectExpression = tsc.callExpression({ 1172 - functionName: tsc.propertyAccessExpression({ 1173 - expression: vSymbol.placeholder, 1174 - name: identifiers.schemas.intersect, 1175 - }), 1176 - parameters: [ 1177 - tsc.arrayLiteralExpression({ 1178 - elements: itemTypes, 1179 - }), 1180 - ], 1181 - }); 1182 - pipes.push(intersectExpression); 1183 - } else { 1184 - const unionExpression = tsc.callExpression({ 1185 - functionName: tsc.propertyAccessExpression({ 1186 - expression: vSymbol.placeholder, 1187 - name: identifiers.schemas.union, 1188 - }), 1189 - parameters: [ 1190 - tsc.arrayLiteralExpression({ 1191 - elements: itemTypes, 1192 - }), 1193 - ], 1194 - }); 1195 - pipes.push(unionExpression); 1196 - } 1197 - } else { 1198 - const schemaPipes = schemaToValibotSchema({ 1199 - _path, 1200 - plugin, 1201 - schema, 1202 - state, 1203 - }); 1204 - pipes.push(...schemaPipes); 1205 - } 1206 - } else { 1207 - // catch-all fallback for failed schemas 1208 - const valibotSchema = schemaTypeToValibotSchema({ 1209 - _path, 1210 - plugin, 1211 - schema: { 1212 - type: 'unknown', 1213 - }, 1214 - state, 1215 - }); 1216 - anyType = valibotSchema.anyType; 1217 - pipes.push(valibotSchema.expression); 1218 - } 1219 - 1220 - if ($ref) { 1221 - state.circularReferenceTracker.delete($ref); 1222 - } 1223 - 1224 - if (pipes.length) { 1225 - if (schema.accessScope === 'read') { 1226 - const readonlyExpression = tsc.callExpression({ 1227 - functionName: tsc.propertyAccessExpression({ 1228 - expression: vSymbol.placeholder, 1229 - name: identifiers.actions.readonly, 1230 - }), 1231 - }); 1232 - pipes.push(readonlyExpression); 1233 - } 1234 - } 1235 - 1236 - if (pipes.length) { 1237 - let callParameter: ts.Expression | undefined; 1238 - 1239 - if (schema.default !== undefined) { 1240 - const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 1241 - callParameter = numberParameter({ isBigInt, value: schema.default }); 1242 - if (callParameter) { 1243 - pipes = [ 1244 - tsc.callExpression({ 1245 - functionName: tsc.propertyAccessExpression({ 1246 - expression: vSymbol.placeholder, 1247 - name: identifiers.schemas.optional, 1248 - }), 1249 - parameters: [pipesToExpression({ pipes, plugin }), callParameter], 1250 - }), 1251 - ]; 1252 - } 1253 - } 1254 - 1255 - if (optional && !callParameter) { 1256 - pipes = [ 1257 - tsc.callExpression({ 1258 - functionName: tsc.propertyAccessExpression({ 1259 - expression: vSymbol.placeholder, 1260 - name: identifiers.schemas.optional, 1261 - }), 1262 - parameters: [pipesToExpression({ pipes, plugin })], 1263 - }), 1264 - ]; 1265 - } 1266 - } 1267 - 1268 - if (symbol) { 1269 - if ($ref) { 1270 - symbol = plugin.registerSymbol({ 1271 - exported: true, 1272 - meta: { 1273 - resourceType: pathToSymbolResourceType(_path), 1274 - }, 1275 - name: buildName({ 1276 - config: { 1277 - case: state.nameCase, 1278 - name: state.nameTransformer, 1279 - }, 1280 - name: refToName($ref), 1281 - }), 1282 - selector: plugin.api.getSelector('ref', $ref), 1283 - }); 1284 - } 1285 - const statement = tsc.constVariable({ 1286 - comment: plugin.config.comments 1287 - ? createSchemaComment({ schema }) 1288 - : undefined, 1289 - exportConst: symbol.exported, 1290 - expression: pipesToExpression({ pipes, plugin }), 1291 - name: symbol.placeholder, 1292 - typeName: state.hasCircularReference 1293 - ? (tsc.propertyAccessExpression({ 1294 - expression: vSymbol.placeholder, 1295 - name: anyType || identifiers.types.GenericSchema.text, 1296 - }) as unknown as ts.TypeNode) 1297 - : undefined, 1298 - }); 1299 - plugin.setSymbolValue(symbol, statement); 1300 - return []; 1301 - } 1302 - 1303 - return pipes; 1304 - }; 1305 - 1306 - export const handler: ValibotPlugin['Handler'] = ({ plugin }) => { 1307 - plugin.registerSymbol({ 1308 - external: 'valibot', 1309 - meta: { importKind: 'namespace' }, 1310 - name: 'v', 1311 - selector: plugin.api.getSelector('import', 'valibot'), 1312 - }); 1313 - 1314 - plugin.forEach( 1315 - 'operation', 1316 - 'parameter', 1317 - 'requestBody', 1318 - 'schema', 1319 - 'webhook', 1320 - (event) => { 1321 - const state: State = { 1322 - circularReferenceTracker: new Set(), 1323 - hasCircularReference: false, 1324 - nameCase: plugin.config.definitions.case, 1325 - nameTransformer: plugin.config.definitions.name, 1326 - }; 1327 - 1328 - switch (event.type) { 1329 - case 'operation': 1330 - operationToValibotSchema({ 1331 - _path: event._path, 1332 - operation: event.operation, 1333 - plugin, 1334 - state, 1335 - }); 1336 - break; 1337 - case 'parameter': 1338 - schemaToValibotSchema({ 1339 - $ref: event.$ref, 1340 - _path: event._path, 1341 - plugin, 1342 - schema: event.parameter.schema, 1343 - state, 1344 - }); 1345 - break; 1346 - case 'requestBody': 1347 - schemaToValibotSchema({ 1348 - $ref: event.$ref, 1349 - _path: event._path, 1350 - plugin, 1351 - schema: event.requestBody.schema, 1352 - state, 1353 - }); 1354 - break; 1355 - case 'schema': 1356 - schemaToValibotSchema({ 1357 - $ref: event.$ref, 1358 - _path: event._path, 1359 - plugin, 1360 - schema: event.schema, 1361 - state, 1362 - }); 1363 - break; 1364 - case 'webhook': 1365 - webhookToValibotSchema({ 1366 - _path: event._path, 1367 - operation: event.operation, 1368 - plugin, 1369 - state, 1370 - }); 1371 - break; 1372 - } 1373 - }, 1374 - ); 1375 - }; 4 + export const handler: ValibotPlugin['Handler'] = (args) => handlerV1(args);
+24
packages/openapi-ts/src/plugins/valibot/shared/types.d.ts
··· 1 + import type { IR } from '../../../ir/types'; 2 + import type { StringCase, StringName } from '../../../types/case'; 3 + import type { ValibotPlugin } from '../types'; 4 + 5 + export type IrSchemaToAstOptions = { 6 + plugin: ValibotPlugin['Instance']; 7 + state: State; 8 + }; 9 + 10 + export type State = { 11 + /** 12 + * Path to the schema in the intermediary representation. 13 + */ 14 + _path: ReadonlyArray<string | number>; 15 + circularReferenceTracker: Set<string>; 16 + hasCircularReference: boolean; 17 + nameCase: StringCase; 18 + nameTransformer: StringName; 19 + }; 20 + 21 + export type ValidatorArgs = { 22 + operation: IR.OperationObject; 23 + plugin: ValibotPlugin['Instance']; 24 + };
+85
packages/openapi-ts/src/plugins/valibot/v1/api.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + import type { ValidatorArgs } from '../shared/types'; 5 + import { identifiers } from './constants'; 6 + 7 + export const createRequestValidatorV1 = ({ 8 + operation, 9 + plugin, 10 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 11 + const symbol = plugin.getSymbol(plugin.api.getSelector('data', operation.id)); 12 + if (!symbol) return; 13 + 14 + const v = plugin.referenceSymbol( 15 + plugin.api.getSelector('external', 'valibot.v'), 16 + ); 17 + 18 + const dataParameterName = 'data'; 19 + 20 + return tsc.arrowFunction({ 21 + async: true, 22 + parameters: [ 23 + { 24 + name: dataParameterName, 25 + }, 26 + ], 27 + statements: [ 28 + tsc.returnStatement({ 29 + expression: tsc.awaitExpression({ 30 + expression: tsc.callExpression({ 31 + functionName: tsc.propertyAccessExpression({ 32 + expression: v.placeholder, 33 + name: identifiers.async.parseAsync, 34 + }), 35 + parameters: [ 36 + tsc.identifier({ text: symbol.placeholder }), 37 + tsc.identifier({ text: dataParameterName }), 38 + ], 39 + }), 40 + }), 41 + }), 42 + ], 43 + }); 44 + }; 45 + 46 + export const createResponseValidatorV1 = ({ 47 + operation, 48 + plugin, 49 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 50 + const symbol = plugin.getSymbol( 51 + plugin.api.getSelector('responses', operation.id), 52 + ); 53 + if (!symbol) return; 54 + 55 + const v = plugin.referenceSymbol( 56 + plugin.api.getSelector('external', 'valibot.v'), 57 + ); 58 + 59 + const dataParameterName = 'data'; 60 + 61 + return tsc.arrowFunction({ 62 + async: true, 63 + parameters: [ 64 + { 65 + name: dataParameterName, 66 + }, 67 + ], 68 + statements: [ 69 + tsc.returnStatement({ 70 + expression: tsc.awaitExpression({ 71 + expression: tsc.callExpression({ 72 + functionName: tsc.propertyAccessExpression({ 73 + expression: v.placeholder, 74 + name: identifiers.async.parseAsync, 75 + }), 76 + parameters: [ 77 + tsc.identifier({ text: symbol.placeholder }), 78 + tsc.identifier({ text: dataParameterName }), 79 + ], 80 + }), 81 + }), 82 + }), 83 + ], 84 + }); 85 + };
+29
packages/openapi-ts/src/plugins/valibot/v1/pipesToAst.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + import type { ValibotPlugin } from '../types'; 5 + import { identifiers } from './constants'; 6 + 7 + export const pipesToAst = ({ 8 + pipes, 9 + plugin, 10 + }: { 11 + pipes: Array<ts.Expression>; 12 + plugin: ValibotPlugin['Instance']; 13 + }): ts.Expression => { 14 + if (pipes.length === 1) { 15 + return pipes[0]!; 16 + } 17 + 18 + const v = plugin.referenceSymbol( 19 + plugin.api.getSelector('external', 'valibot.v'), 20 + ); 21 + const expression = tsc.callExpression({ 22 + functionName: tsc.propertyAccessExpression({ 23 + expression: v.placeholder, 24 + name: identifiers.methods.pipe, 25 + }), 26 + parameters: pipes, 27 + }); 28 + return expression; 29 + };
+363
packages/openapi-ts/src/plugins/valibot/v1/plugin.ts
··· 1 + import type { Symbol } from '@hey-api/codegen-core'; 2 + import type ts from 'typescript'; 3 + 4 + import { deduplicateSchema } from '../../../ir/schema'; 5 + import type { IR } from '../../../ir/types'; 6 + import { buildName } from '../../../openApi/shared/utils/name'; 7 + import { tsc } from '../../../tsc'; 8 + import { jsonPointerToPath, refToName } from '../../../utils/ref'; 9 + import type { SchemaWithType } from '../../shared/types/schema'; 10 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 11 + import { createSchemaComment } from '../../shared/utils/schema'; 12 + import { numberParameter } from '../shared/numbers'; 13 + import type { IrSchemaToAstOptions } from '../shared/types'; 14 + import type { ValibotPlugin } from '../types'; 15 + import { identifiers } from './constants'; 16 + import { irOperationToAst } from './operation'; 17 + import { pipesToAst } from './pipesToAst'; 18 + import { irSchemaWithTypeToAst } from './toAst'; 19 + import { irWebhookToAst } from './webhook'; 20 + 21 + export const irSchemaToAst = ({ 22 + $ref, 23 + optional, 24 + plugin, 25 + schema, 26 + state, 27 + symbol, 28 + }: IrSchemaToAstOptions & { 29 + /** 30 + * When $ref is supplied, a node will be emitted to the file. 31 + */ 32 + $ref?: string; 33 + /** 34 + * Accept `optional` to handle optional object properties. We can't handle 35 + * this inside the object function because `.optional()` must come before 36 + * `.default()` which is handled in this function. 37 + */ 38 + optional?: boolean; 39 + schema: IR.SchemaObject; 40 + symbol?: Symbol; 41 + }): Array<ts.Expression> => { 42 + let anyType: string | undefined; 43 + let pipes: Array<ts.Expression> = []; 44 + 45 + if ($ref) { 46 + state.circularReferenceTracker.add($ref); 47 + 48 + if (!symbol) { 49 + const selector = plugin.api.getSelector('ref', $ref); 50 + if (!plugin.getSymbol(selector)) { 51 + symbol = plugin.referenceSymbol(selector); 52 + } 53 + } 54 + } 55 + 56 + const v = plugin.referenceSymbol( 57 + plugin.api.getSelector('external', 'valibot.v'), 58 + ); 59 + 60 + if (schema.$ref) { 61 + const isCircularReference = state.circularReferenceTracker.has(schema.$ref); 62 + 63 + // if $ref hasn't been processed yet, inline it to avoid the 64 + // "Block-scoped variable used before its declaration." error 65 + // this could be (maybe?) fixed by reshuffling the generation order 66 + const selector = plugin.api.getSelector('ref', schema.$ref); 67 + let refSymbol = plugin.getSymbol(selector); 68 + if (!refSymbol) { 69 + const ref = plugin.context.resolveIrRef<IR.SchemaObject>(schema.$ref); 70 + const schemaPipes = irSchemaToAst({ 71 + $ref: schema.$ref, 72 + plugin, 73 + schema: ref, 74 + state: { 75 + ...state, 76 + _path: jsonPointerToPath(schema.$ref), 77 + }, 78 + }); 79 + pipes.push(...schemaPipes); 80 + 81 + refSymbol = plugin.getSymbol(selector); 82 + } 83 + 84 + if (refSymbol) { 85 + const refIdentifier = tsc.identifier({ text: refSymbol.placeholder }); 86 + if (isCircularReference) { 87 + const lazyExpression = tsc.callExpression({ 88 + functionName: tsc.propertyAccessExpression({ 89 + expression: v.placeholder, 90 + name: identifiers.schemas.lazy, 91 + }), 92 + parameters: [ 93 + tsc.arrowFunction({ 94 + statements: [ 95 + tsc.returnStatement({ 96 + expression: refIdentifier, 97 + }), 98 + ], 99 + }), 100 + ], 101 + }); 102 + pipes.push(lazyExpression); 103 + state.hasCircularReference = true; 104 + } else { 105 + pipes.push(refIdentifier); 106 + } 107 + } 108 + } else if (schema.type) { 109 + const valibotSchema = irSchemaWithTypeToAst({ 110 + plugin, 111 + schema: schema as SchemaWithType, 112 + state, 113 + }); 114 + anyType = valibotSchema.anyType; 115 + pipes.push(valibotSchema.expression); 116 + 117 + if (plugin.config.metadata && schema.description) { 118 + const expression = tsc.callExpression({ 119 + functionName: tsc.propertyAccessExpression({ 120 + expression: v.placeholder, 121 + name: identifiers.actions.metadata, 122 + }), 123 + parameters: [ 124 + tsc.objectExpression({ 125 + obj: [ 126 + { 127 + key: 'description', 128 + value: tsc.stringLiteral({ text: schema.description }), 129 + }, 130 + ], 131 + }), 132 + ], 133 + }); 134 + pipes.push(expression); 135 + } 136 + } else if (schema.items) { 137 + schema = deduplicateSchema({ schema }); 138 + 139 + if (schema.items) { 140 + const itemTypes = schema.items.map((item, index) => { 141 + const schemaPipes = irSchemaToAst({ 142 + plugin, 143 + schema: item, 144 + state: { 145 + ...state, 146 + _path: [...state._path, 'items', index], 147 + }, 148 + }); 149 + return pipesToAst({ pipes: schemaPipes, plugin }); 150 + }); 151 + 152 + if (schema.logicalOperator === 'and') { 153 + const intersectExpression = tsc.callExpression({ 154 + functionName: tsc.propertyAccessExpression({ 155 + expression: v.placeholder, 156 + name: identifiers.schemas.intersect, 157 + }), 158 + parameters: [ 159 + tsc.arrayLiteralExpression({ 160 + elements: itemTypes, 161 + }), 162 + ], 163 + }); 164 + pipes.push(intersectExpression); 165 + } else { 166 + const unionExpression = tsc.callExpression({ 167 + functionName: tsc.propertyAccessExpression({ 168 + expression: v.placeholder, 169 + name: identifiers.schemas.union, 170 + }), 171 + parameters: [ 172 + tsc.arrayLiteralExpression({ 173 + elements: itemTypes, 174 + }), 175 + ], 176 + }); 177 + pipes.push(unionExpression); 178 + } 179 + } else { 180 + const schemaPipes = irSchemaToAst({ 181 + plugin, 182 + schema, 183 + state, 184 + }); 185 + pipes.push(...schemaPipes); 186 + } 187 + } else { 188 + // catch-all fallback for failed schemas 189 + const valibotSchema = irSchemaWithTypeToAst({ 190 + plugin, 191 + schema: { 192 + type: 'unknown', 193 + }, 194 + state, 195 + }); 196 + anyType = valibotSchema.anyType; 197 + pipes.push(valibotSchema.expression); 198 + } 199 + 200 + if ($ref) { 201 + state.circularReferenceTracker.delete($ref); 202 + } 203 + 204 + if (pipes.length) { 205 + if (schema.accessScope === 'read') { 206 + const readonlyExpression = tsc.callExpression({ 207 + functionName: tsc.propertyAccessExpression({ 208 + expression: v.placeholder, 209 + name: identifiers.actions.readonly, 210 + }), 211 + }); 212 + pipes.push(readonlyExpression); 213 + } 214 + 215 + let callParameter: ts.Expression | undefined; 216 + 217 + if (schema.default !== undefined) { 218 + const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 219 + callParameter = numberParameter({ isBigInt, value: schema.default }); 220 + if (callParameter) { 221 + pipes = [ 222 + tsc.callExpression({ 223 + functionName: tsc.propertyAccessExpression({ 224 + expression: v.placeholder, 225 + name: identifiers.schemas.optional, 226 + }), 227 + parameters: [pipesToAst({ pipes, plugin }), callParameter], 228 + }), 229 + ]; 230 + } 231 + } 232 + 233 + if (optional && !callParameter) { 234 + pipes = [ 235 + tsc.callExpression({ 236 + functionName: tsc.propertyAccessExpression({ 237 + expression: v.placeholder, 238 + name: identifiers.schemas.optional, 239 + }), 240 + parameters: [pipesToAst({ pipes, plugin })], 241 + }), 242 + ]; 243 + } 244 + } 245 + 246 + if (symbol) { 247 + if ($ref) { 248 + symbol = plugin.registerSymbol({ 249 + exported: true, 250 + meta: { 251 + resourceType: pathToSymbolResourceType(state._path), 252 + }, 253 + name: buildName({ 254 + config: { 255 + case: state.nameCase, 256 + name: state.nameTransformer, 257 + }, 258 + name: refToName($ref), 259 + }), 260 + selector: plugin.api.getSelector('ref', $ref), 261 + }); 262 + } 263 + const statement = tsc.constVariable({ 264 + comment: plugin.config.comments 265 + ? createSchemaComment({ schema }) 266 + : undefined, 267 + exportConst: symbol.exported, 268 + expression: pipesToAst({ pipes, plugin }), 269 + name: symbol.placeholder, 270 + typeName: state.hasCircularReference 271 + ? (tsc.propertyAccessExpression({ 272 + expression: v.placeholder, 273 + name: anyType || identifiers.types.GenericSchema.text, 274 + }) as unknown as ts.TypeNode) 275 + : undefined, 276 + }); 277 + plugin.setSymbolValue(symbol, statement); 278 + return []; 279 + } 280 + 281 + return pipes; 282 + }; 283 + 284 + export const handlerV1: ValibotPlugin['Handler'] = ({ plugin }) => { 285 + plugin.registerSymbol({ 286 + external: 'valibot', 287 + meta: { importKind: 'namespace' }, 288 + name: 'v', 289 + selector: plugin.api.getSelector('external', 'valibot.v'), 290 + }); 291 + 292 + plugin.forEach( 293 + 'operation', 294 + 'parameter', 295 + 'requestBody', 296 + 'schema', 297 + 'webhook', 298 + (event) => { 299 + const state: Omit<IrSchemaToAstOptions['state'], '_path'> = { 300 + circularReferenceTracker: new Set(), 301 + hasCircularReference: false, 302 + nameCase: plugin.config.definitions.case, 303 + nameTransformer: plugin.config.definitions.name, 304 + }; 305 + 306 + switch (event.type) { 307 + case 'operation': 308 + irOperationToAst({ 309 + operation: event.operation, 310 + plugin, 311 + state: { 312 + ...state, 313 + _path: event._path, 314 + }, 315 + }); 316 + break; 317 + case 'parameter': 318 + irSchemaToAst({ 319 + $ref: event.$ref, 320 + plugin, 321 + schema: event.parameter.schema, 322 + state: { 323 + ...state, 324 + _path: event._path, 325 + }, 326 + }); 327 + break; 328 + case 'requestBody': 329 + irSchemaToAst({ 330 + $ref: event.$ref, 331 + plugin, 332 + schema: event.requestBody.schema, 333 + state: { 334 + ...state, 335 + _path: event._path, 336 + }, 337 + }); 338 + break; 339 + case 'schema': 340 + irSchemaToAst({ 341 + $ref: event.$ref, 342 + plugin, 343 + schema: event.schema, 344 + state: { 345 + ...state, 346 + _path: event._path, 347 + }, 348 + }); 349 + break; 350 + case 'webhook': 351 + irWebhookToAst({ 352 + operation: event.operation, 353 + plugin, 354 + state: { 355 + ...state, 356 + _path: event._path, 357 + }, 358 + }); 359 + break; 360 + } 361 + }, 362 + ); 363 + };
+126
packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { deduplicateSchema } from '../../../../ir/schema'; 4 + import { tsc } from '../../../../tsc'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import type { IrSchemaToAstOptions } from '../../shared/types'; 7 + import { identifiers } from '../constants'; 8 + import { pipesToAst } from '../pipesToAst'; 9 + import { irSchemaToAst } from '../plugin'; 10 + import { unknownToAst } from './unknown'; 11 + 12 + export const arrayToAst = ({ 13 + plugin, 14 + schema, 15 + state, 16 + }: IrSchemaToAstOptions & { 17 + schema: SchemaWithType<'array'>; 18 + }): ts.Expression => { 19 + const v = plugin.referenceSymbol( 20 + plugin.api.getSelector('external', 'valibot.v'), 21 + ); 22 + const functionName = tsc.propertyAccessExpression({ 23 + expression: v.placeholder, 24 + name: identifiers.schemas.array, 25 + }); 26 + 27 + const pipes: Array<ts.CallExpression> = []; 28 + 29 + if (!schema.items) { 30 + const expression = tsc.callExpression({ 31 + functionName, 32 + parameters: [ 33 + unknownToAst({ 34 + plugin, 35 + schema: { 36 + type: 'unknown', 37 + }, 38 + state, 39 + }), 40 + ], 41 + }); 42 + pipes.push(expression); 43 + } else { 44 + schema = deduplicateSchema({ schema }); 45 + 46 + // at least one item is guaranteed 47 + const itemExpressions = schema.items!.map((item, index) => { 48 + const schemaPipes = irSchemaToAst({ 49 + plugin, 50 + schema: item, 51 + state: { 52 + ...state, 53 + _path: [...state._path, 'items', index], 54 + }, 55 + }); 56 + return pipesToAst({ pipes: schemaPipes, plugin }); 57 + }); 58 + 59 + if (itemExpressions.length === 1) { 60 + const expression = tsc.callExpression({ 61 + functionName, 62 + parameters: itemExpressions, 63 + }); 64 + pipes.push(expression); 65 + } else { 66 + if (schema.logicalOperator === 'and') { 67 + // TODO: parser - handle intersection 68 + // return tsc.typeArrayNode( 69 + // tsc.typeIntersectionNode({ types: itemExpressions }), 70 + // ); 71 + } 72 + 73 + // TODO: parser - handle union 74 + // return tsc.typeArrayNode(tsc.typeUnionNode({ types: itemExpressions })); 75 + 76 + const expression = tsc.callExpression({ 77 + functionName, 78 + parameters: [ 79 + unknownToAst({ 80 + plugin, 81 + schema: { 82 + type: 'unknown', 83 + }, 84 + state, 85 + }), 86 + ], 87 + }); 88 + pipes.push(expression); 89 + } 90 + } 91 + 92 + if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 93 + const expression = tsc.callExpression({ 94 + functionName: tsc.propertyAccessExpression({ 95 + expression: v.placeholder, 96 + name: identifiers.actions.length, 97 + }), 98 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 99 + }); 100 + pipes.push(expression); 101 + } else { 102 + if (schema.minItems !== undefined) { 103 + const expression = tsc.callExpression({ 104 + functionName: tsc.propertyAccessExpression({ 105 + expression: v.placeholder, 106 + name: identifiers.actions.minLength, 107 + }), 108 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 109 + }); 110 + pipes.push(expression); 111 + } 112 + 113 + if (schema.maxItems !== undefined) { 114 + const expression = tsc.callExpression({ 115 + functionName: tsc.propertyAccessExpression({ 116 + expression: v.placeholder, 117 + name: identifiers.actions.maxLength, 118 + }), 119 + parameters: [tsc.valueToExpression({ value: schema.maxItems })], 120 + }); 121 + pipes.push(expression); 122 + } 123 + } 124 + 125 + return pipesToAst({ pipes, plugin }); 126 + };
+34
packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + 6 + export const booleanToAst = ({ 7 + plugin, 8 + schema, 9 + }: IrSchemaToAstOptions & { 10 + schema: SchemaWithType<'boolean'>; 11 + }) => { 12 + const v = plugin.referenceSymbol( 13 + plugin.api.getSelector('external', 'valibot.v'), 14 + ); 15 + 16 + if (typeof schema.const === 'boolean') { 17 + const expression = tsc.callExpression({ 18 + functionName: tsc.propertyAccessExpression({ 19 + expression: v.placeholder, 20 + name: identifiers.schemas.literal, 21 + }), 22 + parameters: [tsc.ots.boolean(schema.const)], 23 + }); 24 + return expression; 25 + } 26 + 27 + const expression = tsc.callExpression({ 28 + functionName: tsc.propertyAccessExpression({ 29 + expression: v.placeholder, 30 + name: identifiers.schemas.boolean, 31 + }), 32 + }); 33 + return expression; 34 + };
+71
packages/openapi-ts/src/plugins/valibot/v1/toAst/enum.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import type { IrSchemaToAstOptions } from '../../shared/types'; 6 + import { identifiers } from '../constants'; 7 + import { unknownToAst } from './unknown'; 8 + 9 + export const enumToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'enum'>; 15 + }): ts.CallExpression => { 16 + const enumMembers: Array<ts.LiteralExpression> = []; 17 + 18 + let isNullable = false; 19 + 20 + for (const item of schema.items ?? []) { 21 + // Zod supports only string enums 22 + if (item.type === 'string' && typeof item.const === 'string') { 23 + enumMembers.push( 24 + tsc.stringLiteral({ 25 + text: item.const, 26 + }), 27 + ); 28 + } else if (item.type === 'null' || item.const === null) { 29 + isNullable = true; 30 + } 31 + } 32 + 33 + if (!enumMembers.length) { 34 + return unknownToAst({ 35 + plugin, 36 + schema: { 37 + type: 'unknown', 38 + }, 39 + state, 40 + }); 41 + } 42 + 43 + const v = plugin.referenceSymbol( 44 + plugin.api.getSelector('external', 'valibot.v'), 45 + ); 46 + 47 + let resultExpression = tsc.callExpression({ 48 + functionName: tsc.propertyAccessExpression({ 49 + expression: v.placeholder, 50 + name: identifiers.schemas.picklist, 51 + }), 52 + parameters: [ 53 + tsc.arrayLiteralExpression({ 54 + elements: enumMembers, 55 + multiLine: false, 56 + }), 57 + ], 58 + }); 59 + 60 + if (isNullable) { 61 + resultExpression = tsc.callExpression({ 62 + functionName: tsc.propertyAccessExpression({ 63 + expression: v.placeholder, 64 + name: identifiers.schemas.nullable, 65 + }), 66 + parameters: [resultExpression], 67 + }); 68 + } 69 + 70 + return resultExpression; 71 + };
+121
packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import type { SchemaWithType } from '../../../shared/types/schema'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + import { arrayToAst } from './array'; 6 + import { booleanToAst } from './boolean'; 7 + import { enumToAst } from './enum'; 8 + import { neverToAst } from './never'; 9 + import { nullToAst } from './null'; 10 + import { numberToAst } from './number'; 11 + import { objectToAst } from './object'; 12 + import { stringToAst } from './string'; 13 + import { tupleToAst } from './tuple'; 14 + import { undefinedToAst } from './undefined'; 15 + import { unknownToAst } from './unknown'; 16 + import { voidToAst } from './void'; 17 + 18 + export const irSchemaWithTypeToAst = ({ 19 + schema, 20 + ...args 21 + }: IrSchemaToAstOptions & { 22 + schema: SchemaWithType; 23 + }): { 24 + anyType?: string; 25 + expression: ts.Expression; 26 + } => { 27 + switch (schema.type) { 28 + case 'array': 29 + return { 30 + expression: arrayToAst({ 31 + ...args, 32 + schema: schema as SchemaWithType<'array'>, 33 + }), 34 + }; 35 + case 'boolean': 36 + return { 37 + expression: booleanToAst({ 38 + ...args, 39 + schema: schema as SchemaWithType<'boolean'>, 40 + }), 41 + }; 42 + case 'enum': 43 + return { 44 + expression: enumToAst({ 45 + ...args, 46 + schema: schema as SchemaWithType<'enum'>, 47 + }), 48 + }; 49 + case 'integer': 50 + case 'number': 51 + return { 52 + expression: numberToAst({ 53 + ...args, 54 + schema: schema as SchemaWithType<'integer' | 'number'>, 55 + }), 56 + }; 57 + case 'never': 58 + return { 59 + expression: neverToAst({ 60 + ...args, 61 + schema: schema as SchemaWithType<'never'>, 62 + }), 63 + }; 64 + case 'null': 65 + return { 66 + expression: nullToAst({ 67 + ...args, 68 + schema: schema as SchemaWithType<'null'>, 69 + }), 70 + }; 71 + case 'object': 72 + return objectToAst({ 73 + ...args, 74 + schema: schema as SchemaWithType<'object'>, 75 + }); 76 + case 'string': 77 + // For string schemas with int64/uint64 formats, use number handler to generate union with transform 78 + if (schema.format === 'int64' || schema.format === 'uint64') { 79 + return { 80 + expression: numberToAst({ 81 + ...args, 82 + schema: schema as SchemaWithType<'integer' | 'number'>, 83 + }), 84 + }; 85 + } 86 + return { 87 + expression: stringToAst({ 88 + ...args, 89 + schema: schema as SchemaWithType<'string'>, 90 + }), 91 + }; 92 + case 'tuple': 93 + return { 94 + expression: tupleToAst({ 95 + ...args, 96 + schema: schema as SchemaWithType<'tuple'>, 97 + }), 98 + }; 99 + case 'undefined': 100 + return { 101 + expression: undefinedToAst({ 102 + ...args, 103 + schema: schema as SchemaWithType<'undefined'>, 104 + }), 105 + }; 106 + case 'unknown': 107 + return { 108 + expression: unknownToAst({ 109 + ...args, 110 + schema: schema as SchemaWithType<'unknown'>, 111 + }), 112 + }; 113 + case 'void': 114 + return { 115 + expression: voidToAst({ 116 + ...args, 117 + schema: schema as SchemaWithType<'void'>, 118 + }), 119 + }; 120 + } 121 + };
+21
packages/openapi-ts/src/plugins/valibot/v1/toAst/never.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + 6 + export const neverToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'never'>; 10 + }) => { 11 + const v = plugin.referenceSymbol( 12 + plugin.api.getSelector('external', 'valibot.v'), 13 + ); 14 + const expression = tsc.callExpression({ 15 + functionName: tsc.propertyAccessExpression({ 16 + expression: v.placeholder, 17 + name: identifiers.schemas.never, 18 + }), 19 + }); 20 + return expression; 21 + };
+21
packages/openapi-ts/src/plugins/valibot/v1/toAst/null.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + 6 + export const nullToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'null'>; 10 + }) => { 11 + const v = plugin.referenceSymbol( 12 + plugin.api.getSelector('external', 'valibot.v'), 13 + ); 14 + const expression = tsc.callExpression({ 15 + functionName: tsc.propertyAccessExpression({ 16 + expression: v.placeholder, 17 + name: identifiers.schemas.null, 18 + }), 19 + }); 20 + return expression; 21 + };
+254
packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { 6 + INTEGER_FORMATS, 7 + isIntegerFormat, 8 + needsBigIntForFormat, 9 + numberParameter, 10 + } from '../../shared/numbers'; 11 + import type { IrSchemaToAstOptions } from '../../shared/types'; 12 + import { identifiers } from '../constants'; 13 + import { pipesToAst } from '../pipesToAst'; 14 + 15 + export const numberToAst = ({ 16 + plugin, 17 + schema, 18 + }: IrSchemaToAstOptions & { 19 + schema: SchemaWithType<'integer' | 'number'>; 20 + }) => { 21 + const format = schema.format; 22 + const isInteger = schema.type === 'integer'; 23 + const isBigInt = needsBigIntForFormat(format); 24 + const formatInfo = isIntegerFormat(format) ? INTEGER_FORMATS[format] : null; 25 + 26 + const v = plugin.referenceSymbol( 27 + plugin.api.getSelector('external', 'valibot.v'), 28 + ); 29 + 30 + // Return early if const is defined since we can create a literal type directly without additional validation 31 + if (schema.const !== undefined && schema.const !== null) { 32 + const constValue = schema.const; 33 + let literalValue; 34 + 35 + // Case 1: Number with no format -> generate literal with the number 36 + if (typeof constValue === 'number' && !format) { 37 + literalValue = tsc.ots.number(constValue); 38 + } 39 + // Case 2: Number with format -> check if format needs BigInt, generate appropriate literal 40 + else if (typeof constValue === 'number' && format) { 41 + if (isBigInt) { 42 + // Format requires BigInt, convert number to BigInt 43 + literalValue = tsc.callExpression({ 44 + functionName: 'BigInt', 45 + parameters: [tsc.ots.string(constValue.toString())], 46 + }); 47 + } else { 48 + // Regular format, use number as-is 49 + literalValue = tsc.ots.number(constValue); 50 + } 51 + } 52 + // Case 3: Format that allows string -> generate BigInt literal (for int64/uint64 formats) 53 + else if (typeof constValue === 'string' && isBigInt) { 54 + // Remove 'n' suffix if present in string 55 + const cleanString = constValue.endsWith('n') 56 + ? constValue.slice(0, -1) 57 + : constValue; 58 + literalValue = tsc.callExpression({ 59 + functionName: 'BigInt', 60 + parameters: [tsc.ots.string(cleanString)], 61 + }); 62 + } 63 + // Case 4: Const is typeof bigint (literal) -> transform from literal to BigInt() 64 + else if (typeof constValue === 'bigint') { 65 + // Convert BigInt to string and remove 'n' suffix that toString() adds 66 + const bigintString = constValue.toString(); 67 + const cleanString = bigintString.endsWith('n') 68 + ? bigintString.slice(0, -1) 69 + : bigintString; 70 + literalValue = tsc.callExpression({ 71 + functionName: 'BigInt', 72 + parameters: [tsc.ots.string(cleanString)], 73 + }); 74 + } 75 + // Default case: use value as-is for other types 76 + else { 77 + literalValue = tsc.valueToExpression({ value: constValue }); 78 + } 79 + 80 + return tsc.callExpression({ 81 + functionName: tsc.propertyAccessExpression({ 82 + expression: v.placeholder, 83 + name: identifiers.schemas.literal, 84 + }), 85 + parameters: [literalValue], 86 + }); 87 + } 88 + 89 + const pipes: Array<ts.CallExpression> = []; 90 + 91 + // For bigint formats (int64, uint64), create union of number, string, and bigint with transform 92 + if (isBigInt) { 93 + const unionExpression = tsc.callExpression({ 94 + functionName: tsc.propertyAccessExpression({ 95 + expression: v.placeholder, 96 + name: identifiers.schemas.union, 97 + }), 98 + parameters: [ 99 + tsc.arrayLiteralExpression({ 100 + elements: [ 101 + tsc.callExpression({ 102 + functionName: tsc.propertyAccessExpression({ 103 + expression: v.placeholder, 104 + name: identifiers.schemas.number, 105 + }), 106 + }), 107 + tsc.callExpression({ 108 + functionName: tsc.propertyAccessExpression({ 109 + expression: v.placeholder, 110 + name: identifiers.schemas.string, 111 + }), 112 + }), 113 + tsc.callExpression({ 114 + functionName: tsc.propertyAccessExpression({ 115 + expression: v.placeholder, 116 + name: identifiers.schemas.bigInt, 117 + }), 118 + }), 119 + ], 120 + multiLine: false, 121 + }), 122 + ], 123 + }); 124 + pipes.push(unionExpression); 125 + 126 + // Add transform to convert to BigInt 127 + const transformExpression = tsc.callExpression({ 128 + functionName: tsc.propertyAccessExpression({ 129 + expression: v.placeholder, 130 + name: identifiers.actions.transform, 131 + }), 132 + parameters: [ 133 + tsc.arrowFunction({ 134 + parameters: [{ name: 'x' }], 135 + statements: tsc.callExpression({ 136 + functionName: 'BigInt', 137 + parameters: [tsc.identifier({ text: 'x' })], 138 + }), 139 + }), 140 + ], 141 + }); 142 + pipes.push(transformExpression); 143 + } else { 144 + // For regular number formats, use number schema 145 + const expression = tsc.callExpression({ 146 + functionName: tsc.propertyAccessExpression({ 147 + expression: v.placeholder, 148 + name: identifiers.schemas.number, 149 + }), 150 + }); 151 + pipes.push(expression); 152 + } 153 + 154 + // Add integer validation for integer types (except when using bigint union) 155 + if (!isBigInt && isInteger) { 156 + const expression = tsc.callExpression({ 157 + functionName: tsc.propertyAccessExpression({ 158 + expression: v.placeholder, 159 + name: identifiers.actions.integer, 160 + }), 161 + }); 162 + pipes.push(expression); 163 + } 164 + 165 + // Add format-specific range validations 166 + if (formatInfo) { 167 + const minValue = formatInfo.min; 168 + const maxValue = formatInfo.max; 169 + const minErrorMessage = formatInfo.minError; 170 + const maxErrorMessage = formatInfo.maxError; 171 + 172 + // Add minimum value validation 173 + const minExpression = tsc.callExpression({ 174 + functionName: tsc.propertyAccessExpression({ 175 + expression: v.placeholder, 176 + name: identifiers.actions.minValue, 177 + }), 178 + parameters: [ 179 + isBigInt 180 + ? tsc.callExpression({ 181 + functionName: 'BigInt', 182 + parameters: [tsc.ots.string(minValue.toString())], 183 + }) 184 + : tsc.ots.number(minValue as number), 185 + tsc.ots.string(minErrorMessage), 186 + ], 187 + }); 188 + pipes.push(minExpression); 189 + 190 + // Add maximum value validation 191 + const maxExpression = tsc.callExpression({ 192 + functionName: tsc.propertyAccessExpression({ 193 + expression: v.placeholder, 194 + name: identifiers.actions.maxValue, 195 + }), 196 + parameters: [ 197 + isBigInt 198 + ? tsc.callExpression({ 199 + functionName: 'BigInt', 200 + parameters: [tsc.ots.string(maxValue.toString())], 201 + }) 202 + : tsc.ots.number(maxValue as number), 203 + tsc.ots.string(maxErrorMessage), 204 + ], 205 + }); 206 + pipes.push(maxExpression); 207 + } 208 + 209 + if (schema.exclusiveMinimum !== undefined) { 210 + const expression = tsc.callExpression({ 211 + functionName: tsc.propertyAccessExpression({ 212 + expression: v.placeholder, 213 + name: identifiers.actions.gtValue, 214 + }), 215 + parameters: [ 216 + numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 217 + ], 218 + }); 219 + pipes.push(expression); 220 + } else if (schema.minimum !== undefined) { 221 + const expression = tsc.callExpression({ 222 + functionName: tsc.propertyAccessExpression({ 223 + expression: v.placeholder, 224 + name: identifiers.actions.minValue, 225 + }), 226 + parameters: [numberParameter({ isBigInt, value: schema.minimum })], 227 + }); 228 + pipes.push(expression); 229 + } 230 + 231 + if (schema.exclusiveMaximum !== undefined) { 232 + const expression = tsc.callExpression({ 233 + functionName: tsc.propertyAccessExpression({ 234 + expression: v.placeholder, 235 + name: identifiers.actions.ltValue, 236 + }), 237 + parameters: [ 238 + numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 239 + ], 240 + }); 241 + pipes.push(expression); 242 + } else if (schema.maximum !== undefined) { 243 + const expression = tsc.callExpression({ 244 + functionName: tsc.propertyAccessExpression({ 245 + expression: v.placeholder, 246 + name: identifiers.actions.maxValue, 247 + }), 248 + parameters: [numberParameter({ isBigInt, value: schema.maximum })], 249 + }); 250 + pipes.push(expression); 251 + } 252 + 253 + return pipesToAst({ pipes, plugin }); 254 + };
+118
packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import { numberRegExp } from '../../../../utils/regexp'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import type { IrSchemaToAstOptions } from '../../shared/types'; 7 + import { identifiers } from '../constants'; 8 + import { pipesToAst } from '../pipesToAst'; 9 + import { irSchemaToAst } from '../plugin'; 10 + 11 + export const objectToAst = ({ 12 + plugin, 13 + schema, 14 + state, 15 + }: IrSchemaToAstOptions & { 16 + schema: SchemaWithType<'object'>; 17 + }): { 18 + anyType: string; 19 + expression: ts.CallExpression; 20 + } => { 21 + // TODO: parser - handle constants 22 + const properties: Array<ts.PropertyAssignment> = []; 23 + 24 + const required = schema.required ?? []; 25 + 26 + for (const name in schema.properties) { 27 + const property = schema.properties[name]!; 28 + const isRequired = required.includes(name); 29 + 30 + const schemaPipes = irSchemaToAst({ 31 + optional: !isRequired, 32 + plugin, 33 + schema: property, 34 + state: { 35 + ...state, 36 + _path: [...state._path, 'properties', name], 37 + }, 38 + }); 39 + 40 + numberRegExp.lastIndex = 0; 41 + let propertyName; 42 + if (numberRegExp.test(name)) { 43 + // For numeric literals, we'll handle negative numbers by using a string literal 44 + // instead of trying to use a PrefixUnaryExpression 45 + propertyName = name.startsWith('-') 46 + ? ts.factory.createStringLiteral(name) 47 + : ts.factory.createNumericLiteral(name); 48 + } else { 49 + propertyName = name; 50 + } 51 + // TODO: parser - abstract safe property name logic 52 + if ( 53 + ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 54 + !name.startsWith("'") && 55 + !name.endsWith("'") 56 + ) { 57 + propertyName = `'${name}'`; 58 + } 59 + properties.push( 60 + tsc.propertyAssignment({ 61 + initializer: pipesToAst({ pipes: schemaPipes, plugin }), 62 + name: propertyName, 63 + }), 64 + ); 65 + } 66 + 67 + const v = plugin.referenceSymbol( 68 + plugin.api.getSelector('external', 'valibot.v'), 69 + ); 70 + 71 + if ( 72 + schema.additionalProperties && 73 + schema.additionalProperties.type === 'object' && 74 + !Object.keys(properties).length 75 + ) { 76 + const pipes = irSchemaToAst({ 77 + plugin, 78 + schema: schema.additionalProperties, 79 + state: { 80 + ...state, 81 + _path: [...state._path, 'additionalProperties'], 82 + }, 83 + }); 84 + const expression = tsc.callExpression({ 85 + functionName: tsc.propertyAccessExpression({ 86 + expression: v.placeholder, 87 + name: identifiers.schemas.record, 88 + }), 89 + parameters: [ 90 + tsc.callExpression({ 91 + functionName: tsc.propertyAccessExpression({ 92 + expression: v.placeholder, 93 + name: identifiers.schemas.string, 94 + }), 95 + parameters: [], 96 + }), 97 + pipesToAst({ pipes, plugin }), 98 + ], 99 + }); 100 + return { 101 + anyType: 'AnyZodObject', 102 + expression, 103 + }; 104 + } 105 + 106 + const expression = tsc.callExpression({ 107 + functionName: tsc.propertyAccessExpression({ 108 + expression: v.placeholder, 109 + name: identifiers.schemas.object, 110 + }), 111 + parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 112 + }); 113 + return { 114 + // Zod uses AnyZodObject here, maybe we want to be more specific too 115 + anyType: identifiers.types.GenericSchema.text, 116 + expression, 117 + }; 118 + };
+143
packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import type { IrSchemaToAstOptions } from '../../shared/types'; 6 + import { identifiers } from '../constants'; 7 + import { pipesToAst } from '../pipesToAst'; 8 + 9 + export const stringToAst = ({ 10 + plugin, 11 + schema, 12 + }: IrSchemaToAstOptions & { 13 + schema: SchemaWithType<'string'>; 14 + }) => { 15 + const v = plugin.referenceSymbol( 16 + plugin.api.getSelector('external', 'valibot.v'), 17 + ); 18 + 19 + if (typeof schema.const === 'string') { 20 + const expression = tsc.callExpression({ 21 + functionName: tsc.propertyAccessExpression({ 22 + expression: v.placeholder, 23 + name: identifiers.schemas.literal, 24 + }), 25 + parameters: [tsc.ots.string(schema.const)], 26 + }); 27 + return expression; 28 + } 29 + 30 + const pipes: Array<ts.CallExpression> = []; 31 + 32 + const expression = tsc.callExpression({ 33 + functionName: tsc.propertyAccessExpression({ 34 + expression: v.placeholder, 35 + name: identifiers.schemas.string, 36 + }), 37 + }); 38 + pipes.push(expression); 39 + 40 + if (schema.format) { 41 + switch (schema.format) { 42 + case 'date': 43 + pipes.push( 44 + tsc.callExpression({ 45 + functionName: tsc.propertyAccessExpression({ 46 + expression: v.placeholder, 47 + name: identifiers.actions.isoDate, 48 + }), 49 + }), 50 + ); 51 + break; 52 + case 'date-time': 53 + pipes.push( 54 + tsc.callExpression({ 55 + functionName: tsc.propertyAccessExpression({ 56 + expression: v.placeholder, 57 + name: identifiers.actions.isoTimestamp, 58 + }), 59 + }), 60 + ); 61 + break; 62 + case 'ipv4': 63 + case 'ipv6': 64 + pipes.push( 65 + tsc.callExpression({ 66 + functionName: tsc.propertyAccessExpression({ 67 + expression: v.placeholder, 68 + name: identifiers.actions.ip, 69 + }), 70 + }), 71 + ); 72 + break; 73 + case 'uri': 74 + pipes.push( 75 + tsc.callExpression({ 76 + functionName: tsc.propertyAccessExpression({ 77 + expression: v.placeholder, 78 + name: identifiers.actions.url, 79 + }), 80 + }), 81 + ); 82 + break; 83 + case 'email': 84 + case 'time': 85 + case 'uuid': 86 + pipes.push( 87 + tsc.callExpression({ 88 + functionName: tsc.propertyAccessExpression({ 89 + expression: v.placeholder, 90 + name: tsc.identifier({ text: schema.format }), 91 + }), 92 + }), 93 + ); 94 + break; 95 + } 96 + } 97 + 98 + if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 99 + const expression = tsc.callExpression({ 100 + functionName: tsc.propertyAccessExpression({ 101 + expression: v.placeholder, 102 + name: identifiers.actions.length, 103 + }), 104 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 105 + }); 106 + pipes.push(expression); 107 + } else { 108 + if (schema.minLength !== undefined) { 109 + const expression = tsc.callExpression({ 110 + functionName: tsc.propertyAccessExpression({ 111 + expression: v.placeholder, 112 + name: identifiers.actions.minLength, 113 + }), 114 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 115 + }); 116 + pipes.push(expression); 117 + } 118 + 119 + if (schema.maxLength !== undefined) { 120 + const expression = tsc.callExpression({ 121 + functionName: tsc.propertyAccessExpression({ 122 + expression: v.placeholder, 123 + name: identifiers.actions.maxLength, 124 + }), 125 + parameters: [tsc.valueToExpression({ value: schema.maxLength })], 126 + }); 127 + pipes.push(expression); 128 + } 129 + } 130 + 131 + if (schema.pattern) { 132 + const expression = tsc.callExpression({ 133 + functionName: tsc.propertyAccessExpression({ 134 + expression: v.placeholder, 135 + name: identifiers.actions.regex, 136 + }), 137 + parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 138 + }); 139 + pipes.push(expression); 140 + } 141 + 142 + return pipesToAst({ pipes, plugin }); 143 + };
+77
packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + import { pipesToAst } from '../pipesToAst'; 6 + import { irSchemaToAst } from '../plugin'; 7 + import { unknownToAst } from './unknown'; 8 + 9 + export const tupleToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'tuple'>; 15 + }) => { 16 + const v = plugin.referenceSymbol( 17 + plugin.api.getSelector('external', 'valibot.v'), 18 + ); 19 + 20 + if (schema.const && Array.isArray(schema.const)) { 21 + const tupleElements = schema.const.map((value) => 22 + tsc.callExpression({ 23 + functionName: tsc.propertyAccessExpression({ 24 + expression: v.placeholder, 25 + name: identifiers.schemas.literal, 26 + }), 27 + parameters: [tsc.valueToExpression({ value })], 28 + }), 29 + ); 30 + const expression = tsc.callExpression({ 31 + functionName: tsc.propertyAccessExpression({ 32 + expression: v.placeholder, 33 + name: identifiers.schemas.tuple, 34 + }), 35 + parameters: [ 36 + tsc.arrayLiteralExpression({ 37 + elements: tupleElements, 38 + }), 39 + ], 40 + }); 41 + return expression; 42 + } 43 + 44 + if (schema.items) { 45 + const tupleElements = schema.items.map((item, index) => { 46 + const schemaPipes = irSchemaToAst({ 47 + plugin, 48 + schema: item, 49 + state: { 50 + ...state, 51 + _path: [...state._path, 'items', index], 52 + }, 53 + }); 54 + return pipesToAst({ pipes: schemaPipes, plugin }); 55 + }); 56 + const expression = tsc.callExpression({ 57 + functionName: tsc.propertyAccessExpression({ 58 + expression: v.placeholder, 59 + name: identifiers.schemas.tuple, 60 + }), 61 + parameters: [ 62 + tsc.arrayLiteralExpression({ 63 + elements: tupleElements, 64 + }), 65 + ], 66 + }); 67 + return expression; 68 + } 69 + 70 + return unknownToAst({ 71 + plugin, 72 + schema: { 73 + type: 'unknown', 74 + }, 75 + state, 76 + }); 77 + };
+22
packages/openapi-ts/src/plugins/valibot/v1/toAst/undefined.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + 6 + export const undefinedToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'undefined'>; 10 + }) => { 11 + const v = plugin.referenceSymbol( 12 + plugin.api.getSelector('external', 'valibot.v'), 13 + ); 14 + 15 + const expression = tsc.callExpression({ 16 + functionName: tsc.propertyAccessExpression({ 17 + expression: v.placeholder, 18 + name: identifiers.schemas.undefined, 19 + }), 20 + }); 21 + return expression; 22 + };
+22
packages/openapi-ts/src/plugins/valibot/v1/toAst/unknown.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + 6 + export const unknownToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'unknown'>; 10 + }) => { 11 + const v = plugin.referenceSymbol( 12 + plugin.api.getSelector('external', 'valibot.v'), 13 + ); 14 + 15 + const expression = tsc.callExpression({ 16 + functionName: tsc.propertyAccessExpression({ 17 + expression: v.placeholder, 18 + name: identifiers.schemas.unknown, 19 + }), 20 + }); 21 + return expression; 22 + };
+22
packages/openapi-ts/src/plugins/valibot/v1/toAst/void.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import type { IrSchemaToAstOptions } from '../../shared/types'; 4 + import { identifiers } from '../constants'; 5 + 6 + export const voidToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'void'>; 10 + }) => { 11 + const v = plugin.referenceSymbol( 12 + plugin.api.getSelector('external', 'valibot.v'), 13 + ); 14 + 15 + const expression = tsc.callExpression({ 16 + functionName: tsc.propertyAccessExpression({ 17 + expression: v.placeholder, 18 + name: identifiers.schemas.void, 19 + }), 20 + }); 21 + return expression; 22 + };
+9 -14
packages/openapi-ts/src/plugins/valibot/webhook.ts packages/openapi-ts/src/plugins/valibot/v1/webhook.ts
··· 1 - import type { IR } from '../../ir/types'; 2 - import { buildName } from '../../openApi/shared/utils/name'; 3 - import { pathToSymbolResourceType } from '../shared/utils/meta'; 4 - import { schemaToValibotSchema, type State } from './plugin'; 5 - import type { ValibotPlugin } from './types'; 1 + import type { IR } from '../../../ir/types'; 2 + import { buildName } from '../../../openApi/shared/utils/name'; 3 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 4 + import type { IrSchemaToAstOptions } from '../shared/types'; 5 + import { irSchemaToAst } from './plugin'; 6 6 7 - export const webhookToValibotSchema = ({ 8 - _path, 7 + export const irWebhookToAst = ({ 9 8 operation, 10 9 plugin, 11 10 state, 12 - }: { 13 - _path: ReadonlyArray<string | number>; 11 + }: IrSchemaToAstOptions & { 14 12 operation: IR.OperationObject; 15 - plugin: ValibotPlugin['Instance']; 16 - state: State; 17 13 }) => { 18 14 if (plugin.config.webhooks.enabled) { 19 15 const requiredProperties = new Set<string>(); ··· 116 112 const symbol = plugin.registerSymbol({ 117 113 exported: true, 118 114 meta: { 119 - resourceType: pathToSymbolResourceType(_path), 115 + resourceType: pathToSymbolResourceType(state._path), 120 116 }, 121 117 name: buildName({ 122 118 config: plugin.config.webhooks, ··· 124 120 }), 125 121 selector: plugin.api.getSelector('webhook-request', operation.id), 126 122 }); 127 - schemaToValibotSchema({ 128 - _path, 123 + irSchemaToAst({ 129 124 plugin, 130 125 schema: schemaData, 131 126 state,
+31 -75
packages/openapi-ts/src/plugins/zod/api.ts
··· 1 1 import type { Selector } from '@hey-api/codegen-core'; 2 2 import type ts from 'typescript'; 3 3 4 - import type { IR } from '../../ir/types'; 5 - import { tsc } from '../../tsc'; 6 4 import type { Plugin } from '../types'; 7 - import { identifiers } from './constants'; 8 - import type { ZodPlugin } from './types'; 5 + import { 6 + createRequestValidatorMini, 7 + createResponseValidatorMini, 8 + } from './mini/api'; 9 + import type { ValidatorArgs } from './shared/types'; 10 + import { createRequestValidatorV3, createResponseValidatorV3 } from './v3/api'; 11 + import { createRequestValidatorV4, createResponseValidatorV4 } from './v4/api'; 9 12 10 13 type SelectorType = 11 14 | 'data' 12 - | 'import' 15 + | 'external' 13 16 | 'ref' 14 17 | 'responses' 15 18 | 'type-infer-data' ··· 18 21 | 'type-infer-webhook-request' 19 22 | 'webhook-request'; 20 23 21 - type ValidatorArgs = { 22 - operation: IR.OperationObject; 23 - plugin: ZodPlugin['Instance']; 24 - }; 25 - 26 24 export type IApi = { 27 25 createRequestValidator: (args: ValidatorArgs) => ts.ArrowFunction | undefined; 28 26 createResponseValidator: ( ··· 32 30 * @param type Selector type. 33 31 * @param value Depends on `type`: 34 32 * - `data`: `operation.id` string 35 - * - `import`: headless symbols representing module imports 33 + * - `external`: external modules 36 34 * - `ref`: `$ref` JSON pointer 37 35 * - `responses`: `operation.id` string 38 36 * - `type-infer-data`: `operation.id` string ··· 48 46 export class Api implements IApi { 49 47 constructor(public meta: Plugin.Name<'zod'>) {} 50 48 51 - createRequestValidator({ 52 - operation, 53 - plugin, 54 - }: ValidatorArgs): ts.ArrowFunction | undefined { 55 - const symbol = plugin.getSymbol( 56 - plugin.api.getSelector('data', operation.id), 57 - ); 58 - if (!symbol) return; 59 - 60 - const dataParameterName = 'data'; 61 - 62 - return tsc.arrowFunction({ 63 - async: true, 64 - parameters: [ 65 - { 66 - name: dataParameterName, 67 - }, 68 - ], 69 - statements: [ 70 - tsc.returnStatement({ 71 - expression: tsc.awaitExpression({ 72 - expression: tsc.callExpression({ 73 - functionName: tsc.propertyAccessExpression({ 74 - expression: symbol.placeholder, 75 - name: identifiers.parseAsync, 76 - }), 77 - parameters: [tsc.identifier({ text: dataParameterName })], 78 - }), 79 - }), 80 - }), 81 - ], 82 - }); 49 + createRequestValidator(args: ValidatorArgs): ts.ArrowFunction | undefined { 50 + const { plugin } = args; 51 + switch (plugin.config.compatibilityVersion) { 52 + case 3: 53 + return createRequestValidatorV3(args); 54 + case 'mini': 55 + return createRequestValidatorMini(args); 56 + case 4: 57 + default: 58 + return createRequestValidatorV4(args); 59 + } 83 60 } 84 61 85 - createResponseValidator({ 86 - operation, 87 - plugin, 88 - }: ValidatorArgs): ts.ArrowFunction | undefined { 89 - const symbol = plugin.getSymbol( 90 - plugin.api.getSelector('responses', operation.id), 91 - ); 92 - if (!symbol) return; 93 - 94 - const dataParameterName = 'data'; 95 - 96 - return tsc.arrowFunction({ 97 - async: true, 98 - parameters: [ 99 - { 100 - name: dataParameterName, 101 - }, 102 - ], 103 - statements: [ 104 - tsc.returnStatement({ 105 - expression: tsc.awaitExpression({ 106 - expression: tsc.callExpression({ 107 - functionName: tsc.propertyAccessExpression({ 108 - expression: symbol.placeholder, 109 - name: identifiers.parseAsync, 110 - }), 111 - parameters: [tsc.identifier({ text: dataParameterName })], 112 - }), 113 - }), 114 - }), 115 - ], 116 - }); 62 + createResponseValidator(args: ValidatorArgs): ts.ArrowFunction | undefined { 63 + const { plugin } = args; 64 + switch (plugin.config.compatibilityVersion) { 65 + case 3: 66 + return createResponseValidatorV3(args); 67 + case 'mini': 68 + return createResponseValidatorMini(args); 69 + case 4: 70 + default: 71 + return createResponseValidatorV4(args); 72 + } 117 73 } 118 74 119 75 getSelector(...args: ReadonlyArray<string | undefined>): Selector {
+1
packages/openapi-ts/src/plugins/zod/constants.ts
··· 1 1 import { tsc } from '../../tsc'; 2 2 3 + // TODO: this is inaccurate, it combines identifiers for all supported versions 3 4 export const identifiers = { 4 5 ZodMiniOptional: tsc.identifier({ text: 'ZodMiniOptional' }), 5 6 ZodOptional: tsc.identifier({ text: 'ZodOptional' }),
+16 -18
packages/openapi-ts/src/plugins/zod/export.ts packages/openapi-ts/src/plugins/zod/shared/export.ts
··· 1 1 import type { Symbol } from '@hey-api/codegen-core'; 2 2 import type ts from 'typescript'; 3 3 4 - import type { IR } from '../../ir/types'; 5 - import { tsc } from '../../tsc'; 6 - import { createSchemaComment } from '../shared/utils/schema'; 7 - import { identifiers } from './constants'; 8 - import type { ZodSchema } from './shared/types'; 9 - import type { ZodPlugin } from './types'; 4 + import type { IR } from '../../../ir/types'; 5 + import { tsc } from '../../../tsc'; 6 + import { createSchemaComment } from '../../shared/utils/schema'; 7 + import { identifiers } from '../constants'; 8 + import type { ZodPlugin } from '../types'; 9 + import type { Ast } from './types'; 10 10 11 - export const exportZodSchema = ({ 11 + export const exportAst = ({ 12 + ast, 12 13 plugin, 13 14 schema, 14 15 symbol, 15 16 typeInferSymbol, 16 - zodSchema, 17 17 }: { 18 + ast: Ast; 18 19 plugin: ZodPlugin['Instance']; 19 20 schema: IR.SchemaObject; 20 21 symbol: Symbol; 21 22 typeInferSymbol: Symbol | undefined; 22 - zodSchema: ZodSchema; 23 - }) => { 24 - const zSymbol = plugin.referenceSymbol( 25 - plugin.api.getSelector('import', 'zod'), 26 - ); 23 + }): void => { 24 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 27 25 28 26 const statement = tsc.constVariable({ 29 27 comment: plugin.config.comments 30 28 ? createSchemaComment({ schema }) 31 29 : undefined, 32 30 exportConst: symbol.exported, 33 - expression: zodSchema.expression, 31 + expression: ast.expression, 34 32 name: symbol.placeholder, 35 - typeName: zodSchema.typeName 33 + typeName: ast.typeName 36 34 ? (tsc.propertyAccessExpression({ 37 - expression: zSymbol.placeholder, 38 - name: zodSchema.typeName, 35 + expression: z.placeholder, 36 + name: ast.typeName, 39 37 }) as unknown as ts.TypeNode) 40 38 : undefined, 41 39 }); ··· 52 50 }) as unknown as ts.TypeNode, 53 51 ], 54 52 typeName: tsc.propertyAccessExpression({ 55 - expression: zSymbol.placeholder, 53 + expression: z.placeholder, 56 54 name: identifiers.infer, 57 55 }) as unknown as string, 58 56 }),
+71
packages/openapi-ts/src/plugins/zod/mini/api.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + import { identifiers } from '../constants'; 5 + import type { ValidatorArgs } from '../shared/types'; 6 + 7 + export const createRequestValidatorMini = ({ 8 + operation, 9 + plugin, 10 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 11 + const symbol = plugin.getSymbol(plugin.api.getSelector('data', operation.id)); 12 + if (!symbol) return; 13 + 14 + const dataParameterName = 'data'; 15 + 16 + return tsc.arrowFunction({ 17 + async: true, 18 + parameters: [ 19 + { 20 + name: dataParameterName, 21 + }, 22 + ], 23 + statements: [ 24 + tsc.returnStatement({ 25 + expression: tsc.awaitExpression({ 26 + expression: tsc.callExpression({ 27 + functionName: tsc.propertyAccessExpression({ 28 + expression: symbol.placeholder, 29 + name: identifiers.parseAsync, 30 + }), 31 + parameters: [tsc.identifier({ text: dataParameterName })], 32 + }), 33 + }), 34 + }), 35 + ], 36 + }); 37 + }; 38 + 39 + export const createResponseValidatorMini = ({ 40 + operation, 41 + plugin, 42 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 43 + const symbol = plugin.getSymbol( 44 + plugin.api.getSelector('responses', operation.id), 45 + ); 46 + if (!symbol) return; 47 + 48 + const dataParameterName = 'data'; 49 + 50 + return tsc.arrowFunction({ 51 + async: true, 52 + parameters: [ 53 + { 54 + name: dataParameterName, 55 + }, 56 + ], 57 + statements: [ 58 + tsc.returnStatement({ 59 + expression: tsc.awaitExpression({ 60 + expression: tsc.callExpression({ 61 + functionName: tsc.propertyAccessExpression({ 62 + expression: symbol.placeholder, 63 + name: identifiers.parseAsync, 64 + }), 65 + parameters: [tsc.identifier({ text: dataParameterName })], 66 + }), 67 + }), 68 + }), 69 + ], 70 + }); 71 + };
+105 -1088
packages/openapi-ts/src/plugins/zod/mini/plugin.ts
··· 1 - import ts from 'typescript'; 2 - 3 1 import { deduplicateSchema } from '../../../ir/schema'; 4 2 import type { IR } from '../../../ir/types'; 5 3 import { buildName } from '../../../openApi/shared/utils/name'; 6 4 import { tsc } from '../../../tsc'; 7 - import { refToName } from '../../../utils/ref'; 8 - import { numberRegExp } from '../../../utils/regexp'; 5 + import { jsonPointerToPath, refToName } from '../../../utils/ref'; 6 + import type { SchemaWithType } from '../../shared/types/schema'; 7 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 9 8 import { identifiers } from '../constants'; 10 - import { exportZodSchema } from '../export'; 9 + import { exportAst } from '../shared/export'; 11 10 import { getZodModule } from '../shared/module'; 12 - import { operationToZodSchema } from '../shared/operation'; 13 - import type { SchemaWithType, State, ZodSchema } from '../shared/types'; 14 - import { webhookToZodSchema } from '../shared/webhook'; 11 + import { numberParameter } from '../shared/numbers'; 12 + import { irOperationToAst } from '../shared/operation'; 13 + import type { Ast, IrSchemaToAstOptions } from '../shared/types'; 14 + import { irWebhookToAst } from '../shared/webhook'; 15 15 import type { ZodPlugin } from '../types'; 16 - 17 - const arrayTypeToZodSchema = ({ 18 - plugin, 19 - schema, 20 - state, 21 - }: { 22 - plugin: ZodPlugin['Instance']; 23 - schema: SchemaWithType<'array'>; 24 - state: State; 25 - }): Omit<ZodSchema, 'typeName'> => { 26 - const zSymbol = plugin.referenceSymbol( 27 - plugin.api.getSelector('import', 'zod'), 28 - ); 29 - 30 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 31 - 32 - const functionName = tsc.propertyAccessExpression({ 33 - expression: zSymbol.placeholder, 34 - name: identifiers.array, 35 - }); 36 - 37 - if (!schema.items) { 38 - result.expression = tsc.callExpression({ 39 - functionName, 40 - parameters: [ 41 - unknownTypeToZodSchema({ 42 - plugin, 43 - schema: { 44 - type: 'unknown', 45 - }, 46 - }).expression, 47 - ], 48 - }); 49 - } else { 50 - schema = deduplicateSchema({ schema }); 51 - 52 - // at least one item is guaranteed 53 - const itemExpressions = schema.items!.map((item) => { 54 - const zodSchema = schemaToZodSchema({ 55 - plugin, 56 - schema: item, 57 - state, 58 - }); 59 - if (zodSchema.hasCircularReference) { 60 - result.hasCircularReference = true; 61 - } 62 - return zodSchema.expression; 63 - }); 64 - 65 - if (itemExpressions.length === 1) { 66 - result.expression = tsc.callExpression({ 67 - functionName, 68 - parameters: itemExpressions, 69 - }); 70 - } else { 71 - if (schema.logicalOperator === 'and') { 72 - const firstSchema = schema.items![0]!; 73 - // we want to add an intersection, but not every schema can use the same API. 74 - // if the first item contains another array or not an object, we cannot use 75 - // `.intersection()` as that does not exist on `.union()` and non-object schemas. 76 - let intersectionExpression: ts.Expression; 77 - if ( 78 - firstSchema.logicalOperator === 'or' || 79 - (firstSchema.type && firstSchema.type !== 'object') 80 - ) { 81 - intersectionExpression = tsc.callExpression({ 82 - functionName: tsc.propertyAccessExpression({ 83 - expression: zSymbol.placeholder, 84 - name: identifiers.intersection, 85 - }), 86 - parameters: itemExpressions, 87 - }); 88 - } else { 89 - intersectionExpression = itemExpressions[0]!; 90 - for (let i = 1; i < itemExpressions.length; i++) { 91 - intersectionExpression = tsc.callExpression({ 92 - functionName: tsc.propertyAccessExpression({ 93 - expression: zSymbol.placeholder, 94 - name: identifiers.intersection, 95 - }), 96 - parameters: [intersectionExpression, itemExpressions[i]!], 97 - }); 98 - } 99 - } 16 + import { irSchemaWithTypeToAst } from './toAst'; 100 17 101 - result.expression = tsc.callExpression({ 102 - functionName, 103 - parameters: [intersectionExpression], 104 - }); 105 - } else { 106 - result.expression = tsc.callExpression({ 107 - functionName: tsc.propertyAccessExpression({ 108 - expression: zSymbol.placeholder, 109 - name: identifiers.array, 110 - }), 111 - parameters: [ 112 - tsc.callExpression({ 113 - functionName: tsc.propertyAccessExpression({ 114 - expression: zSymbol.placeholder, 115 - name: identifiers.union, 116 - }), 117 - parameters: [ 118 - tsc.arrayLiteralExpression({ 119 - elements: itemExpressions, 120 - }), 121 - ], 122 - }), 123 - ], 124 - }); 125 - } 126 - } 127 - } 128 - 129 - const checks: Array<ts.Expression> = []; 130 - 131 - if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 132 - checks.push( 133 - tsc.callExpression({ 134 - functionName: tsc.propertyAccessExpression({ 135 - expression: zSymbol.placeholder, 136 - name: identifiers.length, 137 - }), 138 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 139 - }), 140 - ); 141 - } else { 142 - if (schema.minItems !== undefined) { 143 - checks.push( 144 - tsc.callExpression({ 145 - functionName: tsc.propertyAccessExpression({ 146 - expression: zSymbol.placeholder, 147 - name: identifiers.minLength, 148 - }), 149 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 150 - }), 151 - ); 152 - } 153 - 154 - if (schema.maxItems !== undefined) { 155 - checks.push( 156 - tsc.callExpression({ 157 - functionName: tsc.propertyAccessExpression({ 158 - expression: zSymbol.placeholder, 159 - name: identifiers.maxLength, 160 - }), 161 - parameters: [tsc.valueToExpression({ value: schema.maxItems })], 162 - }), 163 - ); 164 - } 165 - } 166 - 167 - if (checks.length) { 168 - result.expression = tsc.callExpression({ 169 - functionName: tsc.propertyAccessExpression({ 170 - expression: result.expression, 171 - name: identifiers.check, 172 - }), 173 - parameters: checks, 174 - }); 175 - } 176 - 177 - return result as Omit<ZodSchema, 'typeName'>; 178 - }; 179 - 180 - const booleanTypeToZodSchema = ({ 181 - plugin, 182 - schema, 183 - }: { 184 - plugin: ZodPlugin['Instance']; 185 - schema: SchemaWithType<'boolean'>; 186 - }): Omit<ZodSchema, 'typeName'> => { 187 - const zSymbol = plugin.referenceSymbol( 188 - plugin.api.getSelector('import', 'zod'), 189 - ); 190 - 191 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 192 - 193 - if (typeof schema.const === 'boolean') { 194 - result.expression = tsc.callExpression({ 195 - functionName: tsc.propertyAccessExpression({ 196 - expression: zSymbol.placeholder, 197 - name: identifiers.literal, 198 - }), 199 - parameters: [tsc.ots.boolean(schema.const)], 200 - }); 201 - return result as Omit<ZodSchema, 'typeName'>; 202 - } 203 - 204 - result.expression = tsc.callExpression({ 205 - functionName: tsc.propertyAccessExpression({ 206 - expression: zSymbol.placeholder, 207 - name: identifiers.boolean, 208 - }), 209 - }); 210 - return result as Omit<ZodSchema, 'typeName'>; 211 - }; 212 - 213 - const enumTypeToZodSchema = ({ 214 - plugin, 215 - schema, 216 - }: { 217 - plugin: ZodPlugin['Instance']; 218 - schema: SchemaWithType<'enum'>; 219 - }): Omit<ZodSchema, 'typeName'> => { 220 - const zSymbol = plugin.referenceSymbol( 221 - plugin.api.getSelector('import', 'zod'), 222 - ); 223 - 224 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 225 - 226 - const enumMembers: Array<ts.LiteralExpression> = []; 227 - const literalMembers: Array<ts.CallExpression> = []; 228 - 229 - let isNullable = false; 230 - let allStrings = true; 231 - 232 - for (const item of schema.items ?? []) { 233 - // Zod supports string, number, and boolean enums 234 - if (item.type === 'string' && typeof item.const === 'string') { 235 - const stringLiteral = tsc.stringLiteral({ 236 - text: item.const, 237 - }); 238 - enumMembers.push(stringLiteral); 239 - literalMembers.push( 240 - tsc.callExpression({ 241 - functionName: tsc.propertyAccessExpression({ 242 - expression: zSymbol.placeholder, 243 - name: identifiers.literal, 244 - }), 245 - parameters: [stringLiteral], 246 - }), 247 - ); 248 - } else if ( 249 - (item.type === 'number' || item.type === 'integer') && 250 - typeof item.const === 'number' 251 - ) { 252 - allStrings = false; 253 - const numberLiteral = tsc.ots.number(item.const); 254 - literalMembers.push( 255 - tsc.callExpression({ 256 - functionName: tsc.propertyAccessExpression({ 257 - expression: zSymbol.placeholder, 258 - name: identifiers.literal, 259 - }), 260 - parameters: [numberLiteral], 261 - }), 262 - ); 263 - } else if (item.type === 'boolean' && typeof item.const === 'boolean') { 264 - allStrings = false; 265 - const booleanLiteral = tsc.ots.boolean(item.const); 266 - literalMembers.push( 267 - tsc.callExpression({ 268 - functionName: tsc.propertyAccessExpression({ 269 - expression: zSymbol.placeholder, 270 - name: identifiers.literal, 271 - }), 272 - parameters: [booleanLiteral], 273 - }), 274 - ); 275 - } else if (item.type === 'null' || item.const === null) { 276 - isNullable = true; 277 - } 278 - } 279 - 280 - if (!literalMembers.length) { 281 - return unknownTypeToZodSchema({ 282 - plugin, 283 - schema: { 284 - type: 'unknown', 285 - }, 286 - }); 287 - } 288 - 289 - // Use z.enum() for pure string enums, z.union() for mixed or non-string types 290 - if (allStrings && enumMembers.length > 0) { 291 - result.expression = tsc.callExpression({ 292 - functionName: tsc.propertyAccessExpression({ 293 - expression: zSymbol.placeholder, 294 - name: identifiers.enum, 295 - }), 296 - parameters: [ 297 - tsc.arrayLiteralExpression({ 298 - elements: enumMembers, 299 - multiLine: false, 300 - }), 301 - ], 302 - }); 303 - } else if (literalMembers.length === 1) { 304 - // For single-member unions, use the member directly instead of wrapping in z.union() 305 - result.expression = literalMembers[0]; 306 - } else { 307 - result.expression = tsc.callExpression({ 308 - functionName: tsc.propertyAccessExpression({ 309 - expression: zSymbol.placeholder, 310 - name: identifiers.union, 311 - }), 312 - parameters: [ 313 - tsc.arrayLiteralExpression({ 314 - elements: literalMembers, 315 - multiLine: literalMembers.length > 3, 316 - }), 317 - ], 318 - }); 319 - } 320 - 321 - if (isNullable) { 322 - result.expression = tsc.callExpression({ 323 - functionName: tsc.propertyAccessExpression({ 324 - expression: zSymbol.placeholder, 325 - name: identifiers.nullable, 326 - }), 327 - parameters: [result.expression], 328 - }); 329 - } 330 - 331 - return result as Omit<ZodSchema, 'typeName'>; 332 - }; 333 - 334 - const neverTypeToZodSchema = ({ 335 - plugin, 336 - }: { 337 - plugin: ZodPlugin['Instance']; 338 - schema: SchemaWithType<'never'>; 339 - }): Omit<ZodSchema, 'typeName'> => { 340 - const zSymbol = plugin.referenceSymbol( 341 - plugin.api.getSelector('import', 'zod'), 342 - ); 343 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 344 - result.expression = tsc.callExpression({ 345 - functionName: tsc.propertyAccessExpression({ 346 - expression: zSymbol.placeholder, 347 - name: identifiers.never, 348 - }), 349 - }); 350 - return result as Omit<ZodSchema, 'typeName'>; 351 - }; 352 - 353 - const nullTypeToZodSchema = ({ 354 - plugin, 355 - }: { 356 - plugin: ZodPlugin['Instance']; 357 - schema: SchemaWithType<'null'>; 358 - }): Omit<ZodSchema, 'typeName'> => { 359 - const zSymbol = plugin.referenceSymbol( 360 - plugin.api.getSelector('import', 'zod'), 361 - ); 362 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 363 - result.expression = tsc.callExpression({ 364 - functionName: tsc.propertyAccessExpression({ 365 - expression: zSymbol.placeholder, 366 - name: identifiers.null, 367 - }), 368 - }); 369 - return result as Omit<ZodSchema, 'typeName'>; 370 - }; 371 - 372 - const numberParameter = ({ 373 - isBigInt, 374 - value, 375 - }: { 376 - isBigInt: boolean; 377 - value: unknown; 378 - }): ts.Expression | undefined => { 379 - const expression = tsc.valueToExpression({ value }); 380 - 381 - if ( 382 - isBigInt && 383 - (typeof value === 'bigint' || 384 - typeof value === 'number' || 385 - typeof value === 'string' || 386 - typeof value === 'boolean') 387 - ) { 388 - return tsc.callExpression({ 389 - functionName: 'BigInt', 390 - parameters: [expression], 391 - }); 392 - } 393 - 394 - return expression; 395 - }; 396 - 397 - const numberTypeToZodSchema = ({ 398 - plugin, 399 - schema, 400 - }: { 401 - plugin: ZodPlugin['Instance']; 402 - schema: SchemaWithType<'integer' | 'number'>; 403 - }): Omit<ZodSchema, 'typeName'> => { 404 - const zSymbol = plugin.referenceSymbol( 405 - plugin.api.getSelector('import', 'zod'), 406 - ); 407 - 408 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 409 - 410 - const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 411 - 412 - if (typeof schema.const === 'number') { 413 - // TODO: parser - handle bigint constants 414 - result.expression = tsc.callExpression({ 415 - functionName: tsc.propertyAccessExpression({ 416 - expression: zSymbol.placeholder, 417 - name: identifiers.literal, 418 - }), 419 - parameters: [tsc.ots.number(schema.const)], 420 - }); 421 - return result as Omit<ZodSchema, 'typeName'>; 422 - } 423 - 424 - result.expression = tsc.callExpression({ 425 - functionName: isBigInt 426 - ? tsc.propertyAccessExpression({ 427 - expression: tsc.propertyAccessExpression({ 428 - expression: zSymbol.placeholder, 429 - name: identifiers.coerce, 430 - }), 431 - name: identifiers.bigint, 432 - }) 433 - : tsc.propertyAccessExpression({ 434 - expression: zSymbol.placeholder, 435 - name: identifiers.number, 436 - }), 437 - }); 438 - 439 - if (!isBigInt && schema.type === 'integer') { 440 - result.expression = tsc.callExpression({ 441 - functionName: tsc.propertyAccessExpression({ 442 - expression: zSymbol.placeholder, 443 - name: identifiers.int, 444 - }), 445 - }); 446 - } 447 - 448 - const checks: Array<ts.Expression> = []; 449 - 450 - if (schema.exclusiveMinimum !== undefined) { 451 - checks.push( 452 - tsc.callExpression({ 453 - functionName: tsc.propertyAccessExpression({ 454 - expression: zSymbol.placeholder, 455 - name: identifiers.gt, 456 - }), 457 - parameters: [ 458 - numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 459 - ], 460 - }), 461 - ); 462 - } else if (schema.minimum !== undefined) { 463 - checks.push( 464 - tsc.callExpression({ 465 - functionName: tsc.propertyAccessExpression({ 466 - expression: zSymbol.placeholder, 467 - name: identifiers.gte, 468 - }), 469 - parameters: [numberParameter({ isBigInt, value: schema.minimum })], 470 - }), 471 - ); 472 - } 473 - 474 - if (schema.exclusiveMaximum !== undefined) { 475 - checks.push( 476 - tsc.callExpression({ 477 - functionName: tsc.propertyAccessExpression({ 478 - expression: zSymbol.placeholder, 479 - name: identifiers.lt, 480 - }), 481 - parameters: [ 482 - numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 483 - ], 484 - }), 485 - ); 486 - } else if (schema.maximum !== undefined) { 487 - checks.push( 488 - tsc.callExpression({ 489 - functionName: tsc.propertyAccessExpression({ 490 - expression: zSymbol.placeholder, 491 - name: identifiers.lte, 492 - }), 493 - parameters: [numberParameter({ isBigInt, value: schema.maximum })], 494 - }), 495 - ); 496 - } 497 - 498 - if (checks.length) { 499 - result.expression = tsc.callExpression({ 500 - functionName: tsc.propertyAccessExpression({ 501 - expression: result.expression, 502 - name: identifiers.check, 503 - }), 504 - parameters: checks, 505 - }); 506 - } 507 - 508 - return result as Omit<ZodSchema, 'typeName'>; 509 - }; 510 - 511 - const objectTypeToZodSchema = ({ 512 - plugin, 513 - schema, 514 - state, 515 - }: { 516 - plugin: ZodPlugin['Instance']; 517 - schema: SchemaWithType<'object'>; 518 - state: State; 519 - }): Omit<ZodSchema, 'typeName'> => { 520 - const zSymbol = plugin.referenceSymbol( 521 - plugin.api.getSelector('import', 'zod'), 522 - ); 523 - 524 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 525 - 526 - // TODO: parser - handle constants 527 - const properties: Array<ts.PropertyAssignment | ts.GetAccessorDeclaration> = 528 - []; 529 - 530 - const required = schema.required ?? []; 531 - 532 - for (const name in schema.properties) { 533 - const property = schema.properties[name]!; 534 - const isRequired = required.includes(name); 535 - 536 - const propertySchema = schemaToZodSchema({ 537 - optional: !isRequired, 538 - plugin, 539 - schema: property, 540 - state, 541 - }); 542 - if (propertySchema.hasCircularReference) { 543 - result.hasCircularReference = true; 544 - } 545 - 546 - numberRegExp.lastIndex = 0; 547 - let propertyName; 548 - if (numberRegExp.test(name)) { 549 - // For numeric literals, we'll handle negative numbers by using a string literal 550 - // instead of trying to use a PrefixUnaryExpression 551 - propertyName = name.startsWith('-') 552 - ? ts.factory.createStringLiteral(name) 553 - : ts.factory.createNumericLiteral(name); 554 - } else { 555 - propertyName = name; 556 - } 557 - // TODO: parser - abstract safe property name logic 558 - if ( 559 - ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 560 - !name.startsWith("'") && 561 - !name.endsWith("'") 562 - ) { 563 - propertyName = `'${name}'`; 564 - } 565 - 566 - if (propertySchema.hasCircularReference) { 567 - properties.push( 568 - tsc.getAccessorDeclaration({ 569 - name: propertyName, 570 - // @ts-expect-error 571 - returnType: propertySchema.typeName 572 - ? tsc.propertyAccessExpression({ 573 - expression: zSymbol.placeholder, 574 - name: propertySchema.typeName, 575 - }) 576 - : undefined, 577 - statements: [ 578 - tsc.returnStatement({ 579 - expression: propertySchema.expression, 580 - }), 581 - ], 582 - }), 583 - ); 584 - } else { 585 - properties.push( 586 - tsc.propertyAssignment({ 587 - initializer: propertySchema.expression, 588 - name: propertyName, 589 - }), 590 - ); 591 - } 592 - } 593 - 594 - if ( 595 - schema.additionalProperties && 596 - (!schema.properties || !Object.keys(schema.properties).length) 597 - ) { 598 - const zodSchema = schemaToZodSchema({ 599 - plugin, 600 - schema: schema.additionalProperties, 601 - state, 602 - }); 603 - result.expression = tsc.callExpression({ 604 - functionName: tsc.propertyAccessExpression({ 605 - expression: zSymbol.placeholder, 606 - name: identifiers.record, 607 - }), 608 - parameters: [ 609 - tsc.callExpression({ 610 - functionName: tsc.propertyAccessExpression({ 611 - expression: zSymbol.placeholder, 612 - name: identifiers.string, 613 - }), 614 - parameters: [], 615 - }), 616 - zodSchema.expression, 617 - ], 618 - }); 619 - if (zodSchema.hasCircularReference) { 620 - result.hasCircularReference = true; 621 - } 622 - return result as Omit<ZodSchema, 'typeName'>; 623 - } 624 - 625 - result.expression = tsc.callExpression({ 626 - functionName: tsc.propertyAccessExpression({ 627 - expression: zSymbol.placeholder, 628 - name: identifiers.object, 629 - }), 630 - parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 631 - }); 632 - 633 - return result as Omit<ZodSchema, 'typeName'>; 634 - }; 635 - 636 - const stringTypeToZodSchema = ({ 637 - plugin, 638 - schema, 639 - }: { 640 - plugin: ZodPlugin['Instance']; 641 - schema: SchemaWithType<'string'>; 642 - }): Omit<ZodSchema, 'typeName'> => { 643 - const zSymbol = plugin.referenceSymbol( 644 - plugin.api.getSelector('import', 'zod'), 645 - ); 646 - 647 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 648 - 649 - if (typeof schema.const === 'string') { 650 - result.expression = tsc.callExpression({ 651 - functionName: tsc.propertyAccessExpression({ 652 - expression: zSymbol.placeholder, 653 - name: identifiers.literal, 654 - }), 655 - parameters: [tsc.ots.string(schema.const)], 656 - }); 657 - return result as Omit<ZodSchema, 'typeName'>; 658 - } 659 - 660 - result.expression = tsc.callExpression({ 661 - functionName: tsc.propertyAccessExpression({ 662 - expression: zSymbol.placeholder, 663 - name: identifiers.string, 664 - }), 665 - }); 666 - 667 - const dateTimeOptions: { key: string; value: boolean }[] = []; 668 - 669 - if (plugin.config.dates.offset) { 670 - dateTimeOptions.push({ key: 'offset', value: true }); 671 - } 672 - if (plugin.config.dates.local) { 673 - dateTimeOptions.push({ key: 'local', value: true }); 674 - } 675 - 676 - if (schema.format) { 677 - switch (schema.format) { 678 - case 'date': 679 - result.expression = tsc.callExpression({ 680 - functionName: tsc.propertyAccessExpression({ 681 - expression: tsc.propertyAccessExpression({ 682 - expression: zSymbol.placeholder, 683 - name: identifiers.iso, 684 - }), 685 - name: identifiers.date, 686 - }), 687 - }); 688 - break; 689 - case 'date-time': 690 - result.expression = tsc.callExpression({ 691 - functionName: tsc.propertyAccessExpression({ 692 - expression: tsc.propertyAccessExpression({ 693 - expression: zSymbol.placeholder, 694 - name: identifiers.iso, 695 - }), 696 - name: identifiers.datetime, 697 - }), 698 - parameters: 699 - dateTimeOptions.length > 0 700 - ? [ 701 - tsc.objectExpression({ 702 - obj: dateTimeOptions, 703 - }), 704 - ] 705 - : [], 706 - }); 707 - break; 708 - case 'email': 709 - result.expression = tsc.callExpression({ 710 - functionName: tsc.propertyAccessExpression({ 711 - expression: zSymbol.placeholder, 712 - name: identifiers.email, 713 - }), 714 - }); 715 - break; 716 - case 'ipv4': 717 - result.expression = tsc.callExpression({ 718 - functionName: tsc.propertyAccessExpression({ 719 - expression: zSymbol.placeholder, 720 - name: identifiers.ipv4, 721 - }), 722 - }); 723 - break; 724 - case 'ipv6': 725 - result.expression = tsc.callExpression({ 726 - functionName: tsc.propertyAccessExpression({ 727 - expression: zSymbol.placeholder, 728 - name: identifiers.ipv6, 729 - }), 730 - }); 731 - break; 732 - case 'time': 733 - result.expression = tsc.callExpression({ 734 - functionName: tsc.propertyAccessExpression({ 735 - expression: tsc.propertyAccessExpression({ 736 - expression: zSymbol.placeholder, 737 - name: identifiers.iso, 738 - }), 739 - name: identifiers.time, 740 - }), 741 - }); 742 - break; 743 - case 'uri': 744 - result.expression = tsc.callExpression({ 745 - functionName: tsc.propertyAccessExpression({ 746 - expression: zSymbol.placeholder, 747 - name: identifiers.url, 748 - }), 749 - }); 750 - break; 751 - case 'uuid': 752 - result.expression = tsc.callExpression({ 753 - functionName: tsc.propertyAccessExpression({ 754 - expression: zSymbol.placeholder, 755 - name: identifiers.uuid, 756 - }), 757 - }); 758 - break; 759 - } 760 - } 761 - 762 - const checks: Array<ts.Expression> = []; 763 - 764 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 765 - checks.push( 766 - tsc.callExpression({ 767 - functionName: tsc.propertyAccessExpression({ 768 - expression: zSymbol.placeholder, 769 - name: identifiers.length, 770 - }), 771 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 772 - }), 773 - ); 774 - } else { 775 - if (schema.minLength !== undefined) { 776 - checks.push( 777 - tsc.callExpression({ 778 - functionName: tsc.propertyAccessExpression({ 779 - expression: zSymbol.placeholder, 780 - name: identifiers.minLength, 781 - }), 782 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 783 - }), 784 - ); 785 - } 786 - 787 - if (schema.maxLength !== undefined) { 788 - checks.push( 789 - tsc.callExpression({ 790 - functionName: tsc.propertyAccessExpression({ 791 - expression: zSymbol.placeholder, 792 - name: identifiers.maxLength, 793 - }), 794 - parameters: [tsc.valueToExpression({ value: schema.maxLength })], 795 - }), 796 - ); 797 - } 798 - } 799 - 800 - if (schema.pattern) { 801 - checks.push( 802 - tsc.callExpression({ 803 - functionName: tsc.propertyAccessExpression({ 804 - expression: zSymbol.placeholder, 805 - name: identifiers.regex, 806 - }), 807 - parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 808 - }), 809 - ); 810 - } 811 - 812 - if (checks.length) { 813 - result.expression = tsc.callExpression({ 814 - functionName: tsc.propertyAccessExpression({ 815 - expression: result.expression, 816 - name: identifiers.check, 817 - }), 818 - parameters: checks, 819 - }); 820 - } 821 - 822 - return result as Omit<ZodSchema, 'typeName'>; 823 - }; 824 - 825 - const tupleTypeToZodSchema = ({ 826 - plugin, 827 - schema, 828 - state, 829 - }: { 830 - plugin: ZodPlugin['Instance']; 831 - schema: SchemaWithType<'tuple'>; 832 - state: State; 833 - }): Omit<ZodSchema, 'typeName'> => { 834 - const zSymbol = plugin.referenceSymbol( 835 - plugin.api.getSelector('import', 'zod'), 836 - ); 837 - 838 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 839 - 840 - if (schema.const && Array.isArray(schema.const)) { 841 - const tupleElements = schema.const.map((value) => 842 - tsc.callExpression({ 843 - functionName: tsc.propertyAccessExpression({ 844 - expression: zSymbol.placeholder, 845 - name: identifiers.literal, 846 - }), 847 - parameters: [tsc.valueToExpression({ value })], 848 - }), 849 - ); 850 - result.expression = tsc.callExpression({ 851 - functionName: tsc.propertyAccessExpression({ 852 - expression: zSymbol.placeholder, 853 - name: identifiers.tuple, 854 - }), 855 - parameters: [ 856 - tsc.arrayLiteralExpression({ 857 - elements: tupleElements, 858 - }), 859 - ], 860 - }); 861 - return result as Omit<ZodSchema, 'typeName'>; 862 - } 863 - 864 - const tupleElements: Array<ts.Expression> = []; 865 - 866 - for (const item of schema.items ?? []) { 867 - const itemSchema = schemaToZodSchema({ 868 - plugin, 869 - schema: item, 870 - state, 871 - }); 872 - tupleElements.push(itemSchema.expression); 873 - 874 - if (itemSchema.hasCircularReference) { 875 - result.hasCircularReference = true; 876 - } 877 - } 878 - 879 - result.expression = tsc.callExpression({ 880 - functionName: tsc.propertyAccessExpression({ 881 - expression: zSymbol.placeholder, 882 - name: identifiers.tuple, 883 - }), 884 - parameters: [ 885 - tsc.arrayLiteralExpression({ 886 - elements: tupleElements, 887 - }), 888 - ], 889 - }); 890 - 891 - return result as Omit<ZodSchema, 'typeName'>; 892 - }; 893 - 894 - const undefinedTypeToZodSchema = ({ 895 - plugin, 896 - }: { 897 - plugin: ZodPlugin['Instance']; 898 - schema: SchemaWithType<'undefined'>; 899 - }): Omit<ZodSchema, 'typeName'> => { 900 - const zSymbol = plugin.referenceSymbol( 901 - plugin.api.getSelector('import', 'zod'), 902 - ); 903 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 904 - result.expression = tsc.callExpression({ 905 - functionName: tsc.propertyAccessExpression({ 906 - expression: zSymbol.placeholder, 907 - name: identifiers.undefined, 908 - }), 909 - }); 910 - return result as Omit<ZodSchema, 'typeName'>; 911 - }; 912 - 913 - const unknownTypeToZodSchema = ({ 914 - plugin, 915 - }: { 916 - plugin: ZodPlugin['Instance']; 917 - schema: SchemaWithType<'unknown'>; 918 - }): Omit<ZodSchema, 'typeName'> => { 919 - const zSymbol = plugin.referenceSymbol( 920 - plugin.api.getSelector('import', 'zod'), 921 - ); 922 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 923 - result.expression = tsc.callExpression({ 924 - functionName: tsc.propertyAccessExpression({ 925 - expression: zSymbol.placeholder, 926 - name: identifiers.unknown, 927 - }), 928 - }); 929 - return result as Omit<ZodSchema, 'typeName'>; 930 - }; 931 - 932 - const voidTypeToZodSchema = ({ 933 - plugin, 934 - }: { 935 - plugin: ZodPlugin['Instance']; 936 - schema: SchemaWithType<'void'>; 937 - }): Omit<ZodSchema, 'typeName'> => { 938 - const zSymbol = plugin.referenceSymbol( 939 - plugin.api.getSelector('import', 'zod'), 940 - ); 941 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 942 - result.expression = tsc.callExpression({ 943 - functionName: tsc.propertyAccessExpression({ 944 - expression: zSymbol.placeholder, 945 - name: identifiers.void, 946 - }), 947 - }); 948 - return result as Omit<ZodSchema, 'typeName'>; 949 - }; 950 - 951 - const schemaTypeToZodSchema = ({ 952 - plugin, 953 - schema, 954 - state, 955 - }: { 956 - plugin: ZodPlugin['Instance']; 957 - schema: IR.SchemaObject; 958 - state: State; 959 - }): Omit<ZodSchema, 'typeName'> => { 960 - switch (schema.type as Required<IR.SchemaObject>['type']) { 961 - case 'array': 962 - return arrayTypeToZodSchema({ 963 - plugin, 964 - schema: schema as SchemaWithType<'array'>, 965 - state, 966 - }); 967 - case 'boolean': 968 - return booleanTypeToZodSchema({ 969 - plugin, 970 - schema: schema as SchemaWithType<'boolean'>, 971 - }); 972 - case 'enum': 973 - return enumTypeToZodSchema({ 974 - plugin, 975 - schema: schema as SchemaWithType<'enum'>, 976 - }); 977 - case 'integer': 978 - case 'number': 979 - return numberTypeToZodSchema({ 980 - plugin, 981 - schema: schema as SchemaWithType<'integer' | 'number'>, 982 - }); 983 - case 'never': 984 - return neverTypeToZodSchema({ 985 - plugin, 986 - schema: schema as SchemaWithType<'never'>, 987 - }); 988 - case 'null': 989 - return nullTypeToZodSchema({ 990 - plugin, 991 - schema: schema as SchemaWithType<'null'>, 992 - }); 993 - case 'object': 994 - return objectTypeToZodSchema({ 995 - plugin, 996 - schema: schema as SchemaWithType<'object'>, 997 - state, 998 - }); 999 - case 'string': 1000 - return stringTypeToZodSchema({ 1001 - plugin, 1002 - schema: schema as SchemaWithType<'string'>, 1003 - }); 1004 - case 'tuple': 1005 - return tupleTypeToZodSchema({ 1006 - plugin, 1007 - schema: schema as SchemaWithType<'tuple'>, 1008 - state, 1009 - }); 1010 - case 'undefined': 1011 - return undefinedTypeToZodSchema({ 1012 - plugin, 1013 - schema: schema as SchemaWithType<'undefined'>, 1014 - }); 1015 - case 'unknown': 1016 - return unknownTypeToZodSchema({ 1017 - plugin, 1018 - schema: schema as SchemaWithType<'unknown'>, 1019 - }); 1020 - case 'void': 1021 - return voidTypeToZodSchema({ 1022 - plugin, 1023 - schema: schema as SchemaWithType<'void'>, 1024 - }); 1025 - } 1026 - }; 1027 - 1028 - const schemaToZodSchema = ({ 18 + export const irSchemaToAst = ({ 1029 19 optional, 1030 20 plugin, 1031 21 schema, 1032 22 state, 1033 - }: { 23 + }: IrSchemaToAstOptions & { 1034 24 /** 1035 25 * Accept `optional` to handle optional object properties. We can't handle 1036 26 * this inside the object function because `.optional()` must come before 1037 27 * `.default()` which is handled in this function. 1038 28 */ 1039 29 optional?: boolean; 1040 - plugin: ZodPlugin['Instance']; 1041 30 schema: IR.SchemaObject; 1042 - state: State; 1043 - }): ZodSchema => { 1044 - let zodSchema: Partial<ZodSchema> = {}; 31 + }): Ast => { 32 + let ast: Partial<Ast> = {}; 1045 33 1046 - const zSymbol = plugin.referenceSymbol( 1047 - plugin.api.getSelector('import', 'zod'), 1048 - ); 34 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 1049 35 1050 36 if (schema.$ref) { 1051 37 const isCircularReference = state.circularReferenceTracker.includes( ··· 1064 50 } 1065 51 1066 52 if (isSelfReference) { 1067 - zodSchema.expression = tsc.callExpression({ 53 + ast.expression = tsc.callExpression({ 1068 54 functionName: tsc.propertyAccessExpression({ 1069 - expression: zSymbol.placeholder, 55 + expression: z.placeholder, 1070 56 name: identifiers.lazy, 1071 57 }), 1072 58 parameters: [ ··· 1081 67 ], 1082 68 }); 1083 69 } else { 1084 - zodSchema.expression = tsc.identifier({ text: symbol.placeholder }); 70 + ast.expression = tsc.identifier({ text: symbol.placeholder }); 1085 71 } 1086 - zodSchema.hasCircularReference = schema.circular; 72 + ast.hasCircularReference = schema.circular; 1087 73 } else { 1088 74 if (!symbol) { 1089 75 // if $ref hasn't been processed yet, inline it to avoid the ··· 1094 80 id: schema.$ref, 1095 81 plugin, 1096 82 schema: ref, 1097 - state, 83 + state: { 84 + ...state, 85 + _path: jsonPointerToPath(schema.$ref), 86 + }, 1098 87 }); 1099 88 } else { 1100 - zodSchema.hasCircularReference = schema.circular; 89 + ast.hasCircularReference = schema.circular; 1101 90 } 1102 91 1103 92 const refSymbol = plugin.referenceSymbol(selector); 1104 - zodSchema.expression = tsc.identifier({ text: refSymbol.placeholder }); 93 + ast.expression = tsc.identifier({ text: refSymbol.placeholder }); 1105 94 } 1106 95 1107 96 state.circularReferenceTracker.pop(); 1108 97 state.currentReferenceTracker.pop(); 1109 98 } else if (schema.type) { 1110 - const zSchema = schemaTypeToZodSchema({ plugin, schema, state }); 1111 - zodSchema.expression = zSchema.expression; 1112 - zodSchema.hasCircularReference = zSchema.hasCircularReference; 99 + const zSchema = irSchemaWithTypeToAst({ 100 + plugin, 101 + schema: schema as SchemaWithType, 102 + state, 103 + }); 104 + ast.expression = zSchema.expression; 105 + ast.hasCircularReference = zSchema.hasCircularReference; 1113 106 1114 107 if (plugin.config.metadata && schema.description) { 1115 - zodSchema.expression = tsc.callExpression({ 108 + ast.expression = tsc.callExpression({ 1116 109 functionName: tsc.propertyAccessExpression({ 1117 - expression: zodSchema.expression, 110 + expression: ast.expression, 1118 111 name: identifiers.register, 1119 112 }), 1120 113 parameters: [ 1121 114 tsc.propertyAccessExpression({ 1122 - expression: zSymbol.placeholder, 115 + expression: z.placeholder, 1123 116 name: identifiers.globalRegistry, 1124 117 }), 1125 118 tsc.objectExpression({ ··· 1137 130 schema = deduplicateSchema({ schema }); 1138 131 1139 132 if (schema.items) { 1140 - const itemSchemas = schema.items.map((item) => 1141 - schemaToZodSchema({ 133 + const itemSchemas = schema.items.map((item, index) => 134 + irSchemaToAst({ 1142 135 plugin, 1143 136 schema: item, 1144 - state, 137 + state: { 138 + ...state, 139 + _path: [...state._path, 'items', index], 140 + }, 1145 141 }), 1146 142 ); 1147 143 ··· 1154 150 firstSchema.logicalOperator === 'or' || 1155 151 (firstSchema.type && firstSchema.type !== 'object') 1156 152 ) { 1157 - zodSchema.expression = tsc.callExpression({ 153 + ast.expression = tsc.callExpression({ 1158 154 functionName: tsc.propertyAccessExpression({ 1159 - expression: zSymbol.placeholder, 155 + expression: z.placeholder, 1160 156 name: identifiers.intersection, 1161 157 }), 1162 158 parameters: itemSchemas.map((schema) => schema.expression), 1163 159 }); 1164 160 } else { 1165 - zodSchema.expression = itemSchemas[0]!.expression; 161 + ast.expression = itemSchemas[0]!.expression; 1166 162 itemSchemas.slice(1).forEach((schema) => { 1167 - zodSchema.expression = tsc.callExpression({ 163 + ast.expression = tsc.callExpression({ 1168 164 functionName: tsc.propertyAccessExpression({ 1169 - expression: zSymbol.placeholder, 165 + expression: z.placeholder, 1170 166 name: identifiers.intersection, 1171 167 }), 1172 168 parameters: [ 1173 - zodSchema.expression, 169 + ast.expression, 1174 170 schema.hasCircularReference 1175 171 ? tsc.callExpression({ 1176 172 functionName: tsc.propertyAccessExpression({ 1177 - expression: zSymbol.placeholder, 173 + expression: z.placeholder, 1178 174 name: identifiers.lazy, 1179 175 }), 1180 176 parameters: [ ··· 1193 189 }); 1194 190 } 1195 191 } else { 1196 - zodSchema.expression = tsc.callExpression({ 192 + ast.expression = tsc.callExpression({ 1197 193 functionName: tsc.propertyAccessExpression({ 1198 - expression: zSymbol.placeholder, 194 + expression: z.placeholder, 1199 195 name: identifiers.union, 1200 196 }), 1201 197 parameters: [ ··· 1206 202 }); 1207 203 } 1208 204 } else { 1209 - zodSchema = schemaToZodSchema({ plugin, schema, state }); 205 + ast = irSchemaToAst({ plugin, schema, state }); 1210 206 } 1211 207 } else { 1212 208 // catch-all fallback for failed schemas 1213 - const zSchema = schemaTypeToZodSchema({ 209 + const zSchema = irSchemaWithTypeToAst({ 1214 210 plugin, 1215 211 schema: { 1216 212 type: 'unknown', 1217 213 }, 1218 214 state, 1219 215 }); 1220 - zodSchema.expression = zSchema.expression; 216 + ast.expression = zSchema.expression; 1221 217 } 1222 218 1223 - if (zodSchema.expression) { 219 + if (ast.expression) { 1224 220 if (schema.accessScope === 'read') { 1225 - zodSchema.expression = tsc.callExpression({ 221 + ast.expression = tsc.callExpression({ 1226 222 functionName: tsc.propertyAccessExpression({ 1227 - expression: zSymbol.placeholder, 223 + expression: z.placeholder, 1228 224 name: identifiers.readonly, 1229 225 }), 1230 - parameters: [zodSchema.expression], 226 + parameters: [ast.expression], 1231 227 }); 1232 228 } 1233 229 1234 230 if (optional) { 1235 - zodSchema.expression = tsc.callExpression({ 231 + ast.expression = tsc.callExpression({ 1236 232 functionName: tsc.propertyAccessExpression({ 1237 - expression: zSymbol.placeholder, 233 + expression: z.placeholder, 1238 234 name: identifiers.optional, 1239 235 }), 1240 - parameters: [zodSchema.expression], 236 + parameters: [ast.expression], 1241 237 }); 1242 - zodSchema.typeName = identifiers.ZodMiniOptional; 238 + ast.typeName = identifiers.ZodMiniOptional; 1243 239 } 1244 240 1245 241 if (schema.default !== undefined) { ··· 1249 245 value: schema.default, 1250 246 }); 1251 247 if (callParameter) { 1252 - zodSchema.expression = tsc.callExpression({ 248 + ast.expression = tsc.callExpression({ 1253 249 functionName: tsc.propertyAccessExpression({ 1254 - expression: zSymbol.placeholder, 250 + expression: z.placeholder, 1255 251 name: identifiers._default, 1256 252 }), 1257 - parameters: [zodSchema.expression, callParameter], 253 + parameters: [ast.expression, callParameter], 1258 254 }); 1259 255 } 1260 256 } 1261 257 } 1262 258 1263 - return zodSchema as ZodSchema; 259 + return ast as Ast; 1264 260 }; 1265 261 1266 262 const handleComponent = ({ ··· 1268 264 plugin, 1269 265 schema, 1270 266 state: _state, 1271 - }: { 267 + }: Omit<IrSchemaToAstOptions, 'state'> & { 1272 268 id: string; 1273 - plugin: ZodPlugin['Instance']; 1274 269 schema: IR.SchemaObject; 1275 - state?: Omit<State, 'currentReferenceTracker'>; 270 + state?: Partial<IrSchemaToAstOptions['state']>; 1276 271 }): void => { 1277 - const state: State = { 1278 - circularReferenceTracker: [id], 1279 - hasCircularReference: false, 1280 - ..._state, 1281 - currentReferenceTracker: [id], 272 + const state: IrSchemaToAstOptions['state'] = { 273 + _path: _state?._path ?? [], 274 + circularReferenceTracker: _state?.circularReferenceTracker ?? [id], 275 + currentReferenceTracker: _state?.currentReferenceTracker ?? [id], 276 + hasCircularReference: _state?.hasCircularReference ?? false, 1282 277 }; 1283 278 1284 279 const selector = plugin.api.getSelector('ref', id); 1285 280 let symbol = plugin.getSymbol(selector); 1286 281 if (symbol && !plugin.getSymbolValue(symbol)) return; 1287 282 1288 - const zodSchema = schemaToZodSchema({ plugin, schema, state }); 283 + const ast = irSchemaToAst({ plugin, schema, state }); 1289 284 const baseName = refToName(id); 285 + const resourceType = pathToSymbolResourceType(state._path); 1290 286 symbol = plugin.registerSymbol({ 1291 287 exported: true, 288 + meta: { 289 + resourceType, 290 + }, 1292 291 name: buildName({ 1293 292 config: plugin.config.definitions, 1294 293 name: baseName, ··· 1300 299 exported: true, 1301 300 meta: { 1302 301 kind: 'type', 302 + resourceType, 1303 303 }, 1304 304 name: buildName({ 1305 305 config: plugin.config.definitions.types.infer, ··· 1308 308 selector: plugin.api.getSelector('type-infer-ref', id), 1309 309 }) 1310 310 : undefined; 1311 - exportZodSchema({ 311 + exportAst({ 312 + ast, 1312 313 plugin, 1313 314 schema, 1314 315 symbol, 1315 316 typeInferSymbol, 1316 - zodSchema, 1317 317 }); 1318 318 }; 1319 319 ··· 1322 322 external: getZodModule({ plugin }), 1323 323 meta: { importKind: 'namespace' }, 1324 324 name: 'z', 1325 - selector: plugin.api.getSelector('import', 'zod'), 325 + selector: plugin.api.getSelector('external', 'zod.z'), 1326 326 }); 1327 327 1328 328 plugin.forEach( ··· 1334 334 (event) => { 1335 335 switch (event.type) { 1336 336 case 'operation': 1337 - operationToZodSchema({ 1338 - getZodSchema: (schema) => { 1339 - const state: State = { 337 + irOperationToAst({ 338 + getAst: (schema, path) => { 339 + const state: IrSchemaToAstOptions['state'] = { 340 + _path: path, 1340 341 circularReferenceTracker: [], 1341 342 currentReferenceTracker: [], 1342 343 hasCircularReference: false, 1343 344 }; 1344 - return schemaToZodSchema({ plugin, schema, state }); 345 + return irSchemaToAst({ plugin, schema, state }); 1345 346 }, 1346 347 operation: event.operation, 1347 348 plugin, 349 + state: { 350 + _path: event._path, 351 + }, 1348 352 }); 1349 353 break; 1350 354 case 'parameter': ··· 1352 356 id: event.$ref, 1353 357 plugin, 1354 358 schema: event.parameter.schema, 359 + state: { 360 + _path: event._path, 361 + }, 1355 362 }); 1356 363 break; 1357 364 case 'requestBody': ··· 1359 366 id: event.$ref, 1360 367 plugin, 1361 368 schema: event.requestBody.schema, 369 + state: { 370 + _path: event._path, 371 + }, 1362 372 }); 1363 373 break; 1364 374 case 'schema': ··· 1366 376 id: event.$ref, 1367 377 plugin, 1368 378 schema: event.schema, 379 + state: { 380 + _path: event._path, 381 + }, 1369 382 }); 1370 383 break; 1371 384 case 'webhook': 1372 - webhookToZodSchema({ 1373 - getZodSchema: (schema) => { 1374 - const state: State = { 385 + irWebhookToAst({ 386 + getAst: (schema, path) => { 387 + const state: IrSchemaToAstOptions['state'] = { 388 + _path: path, 1375 389 circularReferenceTracker: [], 1376 390 currentReferenceTracker: [], 1377 391 hasCircularReference: false, 1378 392 }; 1379 - return schemaToZodSchema({ plugin, schema, state }); 393 + return irSchemaToAst({ plugin, schema, state }); 1380 394 }, 1381 395 operation: event.operation, 1382 396 plugin, 397 + state: { 398 + _path: event._path, 399 + }, 1383 400 }); 1384 401 break; 1385 402 }
+172
packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { deduplicateSchema } from '../../../../ir/schema'; 4 + import { tsc } from '../../../../tsc'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import { identifiers } from '../../constants'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + import { irSchemaToAst } from '../plugin'; 9 + import { unknownToAst } from './unknown'; 10 + 11 + export const arrayToAst = ({ 12 + plugin, 13 + schema, 14 + state, 15 + }: IrSchemaToAstOptions & { 16 + schema: SchemaWithType<'array'>; 17 + }): Omit<Ast, 'typeName'> => { 18 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 19 + 20 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 21 + 22 + const functionName = tsc.propertyAccessExpression({ 23 + expression: z.placeholder, 24 + name: identifiers.array, 25 + }); 26 + 27 + if (!schema.items) { 28 + result.expression = tsc.callExpression({ 29 + functionName, 30 + parameters: [ 31 + unknownToAst({ 32 + plugin, 33 + schema: { 34 + type: 'unknown', 35 + }, 36 + state, 37 + }).expression, 38 + ], 39 + }); 40 + } else { 41 + schema = deduplicateSchema({ schema }); 42 + 43 + // at least one item is guaranteed 44 + const itemExpressions = schema.items!.map((item, index) => { 45 + const zodSchema = irSchemaToAst({ 46 + plugin, 47 + schema: item, 48 + state: { 49 + ...state, 50 + _path: [...state._path, 'items', index], 51 + }, 52 + }); 53 + if (zodSchema.hasCircularReference) { 54 + result.hasCircularReference = true; 55 + } 56 + return zodSchema.expression; 57 + }); 58 + 59 + if (itemExpressions.length === 1) { 60 + result.expression = tsc.callExpression({ 61 + functionName, 62 + parameters: itemExpressions, 63 + }); 64 + } else { 65 + if (schema.logicalOperator === 'and') { 66 + const firstSchema = schema.items![0]!; 67 + // we want to add an intersection, but not every schema can use the same API. 68 + // if the first item contains another array or not an object, we cannot use 69 + // `.intersection()` as that does not exist on `.union()` and non-object schemas. 70 + let intersectionExpression: ts.Expression; 71 + if ( 72 + firstSchema.logicalOperator === 'or' || 73 + (firstSchema.type && firstSchema.type !== 'object') 74 + ) { 75 + intersectionExpression = tsc.callExpression({ 76 + functionName: tsc.propertyAccessExpression({ 77 + expression: z.placeholder, 78 + name: identifiers.intersection, 79 + }), 80 + parameters: itemExpressions, 81 + }); 82 + } else { 83 + intersectionExpression = itemExpressions[0]!; 84 + for (let i = 1; i < itemExpressions.length; i++) { 85 + intersectionExpression = tsc.callExpression({ 86 + functionName: tsc.propertyAccessExpression({ 87 + expression: z.placeholder, 88 + name: identifiers.intersection, 89 + }), 90 + parameters: [intersectionExpression, itemExpressions[i]!], 91 + }); 92 + } 93 + } 94 + 95 + result.expression = tsc.callExpression({ 96 + functionName, 97 + parameters: [intersectionExpression], 98 + }); 99 + } else { 100 + result.expression = tsc.callExpression({ 101 + functionName: tsc.propertyAccessExpression({ 102 + expression: z.placeholder, 103 + name: identifiers.array, 104 + }), 105 + parameters: [ 106 + tsc.callExpression({ 107 + functionName: tsc.propertyAccessExpression({ 108 + expression: z.placeholder, 109 + name: identifiers.union, 110 + }), 111 + parameters: [ 112 + tsc.arrayLiteralExpression({ 113 + elements: itemExpressions, 114 + }), 115 + ], 116 + }), 117 + ], 118 + }); 119 + } 120 + } 121 + } 122 + 123 + const checks: Array<ts.Expression> = []; 124 + 125 + if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 126 + checks.push( 127 + tsc.callExpression({ 128 + functionName: tsc.propertyAccessExpression({ 129 + expression: z.placeholder, 130 + name: identifiers.length, 131 + }), 132 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 133 + }), 134 + ); 135 + } else { 136 + if (schema.minItems !== undefined) { 137 + checks.push( 138 + tsc.callExpression({ 139 + functionName: tsc.propertyAccessExpression({ 140 + expression: z.placeholder, 141 + name: identifiers.minLength, 142 + }), 143 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 144 + }), 145 + ); 146 + } 147 + 148 + if (schema.maxItems !== undefined) { 149 + checks.push( 150 + tsc.callExpression({ 151 + functionName: tsc.propertyAccessExpression({ 152 + expression: z.placeholder, 153 + name: identifiers.maxLength, 154 + }), 155 + parameters: [tsc.valueToExpression({ value: schema.maxItems })], 156 + }), 157 + ); 158 + } 159 + } 160 + 161 + if (checks.length) { 162 + result.expression = tsc.callExpression({ 163 + functionName: tsc.propertyAccessExpression({ 164 + expression: result.expression, 165 + name: identifiers.check, 166 + }), 167 + parameters: checks, 168 + }); 169 + } 170 + 171 + return result as Omit<Ast, 'typeName'>; 172 + };
+34
packages/openapi-ts/src/plugins/zod/mini/toAst/boolean.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const booleanToAst = ({ 7 + plugin, 8 + schema, 9 + }: IrSchemaToAstOptions & { 10 + schema: SchemaWithType<'boolean'>; 11 + }): Omit<Ast, 'typeName'> => { 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + 14 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 15 + 16 + if (typeof schema.const === 'boolean') { 17 + result.expression = tsc.callExpression({ 18 + functionName: tsc.propertyAccessExpression({ 19 + expression: z.placeholder, 20 + name: identifiers.literal, 21 + }), 22 + parameters: [tsc.ots.boolean(schema.const)], 23 + }); 24 + return result as Omit<Ast, 'typeName'>; 25 + } 26 + 27 + result.expression = tsc.callExpression({ 28 + functionName: tsc.propertyAccessExpression({ 29 + expression: z.placeholder, 30 + name: identifiers.boolean, 31 + }), 32 + }); 33 + return result as Omit<Ast, 'typeName'>; 34 + };
+127
packages/openapi-ts/src/plugins/zod/mini/toAst/enum.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import { unknownToAst } from './unknown'; 8 + 9 + export const enumToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'enum'>; 15 + }): Omit<Ast, 'typeName'> => { 16 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 17 + 18 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 19 + 20 + const enumMembers: Array<ts.LiteralExpression> = []; 21 + const literalMembers: Array<ts.CallExpression> = []; 22 + 23 + let isNullable = false; 24 + let allStrings = true; 25 + 26 + for (const item of schema.items ?? []) { 27 + // Zod supports string, number, and boolean enums 28 + if (item.type === 'string' && typeof item.const === 'string') { 29 + const stringLiteral = tsc.stringLiteral({ 30 + text: item.const, 31 + }); 32 + enumMembers.push(stringLiteral); 33 + literalMembers.push( 34 + tsc.callExpression({ 35 + functionName: tsc.propertyAccessExpression({ 36 + expression: z.placeholder, 37 + name: identifiers.literal, 38 + }), 39 + parameters: [stringLiteral], 40 + }), 41 + ); 42 + } else if ( 43 + (item.type === 'number' || item.type === 'integer') && 44 + typeof item.const === 'number' 45 + ) { 46 + allStrings = false; 47 + const numberLiteral = tsc.ots.number(item.const); 48 + literalMembers.push( 49 + tsc.callExpression({ 50 + functionName: tsc.propertyAccessExpression({ 51 + expression: z.placeholder, 52 + name: identifiers.literal, 53 + }), 54 + parameters: [numberLiteral], 55 + }), 56 + ); 57 + } else if (item.type === 'boolean' && typeof item.const === 'boolean') { 58 + allStrings = false; 59 + const booleanLiteral = tsc.ots.boolean(item.const); 60 + literalMembers.push( 61 + tsc.callExpression({ 62 + functionName: tsc.propertyAccessExpression({ 63 + expression: z.placeholder, 64 + name: identifiers.literal, 65 + }), 66 + parameters: [booleanLiteral], 67 + }), 68 + ); 69 + } else if (item.type === 'null' || item.const === null) { 70 + isNullable = true; 71 + } 72 + } 73 + 74 + if (!literalMembers.length) { 75 + return unknownToAst({ 76 + plugin, 77 + schema: { 78 + type: 'unknown', 79 + }, 80 + state, 81 + }); 82 + } 83 + 84 + // Use z.enum() for pure string enums, z.union() for mixed or non-string types 85 + if (allStrings && enumMembers.length > 0) { 86 + result.expression = tsc.callExpression({ 87 + functionName: tsc.propertyAccessExpression({ 88 + expression: z.placeholder, 89 + name: identifiers.enum, 90 + }), 91 + parameters: [ 92 + tsc.arrayLiteralExpression({ 93 + elements: enumMembers, 94 + multiLine: false, 95 + }), 96 + ], 97 + }); 98 + } else if (literalMembers.length === 1) { 99 + // For single-member unions, use the member directly instead of wrapping in z.union() 100 + result.expression = literalMembers[0]; 101 + } else { 102 + result.expression = tsc.callExpression({ 103 + functionName: tsc.propertyAccessExpression({ 104 + expression: z.placeholder, 105 + name: identifiers.union, 106 + }), 107 + parameters: [ 108 + tsc.arrayLiteralExpression({ 109 + elements: literalMembers, 110 + multiLine: literalMembers.length > 3, 111 + }), 112 + ], 113 + }); 114 + } 115 + 116 + if (isNullable) { 117 + result.expression = tsc.callExpression({ 118 + functionName: tsc.propertyAccessExpression({ 119 + expression: z.placeholder, 120 + name: identifiers.nullable, 121 + }), 122 + parameters: [result.expression], 123 + }); 124 + } 125 + 126 + return result as Omit<Ast, 'typeName'>; 127 + };
+85
packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts
··· 1 + import type { SchemaWithType } from '../../../shared/types/schema'; 2 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 3 + import { arrayToAst } from './array'; 4 + import { booleanToAst } from './boolean'; 5 + import { enumToAst } from './enum'; 6 + import { neverToAst } from './never'; 7 + import { nullToAst } from './null'; 8 + import { numberToAst } from './number'; 9 + import { objectToAst } from './object'; 10 + import { stringToAst } from './string'; 11 + import { tupleToAst } from './tuple'; 12 + import { undefinedToAst } from './undefined'; 13 + import { unknownToAst } from './unknown'; 14 + import { voidToAst } from './void'; 15 + 16 + export const irSchemaWithTypeToAst = ({ 17 + schema, 18 + ...args 19 + }: IrSchemaToAstOptions & { 20 + schema: SchemaWithType; 21 + }): Omit<Ast, 'typeName'> => { 22 + switch (schema.type) { 23 + case 'array': 24 + return arrayToAst({ 25 + ...args, 26 + schema: schema as SchemaWithType<'array'>, 27 + }); 28 + case 'boolean': 29 + return booleanToAst({ 30 + ...args, 31 + schema: schema as SchemaWithType<'boolean'>, 32 + }); 33 + case 'enum': 34 + return enumToAst({ 35 + ...args, 36 + schema: schema as SchemaWithType<'enum'>, 37 + }); 38 + case 'integer': 39 + case 'number': 40 + return numberToAst({ 41 + ...args, 42 + schema: schema as SchemaWithType<'integer' | 'number'>, 43 + }); 44 + case 'never': 45 + return neverToAst({ 46 + ...args, 47 + schema: schema as SchemaWithType<'never'>, 48 + }); 49 + case 'null': 50 + return nullToAst({ 51 + ...args, 52 + schema: schema as SchemaWithType<'null'>, 53 + }); 54 + case 'object': 55 + return objectToAst({ 56 + ...args, 57 + schema: schema as SchemaWithType<'object'>, 58 + }); 59 + case 'string': 60 + return stringToAst({ 61 + ...args, 62 + schema: schema as SchemaWithType<'string'>, 63 + }); 64 + case 'tuple': 65 + return tupleToAst({ 66 + ...args, 67 + schema: schema as SchemaWithType<'tuple'>, 68 + }); 69 + case 'undefined': 70 + return undefinedToAst({ 71 + ...args, 72 + schema: schema as SchemaWithType<'undefined'>, 73 + }); 74 + case 'unknown': 75 + return unknownToAst({ 76 + ...args, 77 + schema: schema as SchemaWithType<'unknown'>, 78 + }); 79 + case 'void': 80 + return voidToAst({ 81 + ...args, 82 + schema: schema as SchemaWithType<'void'>, 83 + }); 84 + } 85 + };
+20
packages/openapi-ts/src/plugins/zod/mini/toAst/never.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const neverToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'never'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.never, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+20
packages/openapi-ts/src/plugins/zod/mini/toAst/null.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const nullToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'null'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.null, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+118
packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import { numberParameter } from '../../shared/numbers'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + 9 + export const numberToAst = ({ 10 + plugin, 11 + schema, 12 + }: IrSchemaToAstOptions & { 13 + schema: SchemaWithType<'integer' | 'number'>; 14 + }): Omit<Ast, 'typeName'> => { 15 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 16 + 17 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 18 + 19 + const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 20 + 21 + if (typeof schema.const === 'number') { 22 + // TODO: parser - handle bigint constants 23 + result.expression = tsc.callExpression({ 24 + functionName: tsc.propertyAccessExpression({ 25 + expression: z.placeholder, 26 + name: identifiers.literal, 27 + }), 28 + parameters: [tsc.ots.number(schema.const)], 29 + }); 30 + return result as Omit<Ast, 'typeName'>; 31 + } 32 + 33 + result.expression = tsc.callExpression({ 34 + functionName: isBigInt 35 + ? tsc.propertyAccessExpression({ 36 + expression: tsc.propertyAccessExpression({ 37 + expression: z.placeholder, 38 + name: identifiers.coerce, 39 + }), 40 + name: identifiers.bigint, 41 + }) 42 + : tsc.propertyAccessExpression({ 43 + expression: z.placeholder, 44 + name: identifiers.number, 45 + }), 46 + }); 47 + 48 + if (!isBigInt && schema.type === 'integer') { 49 + result.expression = tsc.callExpression({ 50 + functionName: tsc.propertyAccessExpression({ 51 + expression: z.placeholder, 52 + name: identifiers.int, 53 + }), 54 + }); 55 + } 56 + 57 + const checks: Array<ts.Expression> = []; 58 + 59 + if (schema.exclusiveMinimum !== undefined) { 60 + checks.push( 61 + tsc.callExpression({ 62 + functionName: tsc.propertyAccessExpression({ 63 + expression: z.placeholder, 64 + name: identifiers.gt, 65 + }), 66 + parameters: [ 67 + numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 68 + ], 69 + }), 70 + ); 71 + } else if (schema.minimum !== undefined) { 72 + checks.push( 73 + tsc.callExpression({ 74 + functionName: tsc.propertyAccessExpression({ 75 + expression: z.placeholder, 76 + name: identifiers.gte, 77 + }), 78 + parameters: [numberParameter({ isBigInt, value: schema.minimum })], 79 + }), 80 + ); 81 + } 82 + 83 + if (schema.exclusiveMaximum !== undefined) { 84 + checks.push( 85 + tsc.callExpression({ 86 + functionName: tsc.propertyAccessExpression({ 87 + expression: z.placeholder, 88 + name: identifiers.lt, 89 + }), 90 + parameters: [ 91 + numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 92 + ], 93 + }), 94 + ); 95 + } else if (schema.maximum !== undefined) { 96 + checks.push( 97 + tsc.callExpression({ 98 + functionName: tsc.propertyAccessExpression({ 99 + expression: z.placeholder, 100 + name: identifiers.lte, 101 + }), 102 + parameters: [numberParameter({ isBigInt, value: schema.maximum })], 103 + }), 104 + ); 105 + } 106 + 107 + if (checks.length) { 108 + result.expression = tsc.callExpression({ 109 + functionName: tsc.propertyAccessExpression({ 110 + expression: result.expression, 111 + name: identifiers.check, 112 + }), 113 + parameters: checks, 114 + }); 115 + } 116 + 117 + return result as Omit<Ast, 'typeName'>; 118 + };
+135
packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import { numberRegExp } from '../../../../utils/regexp'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import { identifiers } from '../../constants'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + import { irSchemaToAst } from '../plugin'; 9 + 10 + export const objectToAst = ({ 11 + plugin, 12 + schema, 13 + state, 14 + }: IrSchemaToAstOptions & { 15 + schema: SchemaWithType<'object'>; 16 + }): Omit<Ast, 'typeName'> => { 17 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 18 + 19 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 20 + 21 + // TODO: parser - handle constants 22 + const properties: Array<ts.PropertyAssignment | ts.GetAccessorDeclaration> = 23 + []; 24 + 25 + const required = schema.required ?? []; 26 + 27 + for (const name in schema.properties) { 28 + const property = schema.properties[name]!; 29 + const isRequired = required.includes(name); 30 + 31 + const propertySchema = irSchemaToAst({ 32 + optional: !isRequired, 33 + plugin, 34 + schema: property, 35 + state: { 36 + ...state, 37 + _path: [...state._path, 'properties', name], 38 + }, 39 + }); 40 + if (propertySchema.hasCircularReference) { 41 + result.hasCircularReference = true; 42 + } 43 + 44 + numberRegExp.lastIndex = 0; 45 + let propertyName; 46 + if (numberRegExp.test(name)) { 47 + // For numeric literals, we'll handle negative numbers by using a string literal 48 + // instead of trying to use a PrefixUnaryExpression 49 + propertyName = name.startsWith('-') 50 + ? ts.factory.createStringLiteral(name) 51 + : ts.factory.createNumericLiteral(name); 52 + } else { 53 + propertyName = name; 54 + } 55 + // TODO: parser - abstract safe property name logic 56 + if ( 57 + ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 58 + !name.startsWith("'") && 59 + !name.endsWith("'") 60 + ) { 61 + propertyName = `'${name}'`; 62 + } 63 + 64 + if (propertySchema.hasCircularReference) { 65 + properties.push( 66 + tsc.getAccessorDeclaration({ 67 + name: propertyName, 68 + // @ts-expect-error 69 + returnType: propertySchema.typeName 70 + ? tsc.propertyAccessExpression({ 71 + expression: z.placeholder, 72 + name: propertySchema.typeName, 73 + }) 74 + : undefined, 75 + statements: [ 76 + tsc.returnStatement({ 77 + expression: propertySchema.expression, 78 + }), 79 + ], 80 + }), 81 + ); 82 + } else { 83 + properties.push( 84 + tsc.propertyAssignment({ 85 + initializer: propertySchema.expression, 86 + name: propertyName, 87 + }), 88 + ); 89 + } 90 + } 91 + 92 + if ( 93 + schema.additionalProperties && 94 + (!schema.properties || !Object.keys(schema.properties).length) 95 + ) { 96 + const zodSchema = irSchemaToAst({ 97 + plugin, 98 + schema: schema.additionalProperties, 99 + state: { 100 + ...state, 101 + _path: [...state._path, 'additionalProperties'], 102 + }, 103 + }); 104 + result.expression = tsc.callExpression({ 105 + functionName: tsc.propertyAccessExpression({ 106 + expression: z.placeholder, 107 + name: identifiers.record, 108 + }), 109 + parameters: [ 110 + tsc.callExpression({ 111 + functionName: tsc.propertyAccessExpression({ 112 + expression: z.placeholder, 113 + name: identifiers.string, 114 + }), 115 + parameters: [], 116 + }), 117 + zodSchema.expression, 118 + ], 119 + }); 120 + if (zodSchema.hasCircularReference) { 121 + result.hasCircularReference = true; 122 + } 123 + return result as Omit<Ast, 'typeName'>; 124 + } 125 + 126 + result.expression = tsc.callExpression({ 127 + functionName: tsc.propertyAccessExpression({ 128 + expression: z.placeholder, 129 + name: identifiers.object, 130 + }), 131 + parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 132 + }); 133 + 134 + return result as Omit<Ast, 'typeName'>; 135 + };
+192
packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + 8 + export const stringToAst = ({ 9 + plugin, 10 + schema, 11 + }: IrSchemaToAstOptions & { 12 + schema: SchemaWithType<'string'>; 13 + }): Omit<Ast, 'typeName'> => { 14 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 15 + 16 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 17 + 18 + if (typeof schema.const === 'string') { 19 + result.expression = tsc.callExpression({ 20 + functionName: tsc.propertyAccessExpression({ 21 + expression: z.placeholder, 22 + name: identifiers.literal, 23 + }), 24 + parameters: [tsc.ots.string(schema.const)], 25 + }); 26 + return result as Omit<Ast, 'typeName'>; 27 + } 28 + 29 + result.expression = tsc.callExpression({ 30 + functionName: tsc.propertyAccessExpression({ 31 + expression: z.placeholder, 32 + name: identifiers.string, 33 + }), 34 + }); 35 + 36 + const dateTimeOptions: { key: string; value: boolean }[] = []; 37 + 38 + if (plugin.config.dates.offset) { 39 + dateTimeOptions.push({ key: 'offset', value: true }); 40 + } 41 + if (plugin.config.dates.local) { 42 + dateTimeOptions.push({ key: 'local', value: true }); 43 + } 44 + 45 + if (schema.format) { 46 + switch (schema.format) { 47 + case 'date': 48 + result.expression = tsc.callExpression({ 49 + functionName: tsc.propertyAccessExpression({ 50 + expression: tsc.propertyAccessExpression({ 51 + expression: z.placeholder, 52 + name: identifiers.iso, 53 + }), 54 + name: identifiers.date, 55 + }), 56 + }); 57 + break; 58 + case 'date-time': 59 + result.expression = tsc.callExpression({ 60 + functionName: tsc.propertyAccessExpression({ 61 + expression: tsc.propertyAccessExpression({ 62 + expression: z.placeholder, 63 + name: identifiers.iso, 64 + }), 65 + name: identifiers.datetime, 66 + }), 67 + parameters: 68 + dateTimeOptions.length > 0 69 + ? [ 70 + tsc.objectExpression({ 71 + obj: dateTimeOptions, 72 + }), 73 + ] 74 + : [], 75 + }); 76 + break; 77 + case 'email': 78 + result.expression = tsc.callExpression({ 79 + functionName: tsc.propertyAccessExpression({ 80 + expression: z.placeholder, 81 + name: identifiers.email, 82 + }), 83 + }); 84 + break; 85 + case 'ipv4': 86 + result.expression = tsc.callExpression({ 87 + functionName: tsc.propertyAccessExpression({ 88 + expression: z.placeholder, 89 + name: identifiers.ipv4, 90 + }), 91 + }); 92 + break; 93 + case 'ipv6': 94 + result.expression = tsc.callExpression({ 95 + functionName: tsc.propertyAccessExpression({ 96 + expression: z.placeholder, 97 + name: identifiers.ipv6, 98 + }), 99 + }); 100 + break; 101 + case 'time': 102 + result.expression = tsc.callExpression({ 103 + functionName: tsc.propertyAccessExpression({ 104 + expression: tsc.propertyAccessExpression({ 105 + expression: z.placeholder, 106 + name: identifiers.iso, 107 + }), 108 + name: identifiers.time, 109 + }), 110 + }); 111 + break; 112 + case 'uri': 113 + result.expression = tsc.callExpression({ 114 + functionName: tsc.propertyAccessExpression({ 115 + expression: z.placeholder, 116 + name: identifiers.url, 117 + }), 118 + }); 119 + break; 120 + case 'uuid': 121 + result.expression = tsc.callExpression({ 122 + functionName: tsc.propertyAccessExpression({ 123 + expression: z.placeholder, 124 + name: identifiers.uuid, 125 + }), 126 + }); 127 + break; 128 + } 129 + } 130 + 131 + const checks: Array<ts.Expression> = []; 132 + 133 + if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 134 + checks.push( 135 + tsc.callExpression({ 136 + functionName: tsc.propertyAccessExpression({ 137 + expression: z.placeholder, 138 + name: identifiers.length, 139 + }), 140 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 141 + }), 142 + ); 143 + } else { 144 + if (schema.minLength !== undefined) { 145 + checks.push( 146 + tsc.callExpression({ 147 + functionName: tsc.propertyAccessExpression({ 148 + expression: z.placeholder, 149 + name: identifiers.minLength, 150 + }), 151 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 152 + }), 153 + ); 154 + } 155 + 156 + if (schema.maxLength !== undefined) { 157 + checks.push( 158 + tsc.callExpression({ 159 + functionName: tsc.propertyAccessExpression({ 160 + expression: z.placeholder, 161 + name: identifiers.maxLength, 162 + }), 163 + parameters: [tsc.valueToExpression({ value: schema.maxLength })], 164 + }), 165 + ); 166 + } 167 + } 168 + 169 + if (schema.pattern) { 170 + checks.push( 171 + tsc.callExpression({ 172 + functionName: tsc.propertyAccessExpression({ 173 + expression: z.placeholder, 174 + name: identifiers.regex, 175 + }), 176 + parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 177 + }), 178 + ); 179 + } 180 + 181 + if (checks.length) { 182 + result.expression = tsc.callExpression({ 183 + functionName: tsc.propertyAccessExpression({ 184 + expression: result.expression, 185 + name: identifiers.check, 186 + }), 187 + parameters: checks, 188 + }); 189 + } 190 + 191 + return result as Omit<Ast, 'typeName'>; 192 + };
+76
packages/openapi-ts/src/plugins/zod/mini/toAst/tuple.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import { irSchemaToAst } from '../plugin'; 8 + 9 + export const tupleToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'tuple'>; 15 + }): Omit<Ast, 'typeName'> => { 16 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 17 + 18 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 19 + 20 + if (schema.const && Array.isArray(schema.const)) { 21 + const tupleElements = schema.const.map((value) => 22 + tsc.callExpression({ 23 + functionName: tsc.propertyAccessExpression({ 24 + expression: z.placeholder, 25 + name: identifiers.literal, 26 + }), 27 + parameters: [tsc.valueToExpression({ value })], 28 + }), 29 + ); 30 + result.expression = tsc.callExpression({ 31 + functionName: tsc.propertyAccessExpression({ 32 + expression: z.placeholder, 33 + name: identifiers.tuple, 34 + }), 35 + parameters: [ 36 + tsc.arrayLiteralExpression({ 37 + elements: tupleElements, 38 + }), 39 + ], 40 + }); 41 + return result as Omit<Ast, 'typeName'>; 42 + } 43 + 44 + const tupleElements: Array<ts.Expression> = []; 45 + 46 + if (schema.items) { 47 + schema.items.forEach((item, index) => { 48 + const itemSchema = irSchemaToAst({ 49 + plugin, 50 + schema: item, 51 + state: { 52 + ...state, 53 + _path: [...state._path, 'items', index], 54 + }, 55 + }); 56 + tupleElements.push(itemSchema.expression); 57 + if (itemSchema.hasCircularReference) { 58 + result.hasCircularReference = true; 59 + } 60 + }); 61 + } 62 + 63 + result.expression = tsc.callExpression({ 64 + functionName: tsc.propertyAccessExpression({ 65 + expression: z.placeholder, 66 + name: identifiers.tuple, 67 + }), 68 + parameters: [ 69 + tsc.arrayLiteralExpression({ 70 + elements: tupleElements, 71 + }), 72 + ], 73 + }); 74 + 75 + return result as Omit<Ast, 'typeName'>; 76 + };
+20
packages/openapi-ts/src/plugins/zod/mini/toAst/undefined.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const undefinedToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'undefined'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.undefined, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+20
packages/openapi-ts/src/plugins/zod/mini/toAst/unknown.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const unknownToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'unknown'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.unknown, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+20
packages/openapi-ts/src/plugins/zod/mini/toAst/void.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const voidToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'void'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.void, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+1 -2
packages/openapi-ts/src/plugins/zod/plugin.ts
··· 8 8 switch (plugin.config.compatibilityVersion) { 9 9 case 3: 10 10 return handlerV3(args); 11 - case 4: 12 - return handlerV4(args); 13 11 case 'mini': 14 12 return handlerMini(args); 13 + case 4: 15 14 default: 16 15 return handlerV4(args); 17 16 }
+28
packages/openapi-ts/src/plugins/zod/shared/numbers.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + 5 + export const numberParameter = ({ 6 + isBigInt, 7 + value, 8 + }: { 9 + isBigInt: boolean; 10 + value: unknown; 11 + }): ts.Expression | undefined => { 12 + const expression = tsc.valueToExpression({ value }); 13 + 14 + if ( 15 + isBigInt && 16 + (typeof value === 'bigint' || 17 + typeof value === 'number' || 18 + typeof value === 'string' || 19 + typeof value === 'boolean') 20 + ) { 21 + return tsc.callExpression({ 22 + functionName: 'BigInt', 23 + parameters: [expression], 24 + }); 25 + } 26 + 27 + return expression; 28 + };
+31 -15
packages/openapi-ts/src/plugins/zod/shared/operation.ts
··· 1 1 import { operationResponsesMap } from '../../../ir/operation'; 2 2 import type { IR } from '../../../ir/types'; 3 3 import { buildName } from '../../../openApi/shared/utils/name'; 4 - import { exportZodSchema } from '../export'; 5 - import type { ZodPlugin } from '../types'; 6 - import type { ZodSchema } from './types'; 4 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 5 + import { exportAst } from './export'; 6 + import type { Ast, IrSchemaToAstOptions } from './types'; 7 7 8 - export const operationToZodSchema = ({ 9 - getZodSchema, 8 + export const irOperationToAst = ({ 9 + getAst, 10 10 operation, 11 11 plugin, 12 - }: { 13 - getZodSchema: (schema: IR.SchemaObject) => ZodSchema; 12 + state, 13 + }: Omit<IrSchemaToAstOptions, 'state'> & { 14 + getAst: ( 15 + schema: IR.SchemaObject, 16 + path: ReadonlyArray<string | number>, 17 + ) => Ast; 14 18 operation: IR.OperationObject; 15 - plugin: ZodPlugin['Instance']; 16 - }) => { 19 + state: Partial<IrSchemaToAstOptions['state']>; 20 + }): void => { 17 21 if (plugin.config.requests.enabled) { 18 22 const requiredProperties = new Set<string>(); 19 23 ··· 112 116 113 117 schemaData.required = [...requiredProperties]; 114 118 115 - const zodSchema = getZodSchema(schemaData); 119 + const path = state._path || []; 120 + const ast = getAst(schemaData, path); 121 + const resourceType = pathToSymbolResourceType(path); 116 122 const symbol = plugin.registerSymbol({ 117 123 exported: true, 124 + meta: { 125 + resourceType, 126 + }, 118 127 name: buildName({ 119 128 config: plugin.config.requests, 120 129 name: operation.id, ··· 126 135 exported: true, 127 136 meta: { 128 137 kind: 'type', 138 + resourceType, 129 139 }, 130 140 name: buildName({ 131 141 config: plugin.config.requests.types.infer, ··· 134 144 selector: plugin.api.getSelector('type-infer-data', operation.id), 135 145 }) 136 146 : undefined; 137 - exportZodSchema({ 147 + exportAst({ 148 + ast, 138 149 plugin, 139 150 schema: schemaData, 140 151 symbol, 141 152 typeInferSymbol, 142 - zodSchema, 143 153 }); 144 154 } 145 155 ··· 148 158 const { response } = operationResponsesMap(operation); 149 159 150 160 if (response) { 151 - const zodSchema = getZodSchema(response); 161 + const path = [...(state._path || []), 'responses']; 162 + const ast = getAst(response, path); 163 + const resourceType = pathToSymbolResourceType(path); 152 164 const symbol = plugin.registerSymbol({ 153 165 exported: true, 166 + meta: { 167 + resourceType, 168 + }, 154 169 name: buildName({ 155 170 config: plugin.config.responses, 156 171 name: operation.id, ··· 162 177 exported: true, 163 178 meta: { 164 179 kind: 'type', 180 + resourceType, 165 181 }, 166 182 name: buildName({ 167 183 config: plugin.config.responses.types.infer, ··· 173 189 ), 174 190 }) 175 191 : undefined; 176 - exportZodSchema({ 192 + exportAst({ 193 + ast, 177 194 plugin, 178 195 schema: response, 179 196 symbol, 180 197 typeInferSymbol, 181 - zodSchema, 182 198 }); 183 199 } 184 200 }
+18 -8
packages/openapi-ts/src/plugins/zod/shared/types.d.ts
··· 1 1 import type ts from 'typescript'; 2 2 3 3 import type { IR } from '../../../ir/types'; 4 + import type { ZodPlugin } from '../types'; 4 5 5 - export interface SchemaWithType<T extends Required<IR.SchemaObject>['type']> 6 - extends Omit<IR.SchemaObject, 'type'> { 7 - type: Extract<Required<IR.SchemaObject>['type'], T>; 8 - } 6 + export type Ast = { 7 + expression: ts.Expression; 8 + hasCircularReference?: boolean; 9 + typeName?: string | ts.Identifier; 10 + }; 11 + 12 + export type IrSchemaToAstOptions = { 13 + plugin: ZodPlugin['Instance']; 14 + state: State; 15 + }; 9 16 10 17 export type State = { 18 + /** 19 + * Path to the schema in the intermediary representation. 20 + */ 21 + _path: ReadonlyArray<string | number>; 11 22 circularReferenceTracker: Array<string>; 12 23 /** 13 24 * Works the same as `circularReferenceTracker`, but it resets whenever we ··· 18 29 hasCircularReference: boolean; 19 30 }; 20 31 21 - export type ZodSchema = { 22 - expression: ts.Expression; 23 - hasCircularReference?: boolean; 24 - typeName?: string | ts.Identifier; 32 + export type ValidatorArgs = { 33 + operation: IR.OperationObject; 34 + plugin: ZodPlugin['Instance']; 25 35 };
+21 -11
packages/openapi-ts/src/plugins/zod/shared/webhook.ts
··· 1 1 import type { IR } from '../../../ir/types'; 2 2 import { buildName } from '../../../openApi/shared/utils/name'; 3 - import { exportZodSchema } from '../export'; 4 - import type { ZodPlugin } from '../types'; 5 - import type { ZodSchema } from './types'; 3 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 4 + import { exportAst } from './export'; 5 + import type { Ast, IrSchemaToAstOptions } from './types'; 6 6 7 - export const webhookToZodSchema = ({ 8 - getZodSchema, 7 + export const irWebhookToAst = ({ 8 + getAst, 9 9 operation, 10 10 plugin, 11 - }: { 12 - getZodSchema: (schema: IR.SchemaObject) => ZodSchema; 11 + state, 12 + }: Omit<IrSchemaToAstOptions, 'state'> & { 13 + getAst: ( 14 + schema: IR.SchemaObject, 15 + path: ReadonlyArray<string | number>, 16 + ) => Ast; 13 17 operation: IR.OperationObject; 14 - plugin: ZodPlugin['Instance']; 18 + state: Partial<IrSchemaToAstOptions['state']>; 15 19 }) => { 16 20 if (plugin.config.webhooks.enabled) { 17 21 const requiredProperties = new Set<string>(); ··· 111 115 112 116 schemaData.required = [...requiredProperties]; 113 117 114 - const zodSchema = getZodSchema(schemaData); 118 + const path = state._path || []; 119 + const ast = getAst(schemaData, path); 120 + const resourceType = pathToSymbolResourceType(path); 115 121 const symbol = plugin.registerSymbol({ 116 122 exported: true, 123 + meta: { 124 + resourceType, 125 + }, 117 126 name: buildName({ 118 127 config: plugin.config.webhooks, 119 128 name: operation.id, ··· 125 134 exported: true, 126 135 meta: { 127 136 kind: 'type', 137 + resourceType, 128 138 }, 129 139 name: buildName({ 130 140 config: plugin.config.webhooks.types.infer, ··· 136 146 ), 137 147 }) 138 148 : undefined; 139 - exportZodSchema({ 149 + exportAst({ 150 + ast, 140 151 plugin, 141 152 schema: schemaData, 142 153 symbol, 143 154 typeInferSymbol, 144 - zodSchema, 145 155 }); 146 156 } 147 157 };
+10 -10
packages/openapi-ts/src/plugins/zod/types.d.ts
··· 96 96 */ 97 97 types?: { 98 98 /** 99 - * Configuration for `z.infer` types. 99 + * Configuration for `infer` types. 100 100 * 101 101 * Can be: 102 102 * - `boolean`: Shorthand for `{ enabled: boolean }` ··· 189 189 */ 190 190 types?: { 191 191 /** 192 - * Configuration for `z.infer` types. 192 + * Configuration for `infer` types. 193 193 * 194 194 * Can be: 195 195 * - `boolean`: Shorthand for `{ enabled: boolean }` ··· 267 267 */ 268 268 types?: { 269 269 /** 270 - * Configuration for `z.infer` types. 270 + * Configuration for `infer` types. 271 271 * 272 272 * Can be: 273 273 * - `boolean`: Shorthand for `{ enabled: boolean }` ··· 309 309 */ 310 310 types?: { 311 311 /** 312 - * Configuration for `z.infer` types. 312 + * Configuration for `infer` types. 313 313 * 314 314 * Can be: 315 315 * - `boolean`: Shorthand for `{ enabled: boolean }` ··· 378 378 */ 379 379 types?: { 380 380 /** 381 - * Configuration for `z.infer` types. 381 + * Configuration for `infer` types. 382 382 * 383 383 * Can be: 384 384 * - `boolean`: Shorthand for `{ enabled: boolean }` ··· 499 499 */ 500 500 types: { 501 501 /** 502 - * Configuration for `z.infer` types. 502 + * Configuration for `infer` types. 503 503 */ 504 504 infer: { 505 505 /** ··· 572 572 */ 573 573 types: { 574 574 /** 575 - * Configuration for `z.infer` types. 575 + * Configuration for `infer` types. 576 576 */ 577 577 infer: { 578 578 /** ··· 630 630 */ 631 631 types: { 632 632 /** 633 - * Configuration for `z.infer` types. 633 + * Configuration for `infer` types. 634 634 */ 635 635 infer: { 636 636 /** ··· 662 662 */ 663 663 types: { 664 664 /** 665 - * Configuration for `z.infer` types. 665 + * Configuration for `infer` types. 666 666 */ 667 667 infer: { 668 668 /** ··· 711 711 */ 712 712 types: { 713 713 /** 714 - * Configuration for `z.infer` types. 714 + * Configuration for `infer` types. 715 715 */ 716 716 infer: { 717 717 /**
+71
packages/openapi-ts/src/plugins/zod/v3/api.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + import { identifiers } from '../constants'; 5 + import type { ValidatorArgs } from '../shared/types'; 6 + 7 + export const createRequestValidatorV3 = ({ 8 + operation, 9 + plugin, 10 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 11 + const symbol = plugin.getSymbol(plugin.api.getSelector('data', operation.id)); 12 + if (!symbol) return; 13 + 14 + const dataParameterName = 'data'; 15 + 16 + return tsc.arrowFunction({ 17 + async: true, 18 + parameters: [ 19 + { 20 + name: dataParameterName, 21 + }, 22 + ], 23 + statements: [ 24 + tsc.returnStatement({ 25 + expression: tsc.awaitExpression({ 26 + expression: tsc.callExpression({ 27 + functionName: tsc.propertyAccessExpression({ 28 + expression: symbol.placeholder, 29 + name: identifiers.parseAsync, 30 + }), 31 + parameters: [tsc.identifier({ text: dataParameterName })], 32 + }), 33 + }), 34 + }), 35 + ], 36 + }); 37 + }; 38 + 39 + export const createResponseValidatorV3 = ({ 40 + operation, 41 + plugin, 42 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 43 + const symbol = plugin.getSymbol( 44 + plugin.api.getSelector('responses', operation.id), 45 + ); 46 + if (!symbol) return; 47 + 48 + const dataParameterName = 'data'; 49 + 50 + return tsc.arrowFunction({ 51 + async: true, 52 + parameters: [ 53 + { 54 + name: dataParameterName, 55 + }, 56 + ], 57 + statements: [ 58 + tsc.returnStatement({ 59 + expression: tsc.awaitExpression({ 60 + expression: tsc.callExpression({ 61 + functionName: tsc.propertyAccessExpression({ 62 + expression: symbol.placeholder, 63 + name: identifiers.parseAsync, 64 + }), 65 + parameters: [tsc.identifier({ text: dataParameterName })], 66 + }), 67 + }), 68 + }), 69 + ], 70 + }); 71 + };
+105 -1012
packages/openapi-ts/src/plugins/zod/v3/plugin.ts
··· 1 - import ts from 'typescript'; 2 - 3 1 import { deduplicateSchema } from '../../../ir/schema'; 4 2 import type { IR } from '../../../ir/types'; 5 3 import { buildName } from '../../../openApi/shared/utils/name'; 6 4 import { tsc } from '../../../tsc'; 7 - import { refToName } from '../../../utils/ref'; 8 - import { numberRegExp } from '../../../utils/regexp'; 5 + import { jsonPointerToPath, refToName } from '../../../utils/ref'; 6 + import type { SchemaWithType } from '../../shared/types/schema'; 7 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 9 8 import { identifiers } from '../constants'; 10 - import { exportZodSchema } from '../export'; 9 + import { exportAst } from '../shared/export'; 11 10 import { getZodModule } from '../shared/module'; 12 - import { operationToZodSchema } from '../shared/operation'; 13 - import type { SchemaWithType, State, ZodSchema } from '../shared/types'; 14 - import { webhookToZodSchema } from '../shared/webhook'; 11 + import { numberParameter } from '../shared/numbers'; 12 + import { irOperationToAst } from '../shared/operation'; 13 + import type { Ast, IrSchemaToAstOptions } from '../shared/types'; 14 + import { irWebhookToAst } from '../shared/webhook'; 15 15 import type { ZodPlugin } from '../types'; 16 - 17 - const arrayTypeToZodSchema = ({ 18 - plugin, 19 - schema, 20 - state, 21 - }: { 22 - plugin: ZodPlugin['Instance']; 23 - schema: SchemaWithType<'array'>; 24 - state: State; 25 - }): Omit<ZodSchema, 'typeName'> & { 26 - anyType?: string; 27 - } => { 28 - const zSymbol = plugin.referenceSymbol( 29 - plugin.api.getSelector('import', 'zod'), 30 - ); 31 - 32 - const functionName = tsc.propertyAccessExpression({ 33 - expression: zSymbol.placeholder, 34 - name: identifiers.array, 35 - }); 36 - 37 - let arrayExpression: ts.CallExpression | undefined; 38 - let hasCircularReference = false; 39 - 40 - if (!schema.items) { 41 - arrayExpression = tsc.callExpression({ 42 - functionName, 43 - parameters: [ 44 - unknownTypeToZodSchema({ 45 - plugin, 46 - schema: { 47 - type: 'unknown', 48 - }, 49 - }), 50 - ], 51 - }); 52 - } else { 53 - schema = deduplicateSchema({ schema }); 54 - 55 - // at least one item is guaranteed 56 - const itemExpressions = schema.items!.map((item) => { 57 - const zodSchema = schemaToZodSchema({ 58 - plugin, 59 - schema: item, 60 - state, 61 - }); 62 - if (zodSchema.hasCircularReference) { 63 - hasCircularReference = true; 64 - } 65 - return zodSchema.expression; 66 - }); 67 - 68 - if (itemExpressions.length === 1) { 69 - arrayExpression = tsc.callExpression({ 70 - functionName, 71 - parameters: itemExpressions, 72 - }); 73 - } else { 74 - if (schema.logicalOperator === 'and') { 75 - const firstSchema = schema.items![0]!; 76 - // we want to add an intersection, but not every schema can use the same API. 77 - // if the first item contains another array or not an object, we cannot use 78 - // `.and()` as that does not exist on `.union()` and non-object schemas. 79 - let intersectionExpression: ts.Expression; 80 - if ( 81 - firstSchema.logicalOperator === 'or' || 82 - (firstSchema.type && firstSchema.type !== 'object') 83 - ) { 84 - intersectionExpression = tsc.callExpression({ 85 - functionName: tsc.propertyAccessExpression({ 86 - expression: zSymbol.placeholder, 87 - name: identifiers.intersection, 88 - }), 89 - parameters: itemExpressions, 90 - }); 91 - } else { 92 - intersectionExpression = itemExpressions[0]!; 93 - for (let i = 1; i < itemExpressions.length; i++) { 94 - intersectionExpression = tsc.callExpression({ 95 - functionName: tsc.propertyAccessExpression({ 96 - expression: intersectionExpression, 97 - name: identifiers.and, 98 - }), 99 - parameters: [itemExpressions[i]!], 100 - }); 101 - } 102 - } 103 - 104 - arrayExpression = tsc.callExpression({ 105 - functionName, 106 - parameters: [intersectionExpression], 107 - }); 108 - } else { 109 - arrayExpression = tsc.callExpression({ 110 - functionName: tsc.propertyAccessExpression({ 111 - expression: zSymbol.placeholder, 112 - name: identifiers.array, 113 - }), 114 - parameters: [ 115 - tsc.callExpression({ 116 - functionName: tsc.propertyAccessExpression({ 117 - expression: zSymbol.placeholder, 118 - name: identifiers.union, 119 - }), 120 - parameters: [ 121 - tsc.arrayLiteralExpression({ 122 - elements: itemExpressions, 123 - }), 124 - ], 125 - }), 126 - ], 127 - }); 128 - } 129 - } 130 - } 131 - 132 - if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 133 - arrayExpression = tsc.callExpression({ 134 - functionName: tsc.propertyAccessExpression({ 135 - expression: arrayExpression, 136 - name: identifiers.length, 137 - }), 138 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 139 - }); 140 - } else { 141 - if (schema.minItems !== undefined) { 142 - arrayExpression = tsc.callExpression({ 143 - functionName: tsc.propertyAccessExpression({ 144 - expression: arrayExpression, 145 - name: identifiers.min, 146 - }), 147 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 148 - }); 149 - } 150 - 151 - if (schema.maxItems !== undefined) { 152 - arrayExpression = tsc.callExpression({ 153 - functionName: tsc.propertyAccessExpression({ 154 - expression: arrayExpression, 155 - name: identifiers.max, 156 - }), 157 - parameters: [tsc.valueToExpression({ value: schema.maxItems })], 158 - }); 159 - } 160 - } 161 - 162 - return { 163 - expression: arrayExpression, 164 - hasCircularReference, 165 - }; 166 - }; 167 - 168 - const booleanTypeToZodSchema = ({ 169 - plugin, 170 - schema, 171 - }: { 172 - plugin: ZodPlugin['Instance']; 173 - schema: SchemaWithType<'boolean'>; 174 - }) => { 175 - const zSymbol = plugin.referenceSymbol( 176 - plugin.api.getSelector('import', 'zod'), 177 - ); 178 - 179 - if (typeof schema.const === 'boolean') { 180 - const expression = tsc.callExpression({ 181 - functionName: tsc.propertyAccessExpression({ 182 - expression: zSymbol.placeholder, 183 - name: identifiers.literal, 184 - }), 185 - parameters: [tsc.ots.boolean(schema.const)], 186 - }); 187 - return expression; 188 - } 189 - 190 - const expression = tsc.callExpression({ 191 - functionName: tsc.propertyAccessExpression({ 192 - expression: zSymbol.placeholder, 193 - name: identifiers.boolean, 194 - }), 195 - }); 196 - return expression; 197 - }; 198 - 199 - const enumTypeToZodSchema = ({ 200 - plugin, 201 - schema, 202 - }: { 203 - plugin: ZodPlugin['Instance']; 204 - schema: SchemaWithType<'enum'>; 205 - }): ts.CallExpression => { 206 - const zSymbol = plugin.referenceSymbol( 207 - plugin.api.getSelector('import', 'zod'), 208 - ); 209 - 210 - const enumMembers: Array<ts.LiteralExpression> = []; 211 - const literalMembers: Array<ts.CallExpression> = []; 212 - 213 - let isNullable = false; 214 - let allStrings = true; 215 - 216 - for (const item of schema.items ?? []) { 217 - // Zod supports string, number, and boolean enums 218 - if (item.type === 'string' && typeof item.const === 'string') { 219 - const stringLiteral = tsc.stringLiteral({ 220 - text: item.const, 221 - }); 222 - enumMembers.push(stringLiteral); 223 - literalMembers.push( 224 - tsc.callExpression({ 225 - functionName: tsc.propertyAccessExpression({ 226 - expression: zSymbol.placeholder, 227 - name: identifiers.literal, 228 - }), 229 - parameters: [stringLiteral], 230 - }), 231 - ); 232 - } else if ( 233 - (item.type === 'number' || item.type === 'integer') && 234 - typeof item.const === 'number' 235 - ) { 236 - allStrings = false; 237 - const numberLiteral = tsc.ots.number(item.const); 238 - literalMembers.push( 239 - tsc.callExpression({ 240 - functionName: tsc.propertyAccessExpression({ 241 - expression: zSymbol.placeholder, 242 - name: identifiers.literal, 243 - }), 244 - parameters: [numberLiteral], 245 - }), 246 - ); 247 - } else if (item.type === 'boolean' && typeof item.const === 'boolean') { 248 - allStrings = false; 249 - const booleanLiteral = tsc.ots.boolean(item.const); 250 - literalMembers.push( 251 - tsc.callExpression({ 252 - functionName: tsc.propertyAccessExpression({ 253 - expression: zSymbol.placeholder, 254 - name: identifiers.literal, 255 - }), 256 - parameters: [booleanLiteral], 257 - }), 258 - ); 259 - } else if (item.type === 'null' || item.const === null) { 260 - isNullable = true; 261 - } 262 - } 16 + import { irSchemaWithTypeToAst } from './toAst'; 263 17 264 - if (!literalMembers.length) { 265 - return unknownTypeToZodSchema({ 266 - plugin, 267 - schema: { 268 - type: 'unknown', 269 - }, 270 - }); 271 - } 272 - 273 - // Use z.enum() for pure string enums, z.union() for mixed or non-string types 274 - let enumExpression: ts.CallExpression; 275 - if (allStrings && enumMembers.length > 0) { 276 - enumExpression = tsc.callExpression({ 277 - functionName: tsc.propertyAccessExpression({ 278 - expression: zSymbol.placeholder, 279 - name: identifiers.enum, 280 - }), 281 - parameters: [ 282 - tsc.arrayLiteralExpression({ 283 - elements: enumMembers, 284 - multiLine: false, 285 - }), 286 - ], 287 - }); 288 - } else if (literalMembers.length === 1) { 289 - // For single-member unions, use the member directly instead of wrapping in z.union() 290 - enumExpression = literalMembers[0]!; 291 - } else { 292 - enumExpression = tsc.callExpression({ 293 - functionName: tsc.propertyAccessExpression({ 294 - expression: zSymbol.placeholder, 295 - name: identifiers.union, 296 - }), 297 - parameters: [ 298 - tsc.arrayLiteralExpression({ 299 - elements: literalMembers, 300 - multiLine: literalMembers.length > 3, 301 - }), 302 - ], 303 - }); 304 - } 305 - 306 - if (isNullable) { 307 - enumExpression = tsc.callExpression({ 308 - functionName: tsc.propertyAccessExpression({ 309 - expression: enumExpression, 310 - name: identifiers.nullable, 311 - }), 312 - }); 313 - } 314 - 315 - return enumExpression; 316 - }; 317 - 318 - const neverTypeToZodSchema = ({ 319 - plugin, 320 - }: { 321 - plugin: ZodPlugin['Instance']; 322 - schema: SchemaWithType<'never'>; 323 - }) => { 324 - const zSymbol = plugin.referenceSymbol( 325 - plugin.api.getSelector('import', 'zod'), 326 - ); 327 - const expression = tsc.callExpression({ 328 - functionName: tsc.propertyAccessExpression({ 329 - expression: zSymbol.placeholder, 330 - name: identifiers.never, 331 - }), 332 - }); 333 - return expression; 334 - }; 335 - 336 - const nullTypeToZodSchema = ({ 337 - plugin, 338 - }: { 339 - plugin: ZodPlugin['Instance']; 340 - schema: SchemaWithType<'null'>; 341 - }) => { 342 - const zSymbol = plugin.referenceSymbol( 343 - plugin.api.getSelector('import', 'zod'), 344 - ); 345 - const expression = tsc.callExpression({ 346 - functionName: tsc.propertyAccessExpression({ 347 - expression: zSymbol.placeholder, 348 - name: identifiers.null, 349 - }), 350 - }); 351 - return expression; 352 - }; 353 - 354 - const numberParameter = ({ 355 - isBigInt, 356 - value, 357 - }: { 358 - isBigInt: boolean; 359 - value: unknown; 360 - }) => { 361 - const expression = tsc.valueToExpression({ value }); 362 - 363 - if ( 364 - isBigInt && 365 - (typeof value === 'bigint' || 366 - typeof value === 'number' || 367 - typeof value === 'string' || 368 - typeof value === 'boolean') 369 - ) { 370 - return tsc.callExpression({ 371 - functionName: 'BigInt', 372 - parameters: [expression], 373 - }); 374 - } 375 - 376 - return expression; 377 - }; 378 - 379 - const numberTypeToZodSchema = ({ 380 - plugin, 381 - schema, 382 - }: { 383 - plugin: ZodPlugin['Instance']; 384 - schema: SchemaWithType<'integer' | 'number'>; 385 - }) => { 386 - const zSymbol = plugin.referenceSymbol( 387 - plugin.api.getSelector('import', 'zod'), 388 - ); 389 - 390 - const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 391 - 392 - if (typeof schema.const === 'number') { 393 - // TODO: parser - handle bigint constants 394 - const expression = tsc.callExpression({ 395 - functionName: tsc.propertyAccessExpression({ 396 - expression: zSymbol.placeholder, 397 - name: identifiers.literal, 398 - }), 399 - parameters: [tsc.ots.number(schema.const)], 400 - }); 401 - return expression; 402 - } 403 - 404 - let numberExpression = tsc.callExpression({ 405 - functionName: isBigInt 406 - ? tsc.propertyAccessExpression({ 407 - expression: tsc.propertyAccessExpression({ 408 - expression: zSymbol.placeholder, 409 - name: identifiers.coerce, 410 - }), 411 - name: identifiers.bigint, 412 - }) 413 - : tsc.propertyAccessExpression({ 414 - expression: zSymbol.placeholder, 415 - name: identifiers.number, 416 - }), 417 - }); 418 - 419 - if (!isBigInt && schema.type === 'integer') { 420 - numberExpression = tsc.callExpression({ 421 - functionName: tsc.propertyAccessExpression({ 422 - expression: numberExpression, 423 - name: identifiers.int, 424 - }), 425 - }); 426 - } 427 - 428 - if (schema.exclusiveMinimum !== undefined) { 429 - numberExpression = tsc.callExpression({ 430 - functionName: tsc.propertyAccessExpression({ 431 - expression: numberExpression, 432 - name: identifiers.gt, 433 - }), 434 - parameters: [ 435 - numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 436 - ], 437 - }); 438 - } else if (schema.minimum !== undefined) { 439 - numberExpression = tsc.callExpression({ 440 - functionName: tsc.propertyAccessExpression({ 441 - expression: numberExpression, 442 - name: identifiers.gte, 443 - }), 444 - parameters: [numberParameter({ isBigInt, value: schema.minimum })], 445 - }); 446 - } 447 - 448 - if (schema.exclusiveMaximum !== undefined) { 449 - numberExpression = tsc.callExpression({ 450 - functionName: tsc.propertyAccessExpression({ 451 - expression: numberExpression, 452 - name: identifiers.lt, 453 - }), 454 - parameters: [ 455 - numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 456 - ], 457 - }); 458 - } else if (schema.maximum !== undefined) { 459 - numberExpression = tsc.callExpression({ 460 - functionName: tsc.propertyAccessExpression({ 461 - expression: numberExpression, 462 - name: identifiers.lte, 463 - }), 464 - parameters: [numberParameter({ isBigInt, value: schema.maximum })], 465 - }); 466 - } 467 - 468 - return numberExpression; 469 - }; 470 - 471 - const objectTypeToZodSchema = ({ 472 - plugin, 473 - schema, 474 - state, 475 - }: { 476 - plugin: ZodPlugin['Instance']; 477 - schema: SchemaWithType<'object'>; 478 - state: State; 479 - }): Omit<ZodSchema, 'typeName'> & { 480 - anyType?: string; 481 - } => { 482 - const zSymbol = plugin.referenceSymbol( 483 - plugin.api.getSelector('import', 'zod'), 484 - ); 485 - 486 - let hasCircularReference = false; 487 - 488 - // TODO: parser - handle constants 489 - const properties: Array<ts.PropertyAssignment> = []; 490 - 491 - const required = schema.required ?? []; 492 - 493 - for (const name in schema.properties) { 494 - const property = schema.properties[name]!; 495 - const isRequired = required.includes(name); 496 - 497 - const propertyExpression = schemaToZodSchema({ 498 - optional: !isRequired, 499 - plugin, 500 - schema: property, 501 - state, 502 - }); 503 - 504 - if (propertyExpression.hasCircularReference) { 505 - hasCircularReference = true; 506 - } 507 - 508 - numberRegExp.lastIndex = 0; 509 - let propertyName; 510 - if (numberRegExp.test(name)) { 511 - // For numeric literals, we'll handle negative numbers by using a string literal 512 - // instead of trying to use a PrefixUnaryExpression 513 - propertyName = name.startsWith('-') 514 - ? ts.factory.createStringLiteral(name) 515 - : ts.factory.createNumericLiteral(name); 516 - } else { 517 - propertyName = name; 518 - } 519 - // TODO: parser - abstract safe property name logic 520 - if ( 521 - ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 522 - !name.startsWith("'") && 523 - !name.endsWith("'") 524 - ) { 525 - propertyName = `'${name}'`; 526 - } 527 - properties.push( 528 - tsc.propertyAssignment({ 529 - initializer: propertyExpression.expression, 530 - name: propertyName, 531 - }), 532 - ); 533 - } 534 - 535 - if ( 536 - schema.additionalProperties && 537 - (!schema.properties || !Object.keys(schema.properties).length) 538 - ) { 539 - const zodSchema = schemaToZodSchema({ 540 - plugin, 541 - schema: schema.additionalProperties, 542 - state, 543 - }); 544 - const expression = tsc.callExpression({ 545 - functionName: tsc.propertyAccessExpression({ 546 - expression: zSymbol.placeholder, 547 - name: identifiers.record, 548 - }), 549 - parameters: [zodSchema.expression], 550 - }); 551 - return { 552 - anyType: 'AnyZodObject', 553 - expression, 554 - hasCircularReference: zodSchema.hasCircularReference, 555 - }; 556 - } 557 - 558 - const expression = tsc.callExpression({ 559 - functionName: tsc.propertyAccessExpression({ 560 - expression: zSymbol.placeholder, 561 - name: identifiers.object, 562 - }), 563 - parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 564 - }); 565 - return { 566 - anyType: 'AnyZodObject', 567 - expression, 568 - hasCircularReference, 569 - }; 570 - }; 571 - 572 - const stringTypeToZodSchema = ({ 573 - plugin, 574 - schema, 575 - }: { 576 - plugin: ZodPlugin['Instance']; 577 - schema: SchemaWithType<'string'>; 578 - }) => { 579 - const zSymbol = plugin.referenceSymbol( 580 - plugin.api.getSelector('import', 'zod'), 581 - ); 582 - 583 - if (typeof schema.const === 'string') { 584 - const expression = tsc.callExpression({ 585 - functionName: tsc.propertyAccessExpression({ 586 - expression: zSymbol.placeholder, 587 - name: identifiers.literal, 588 - }), 589 - parameters: [tsc.ots.string(schema.const)], 590 - }); 591 - return expression; 592 - } 593 - 594 - let stringExpression = tsc.callExpression({ 595 - functionName: tsc.propertyAccessExpression({ 596 - expression: zSymbol.placeholder, 597 - name: identifiers.string, 598 - }), 599 - }); 600 - 601 - const dateTimeOptions: { key: string; value: boolean }[] = []; 602 - 603 - if (plugin.config.dates.offset) { 604 - dateTimeOptions.push({ key: 'offset', value: true }); 605 - } 606 - if (plugin.config.dates.local) { 607 - dateTimeOptions.push({ key: 'local', value: true }); 608 - } 609 - 610 - if (schema.format) { 611 - switch (schema.format) { 612 - case 'date': 613 - stringExpression = tsc.callExpression({ 614 - functionName: tsc.propertyAccessExpression({ 615 - expression: stringExpression, 616 - name: identifiers.date, 617 - }), 618 - }); 619 - break; 620 - case 'date-time': 621 - stringExpression = tsc.callExpression({ 622 - functionName: tsc.propertyAccessExpression({ 623 - expression: stringExpression, 624 - name: identifiers.datetime, 625 - }), 626 - parameters: 627 - dateTimeOptions.length > 0 628 - ? [ 629 - tsc.objectExpression({ 630 - obj: dateTimeOptions, 631 - }), 632 - ] 633 - : [], 634 - }); 635 - break; 636 - case 'email': 637 - stringExpression = tsc.callExpression({ 638 - functionName: tsc.propertyAccessExpression({ 639 - expression: stringExpression, 640 - name: identifiers.email, 641 - }), 642 - }); 643 - break; 644 - case 'ipv4': 645 - case 'ipv6': 646 - stringExpression = tsc.callExpression({ 647 - functionName: tsc.propertyAccessExpression({ 648 - expression: stringExpression, 649 - name: identifiers.ip, 650 - }), 651 - }); 652 - break; 653 - case 'time': 654 - stringExpression = tsc.callExpression({ 655 - functionName: tsc.propertyAccessExpression({ 656 - expression: stringExpression, 657 - name: identifiers.time, 658 - }), 659 - }); 660 - break; 661 - case 'uri': 662 - stringExpression = tsc.callExpression({ 663 - functionName: tsc.propertyAccessExpression({ 664 - expression: stringExpression, 665 - name: identifiers.url, 666 - }), 667 - }); 668 - break; 669 - case 'uuid': 670 - stringExpression = tsc.callExpression({ 671 - functionName: tsc.propertyAccessExpression({ 672 - expression: stringExpression, 673 - name: identifiers.uuid, 674 - }), 675 - }); 676 - break; 677 - } 678 - } 679 - 680 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 681 - stringExpression = tsc.callExpression({ 682 - functionName: tsc.propertyAccessExpression({ 683 - expression: stringExpression, 684 - name: identifiers.length, 685 - }), 686 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 687 - }); 688 - } else { 689 - if (schema.minLength !== undefined) { 690 - stringExpression = tsc.callExpression({ 691 - functionName: tsc.propertyAccessExpression({ 692 - expression: stringExpression, 693 - name: identifiers.min, 694 - }), 695 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 696 - }); 697 - } 698 - 699 - if (schema.maxLength !== undefined) { 700 - stringExpression = tsc.callExpression({ 701 - functionName: tsc.propertyAccessExpression({ 702 - expression: stringExpression, 703 - name: identifiers.max, 704 - }), 705 - parameters: [tsc.valueToExpression({ value: schema.maxLength })], 706 - }); 707 - } 708 - } 709 - 710 - if (schema.pattern) { 711 - stringExpression = tsc.callExpression({ 712 - functionName: tsc.propertyAccessExpression({ 713 - expression: stringExpression, 714 - name: identifiers.regex, 715 - }), 716 - parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 717 - }); 718 - } 719 - 720 - return stringExpression; 721 - }; 722 - 723 - const tupleTypeToZodSchema = ({ 724 - plugin, 725 - schema, 726 - state, 727 - }: { 728 - plugin: ZodPlugin['Instance']; 729 - schema: SchemaWithType<'tuple'>; 730 - state: State; 731 - }): Omit<ZodSchema, 'typeName'> & { 732 - anyType?: string; 733 - } => { 734 - const zSymbol = plugin.referenceSymbol( 735 - plugin.api.getSelector('import', 'zod'), 736 - ); 737 - 738 - let hasCircularReference = false; 739 - 740 - if (schema.const && Array.isArray(schema.const)) { 741 - const tupleElements = schema.const.map((value) => 742 - tsc.callExpression({ 743 - functionName: tsc.propertyAccessExpression({ 744 - expression: zSymbol.placeholder, 745 - name: identifiers.literal, 746 - }), 747 - parameters: [tsc.valueToExpression({ value })], 748 - }), 749 - ); 750 - const expression = tsc.callExpression({ 751 - functionName: tsc.propertyAccessExpression({ 752 - expression: zSymbol.placeholder, 753 - name: identifiers.tuple, 754 - }), 755 - parameters: [ 756 - tsc.arrayLiteralExpression({ 757 - elements: tupleElements, 758 - }), 759 - ], 760 - }); 761 - return { 762 - expression, 763 - hasCircularReference, 764 - }; 765 - } 766 - 767 - const tupleElements: Array<ts.Expression> = []; 768 - 769 - for (const item of schema.items ?? []) { 770 - const zodSchema = schemaToZodSchema({ 771 - plugin, 772 - schema: item, 773 - state, 774 - }); 775 - if (zodSchema.hasCircularReference) { 776 - hasCircularReference = true; 777 - } 778 - tupleElements.push(zodSchema.expression); 779 - } 780 - 781 - const expression = tsc.callExpression({ 782 - functionName: tsc.propertyAccessExpression({ 783 - expression: zSymbol.placeholder, 784 - name: identifiers.tuple, 785 - }), 786 - parameters: [ 787 - tsc.arrayLiteralExpression({ 788 - elements: tupleElements, 789 - }), 790 - ], 791 - }); 792 - return { 793 - expression, 794 - hasCircularReference, 795 - }; 796 - }; 797 - 798 - const undefinedTypeToZodSchema = ({ 799 - plugin, 800 - }: { 801 - plugin: ZodPlugin['Instance']; 802 - schema: SchemaWithType<'undefined'>; 803 - }) => { 804 - const zSymbol = plugin.referenceSymbol( 805 - plugin.api.getSelector('import', 'zod'), 806 - ); 807 - const expression = tsc.callExpression({ 808 - functionName: tsc.propertyAccessExpression({ 809 - expression: zSymbol.placeholder, 810 - name: identifiers.undefined, 811 - }), 812 - }); 813 - return expression; 814 - }; 815 - 816 - const unknownTypeToZodSchema = ({ 817 - plugin, 818 - }: { 819 - plugin: ZodPlugin['Instance']; 820 - schema: SchemaWithType<'unknown'>; 821 - }) => { 822 - const zSymbol = plugin.referenceSymbol( 823 - plugin.api.getSelector('import', 'zod'), 824 - ); 825 - const expression = tsc.callExpression({ 826 - functionName: tsc.propertyAccessExpression({ 827 - expression: zSymbol.placeholder, 828 - name: identifiers.unknown, 829 - }), 830 - }); 831 - return expression; 832 - }; 833 - 834 - const voidTypeToZodSchema = ({ 835 - plugin, 836 - }: { 837 - plugin: ZodPlugin['Instance']; 838 - schema: SchemaWithType<'void'>; 839 - }) => { 840 - const zSymbol = plugin.referenceSymbol( 841 - plugin.api.getSelector('import', 'zod'), 842 - ); 843 - const expression = tsc.callExpression({ 844 - functionName: tsc.propertyAccessExpression({ 845 - expression: zSymbol.placeholder, 846 - name: identifiers.void, 847 - }), 848 - }); 849 - return expression; 850 - }; 851 - 852 - const schemaTypeToZodSchema = ({ 853 - plugin, 854 - schema, 855 - state, 856 - }: { 857 - plugin: ZodPlugin['Instance']; 858 - schema: IR.SchemaObject; 859 - state: State; 860 - }): Omit<ZodSchema, 'typeName'> & { 861 - anyType?: string; 862 - } => { 863 - switch (schema.type as Required<IR.SchemaObject>['type']) { 864 - case 'array': 865 - return arrayTypeToZodSchema({ 866 - plugin, 867 - schema: schema as SchemaWithType<'array'>, 868 - state, 869 - }); 870 - case 'boolean': 871 - return { 872 - expression: booleanTypeToZodSchema({ 873 - plugin, 874 - schema: schema as SchemaWithType<'boolean'>, 875 - }), 876 - }; 877 - case 'enum': 878 - return { 879 - expression: enumTypeToZodSchema({ 880 - plugin, 881 - schema: schema as SchemaWithType<'enum'>, 882 - }), 883 - }; 884 - case 'integer': 885 - case 'number': 886 - return { 887 - expression: numberTypeToZodSchema({ 888 - plugin, 889 - schema: schema as SchemaWithType<'integer' | 'number'>, 890 - }), 891 - }; 892 - case 'never': 893 - return { 894 - expression: neverTypeToZodSchema({ 895 - plugin, 896 - schema: schema as SchemaWithType<'never'>, 897 - }), 898 - }; 899 - case 'null': 900 - return { 901 - expression: nullTypeToZodSchema({ 902 - plugin, 903 - schema: schema as SchemaWithType<'null'>, 904 - }), 905 - }; 906 - case 'object': 907 - return objectTypeToZodSchema({ 908 - plugin, 909 - schema: schema as SchemaWithType<'object'>, 910 - state, 911 - }); 912 - case 'string': 913 - return { 914 - expression: stringTypeToZodSchema({ 915 - plugin, 916 - schema: schema as SchemaWithType<'string'>, 917 - }), 918 - }; 919 - case 'tuple': 920 - return tupleTypeToZodSchema({ 921 - plugin, 922 - schema: schema as SchemaWithType<'tuple'>, 923 - state, 924 - }); 925 - case 'undefined': 926 - return { 927 - expression: undefinedTypeToZodSchema({ 928 - plugin, 929 - schema: schema as SchemaWithType<'undefined'>, 930 - }), 931 - }; 932 - case 'unknown': 933 - return { 934 - expression: unknownTypeToZodSchema({ 935 - plugin, 936 - schema: schema as SchemaWithType<'unknown'>, 937 - }), 938 - }; 939 - case 'void': 940 - return { 941 - expression: voidTypeToZodSchema({ 942 - plugin, 943 - schema: schema as SchemaWithType<'void'>, 944 - }), 945 - }; 946 - } 947 - }; 948 - 949 - const schemaToZodSchema = ({ 18 + export const irSchemaToAst = ({ 950 19 optional, 951 20 plugin, 952 21 schema, 953 22 state, 954 - }: { 23 + }: IrSchemaToAstOptions & { 955 24 /** 956 25 * Accept `optional` to handle optional object properties. We can't handle 957 26 * this inside the object function because `.optional()` must come before 958 27 * `.default()` which is handled in this function. 959 28 */ 960 29 optional?: boolean; 961 - plugin: ZodPlugin['Instance']; 962 30 schema: IR.SchemaObject; 963 - state: State; 964 - }): ZodSchema => { 965 - let zodSchema: Partial<ZodSchema> = {}; 31 + }): Ast => { 32 + let ast: Partial<Ast> = {}; 966 33 967 - const zSymbol = plugin.referenceSymbol( 968 - plugin.api.getSelector('import', 'zod'), 969 - ); 34 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 970 35 971 36 if (schema.$ref) { 972 37 const isCircularReference = state.circularReferenceTracker.includes( ··· 983 48 symbol = plugin.referenceSymbol(selector); 984 49 } 985 50 986 - zodSchema.expression = tsc.callExpression({ 51 + ast.expression = tsc.callExpression({ 987 52 functionName: tsc.propertyAccessExpression({ 988 - expression: zSymbol.placeholder, 53 + expression: z.placeholder, 989 54 name: identifiers.lazy, 990 55 }), 991 56 parameters: [ ··· 998 63 }), 999 64 ], 1000 65 }); 1001 - zodSchema.hasCircularReference = schema.circular; 66 + ast.hasCircularReference = schema.circular; 1002 67 } else { 1003 68 if (!symbol) { 1004 69 // if $ref hasn't been processed yet, inline it to avoid the ··· 1011 76 schema: ref, 1012 77 state: { 1013 78 ...state, 79 + _path: jsonPointerToPath(schema.$ref), 1014 80 currentReferenceTracker: [schema.$ref], 1015 81 }, 1016 82 }); 1017 83 } else { 1018 - zodSchema.hasCircularReference = schema.circular; 84 + ast.hasCircularReference = schema.circular; 1019 85 } 1020 86 1021 87 const refSymbol = plugin.referenceSymbol(selector); 1022 - zodSchema.expression = tsc.identifier({ text: refSymbol.placeholder }); 88 + ast.expression = tsc.identifier({ text: refSymbol.placeholder }); 1023 89 } 1024 90 1025 91 state.circularReferenceTracker.pop(); 1026 92 state.currentReferenceTracker.pop(); 1027 93 } else if (schema.type) { 1028 - const zSchema = schemaTypeToZodSchema({ plugin, schema, state }); 1029 - zodSchema.expression = zSchema.expression; 1030 - zodSchema.hasCircularReference = zSchema.hasCircularReference; 1031 - zodSchema.typeName = zSchema.anyType; 94 + const zSchema = irSchemaWithTypeToAst({ 95 + plugin, 96 + schema: schema as SchemaWithType, 97 + state, 98 + }); 99 + ast.expression = zSchema.expression; 100 + ast.hasCircularReference = zSchema.hasCircularReference; 101 + ast.typeName = zSchema.anyType; 1032 102 1033 103 if (plugin.config.metadata && schema.description) { 1034 - zodSchema.expression = tsc.callExpression({ 104 + ast.expression = tsc.callExpression({ 1035 105 functionName: tsc.propertyAccessExpression({ 1036 - expression: zodSchema.expression, 106 + expression: ast.expression, 1037 107 name: identifiers.describe, 1038 108 }), 1039 109 parameters: [tsc.stringLiteral({ text: schema.description })], ··· 1043 113 schema = deduplicateSchema({ schema }); 1044 114 1045 115 if (schema.items) { 1046 - const itemTypes = schema.items.map((item) => { 1047 - const zSchema = schemaToZodSchema({ 116 + const itemTypes = schema.items.map((item, index) => { 117 + const zSchema = irSchemaToAst({ 1048 118 plugin, 1049 119 schema: item, 1050 - state, 120 + state: { 121 + ...state, 122 + _path: [...state._path, 'items', index], 123 + }, 1051 124 }); 1052 125 if (zSchema.hasCircularReference) { 1053 - zodSchema.hasCircularReference = true; 126 + ast.hasCircularReference = true; 1054 127 } 1055 128 return zSchema.expression; 1056 129 }); ··· 1064 137 firstSchema.logicalOperator === 'or' || 1065 138 (firstSchema.type && firstSchema.type !== 'object') 1066 139 ) { 1067 - zodSchema.expression = tsc.callExpression({ 140 + ast.expression = tsc.callExpression({ 1068 141 functionName: tsc.propertyAccessExpression({ 1069 - expression: zSymbol.placeholder, 142 + expression: z.placeholder, 1070 143 name: identifiers.intersection, 1071 144 }), 1072 145 parameters: itemTypes, 1073 146 }); 1074 147 } else { 1075 - zodSchema.expression = itemTypes[0]; 148 + ast.expression = itemTypes[0]; 1076 149 itemTypes.slice(1).forEach((item) => { 1077 - zodSchema.expression = tsc.callExpression({ 150 + ast.expression = tsc.callExpression({ 1078 151 functionName: tsc.propertyAccessExpression({ 1079 - expression: zodSchema.expression!, 152 + expression: ast.expression!, 1080 153 name: identifiers.and, 1081 154 }), 1082 155 parameters: [item], ··· 1084 157 }); 1085 158 } 1086 159 } else { 1087 - zodSchema.expression = tsc.callExpression({ 160 + ast.expression = tsc.callExpression({ 1088 161 functionName: tsc.propertyAccessExpression({ 1089 - expression: zSymbol.placeholder, 162 + expression: z.placeholder, 1090 163 name: identifiers.union, 1091 164 }), 1092 165 parameters: [ ··· 1097 170 }); 1098 171 } 1099 172 } else { 1100 - zodSchema = schemaToZodSchema({ plugin, schema, state }); 173 + ast = irSchemaToAst({ plugin, schema, state }); 1101 174 } 1102 175 } else { 1103 176 // catch-all fallback for failed schemas 1104 - const zSchema = schemaTypeToZodSchema({ 177 + const zSchema = irSchemaWithTypeToAst({ 1105 178 plugin, 1106 179 schema: { 1107 180 type: 'unknown', 1108 181 }, 1109 182 state, 1110 183 }); 1111 - zodSchema.expression = zSchema.expression; 1112 - zodSchema.hasCircularReference = zSchema.hasCircularReference; 1113 - zodSchema.typeName = zSchema.anyType; 184 + ast.expression = zSchema.expression; 185 + ast.hasCircularReference = zSchema.hasCircularReference; 186 + ast.typeName = zSchema.anyType; 1114 187 } 1115 188 1116 - if (zodSchema.expression) { 189 + if (ast.expression) { 1117 190 if (schema.accessScope === 'read') { 1118 - zodSchema.expression = tsc.callExpression({ 191 + ast.expression = tsc.callExpression({ 1119 192 functionName: tsc.propertyAccessExpression({ 1120 - expression: zodSchema.expression, 193 + expression: ast.expression, 1121 194 name: identifiers.readonly, 1122 195 }), 1123 196 }); 1124 197 } 1125 198 1126 199 if (optional) { 1127 - zodSchema.expression = tsc.callExpression({ 200 + ast.expression = tsc.callExpression({ 1128 201 functionName: tsc.propertyAccessExpression({ 1129 - expression: zodSchema.expression, 202 + expression: ast.expression, 1130 203 name: identifiers.optional, 1131 204 }), 1132 205 }); ··· 1139 212 value: schema.default, 1140 213 }); 1141 214 if (callParameter) { 1142 - zodSchema.expression = tsc.callExpression({ 215 + ast.expression = tsc.callExpression({ 1143 216 functionName: tsc.propertyAccessExpression({ 1144 - expression: zodSchema.expression, 217 + expression: ast.expression, 1145 218 name: identifiers.default, 1146 219 }), 1147 220 parameters: [callParameter], ··· 1150 223 } 1151 224 } 1152 225 1153 - if (zodSchema.hasCircularReference) { 1154 - if (!zodSchema.typeName) { 1155 - zodSchema.typeName = 'ZodTypeAny'; 226 + if (ast.hasCircularReference) { 227 + if (!ast.typeName) { 228 + ast.typeName = 'ZodTypeAny'; 1156 229 } 1157 - } else if (zodSchema.typeName) { 1158 - zodSchema.typeName = undefined; 230 + } else if (ast.typeName) { 231 + ast.typeName = undefined; 1159 232 } 1160 233 1161 - return zodSchema as ZodSchema; 234 + return ast as Ast; 1162 235 }; 1163 236 1164 237 const handleComponent = ({ 1165 238 id, 1166 239 plugin, 1167 240 schema, 1168 - state, 1169 - }: { 241 + state: _state, 242 + }: Omit<IrSchemaToAstOptions, 'state'> & { 1170 243 id: string; 1171 - plugin: ZodPlugin['Instance']; 1172 244 schema: IR.SchemaObject; 1173 - state?: State; 245 + state?: Partial<IrSchemaToAstOptions['state']>; 1174 246 }): void => { 1175 - if (!state) { 1176 - state = { 1177 - circularReferenceTracker: [id], 1178 - currentReferenceTracker: [id], 1179 - hasCircularReference: false, 1180 - }; 1181 - } 247 + const state: IrSchemaToAstOptions['state'] = { 248 + _path: _state?._path ?? [], 249 + circularReferenceTracker: _state?.circularReferenceTracker ?? [id], 250 + currentReferenceTracker: _state?.currentReferenceTracker ?? [id], 251 + hasCircularReference: _state?.hasCircularReference ?? false, 252 + }; 1182 253 1183 254 const selector = plugin.api.getSelector('ref', id); 1184 255 let symbol = plugin.getSymbol(selector); 1185 256 if (symbol) return; 1186 257 1187 - const zodSchema = schemaToZodSchema({ plugin, schema, state }); 258 + const ast = irSchemaToAst({ plugin, schema, state }); 1188 259 const baseName = refToName(id); 260 + const resourceType = pathToSymbolResourceType(state._path); 1189 261 symbol = plugin.registerSymbol({ 1190 262 exported: true, 263 + meta: { 264 + resourceType, 265 + }, 1191 266 name: buildName({ 1192 267 config: plugin.config.definitions, 1193 268 name: baseName, ··· 1199 274 exported: true, 1200 275 meta: { 1201 276 kind: 'type', 277 + resourceType, 1202 278 }, 1203 279 name: buildName({ 1204 280 config: plugin.config.definitions.types.infer, ··· 1207 283 selector: plugin.api.getSelector('type-infer-ref', id), 1208 284 }) 1209 285 : undefined; 1210 - exportZodSchema({ 286 + exportAst({ 287 + ast, 1211 288 plugin, 1212 289 schema, 1213 290 symbol, 1214 291 typeInferSymbol, 1215 - zodSchema, 1216 292 }); 1217 293 }; 1218 294 ··· 1220 296 plugin.registerSymbol({ 1221 297 external: getZodModule({ plugin }), 1222 298 name: 'z', 1223 - selector: plugin.api.getSelector('import', 'zod'), 299 + selector: plugin.api.getSelector('external', 'zod.z'), 1224 300 }); 1225 301 1226 302 plugin.forEach( ··· 1232 308 (event) => { 1233 309 switch (event.type) { 1234 310 case 'operation': 1235 - operationToZodSchema({ 1236 - getZodSchema: (schema) => { 1237 - const state: State = { 311 + irOperationToAst({ 312 + getAst: (schema, path) => { 313 + const state: IrSchemaToAstOptions['state'] = { 314 + _path: path, 1238 315 circularReferenceTracker: [], 1239 316 currentReferenceTracker: [], 1240 317 hasCircularReference: false, 1241 318 }; 1242 - return schemaToZodSchema({ plugin, schema, state }); 319 + return irSchemaToAst({ plugin, schema, state }); 1243 320 }, 1244 321 operation: event.operation, 1245 322 plugin, 323 + state: { 324 + _path: event._path, 325 + }, 1246 326 }); 1247 327 break; 1248 328 case 'parameter': ··· 1250 330 id: event.$ref, 1251 331 plugin, 1252 332 schema: event.parameter.schema, 333 + state: { 334 + _path: event._path, 335 + }, 1253 336 }); 1254 337 break; 1255 338 case 'requestBody': ··· 1257 340 id: event.$ref, 1258 341 plugin, 1259 342 schema: event.requestBody.schema, 343 + state: { 344 + _path: event._path, 345 + }, 1260 346 }); 1261 347 break; 1262 348 case 'schema': ··· 1264 350 id: event.$ref, 1265 351 plugin, 1266 352 schema: event.schema, 353 + state: { 354 + _path: event._path, 355 + }, 1267 356 }); 1268 357 break; 1269 358 case 'webhook': 1270 - webhookToZodSchema({ 1271 - getZodSchema: (schema) => { 1272 - const state: State = { 359 + irWebhookToAst({ 360 + getAst: (schema, path) => { 361 + const state: IrSchemaToAstOptions['state'] = { 362 + _path: path, 1273 363 circularReferenceTracker: [], 1274 364 currentReferenceTracker: [], 1275 365 hasCircularReference: false, 1276 366 }; 1277 - return schemaToZodSchema({ plugin, schema, state }); 367 + return irSchemaToAst({ plugin, schema, state }); 1278 368 }, 1279 369 operation: event.operation, 1280 370 plugin, 371 + state: { 372 + _path: event._path, 373 + }, 1281 374 }); 1282 375 break; 1283 376 }
+160
packages/openapi-ts/src/plugins/zod/v3/toAst/array.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { deduplicateSchema } from '../../../../ir/schema'; 4 + import { tsc } from '../../../../tsc'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import { identifiers } from '../../constants'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + import { irSchemaToAst } from '../plugin'; 9 + import { unknownToAst } from './unknown'; 10 + 11 + export const arrayToAst = ({ 12 + plugin, 13 + schema, 14 + state, 15 + }: IrSchemaToAstOptions & { 16 + schema: SchemaWithType<'array'>; 17 + }): Omit<Ast, 'typeName'> & { 18 + anyType?: string; 19 + } => { 20 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 21 + 22 + const functionName = tsc.propertyAccessExpression({ 23 + expression: z.placeholder, 24 + name: identifiers.array, 25 + }); 26 + 27 + let arrayExpression: ts.CallExpression | undefined; 28 + let hasCircularReference = false; 29 + 30 + if (!schema.items) { 31 + arrayExpression = tsc.callExpression({ 32 + functionName, 33 + parameters: [ 34 + unknownToAst({ 35 + plugin, 36 + schema: { 37 + type: 'unknown', 38 + }, 39 + state, 40 + }), 41 + ], 42 + }); 43 + } else { 44 + schema = deduplicateSchema({ schema }); 45 + 46 + // at least one item is guaranteed 47 + const itemExpressions = schema.items!.map((item, index) => { 48 + const zodSchema = irSchemaToAst({ 49 + plugin, 50 + schema: item, 51 + state: { 52 + ...state, 53 + _path: [...state._path, 'items', index], 54 + }, 55 + }); 56 + if (zodSchema.hasCircularReference) { 57 + hasCircularReference = true; 58 + } 59 + return zodSchema.expression; 60 + }); 61 + 62 + if (itemExpressions.length === 1) { 63 + arrayExpression = tsc.callExpression({ 64 + functionName, 65 + parameters: itemExpressions, 66 + }); 67 + } else { 68 + if (schema.logicalOperator === 'and') { 69 + const firstSchema = schema.items![0]!; 70 + // we want to add an intersection, but not every schema can use the same API. 71 + // if the first item contains another array or not an object, we cannot use 72 + // `.and()` as that does not exist on `.union()` and non-object schemas. 73 + let intersectionExpression: ts.Expression; 74 + if ( 75 + firstSchema.logicalOperator === 'or' || 76 + (firstSchema.type && firstSchema.type !== 'object') 77 + ) { 78 + intersectionExpression = tsc.callExpression({ 79 + functionName: tsc.propertyAccessExpression({ 80 + expression: z.placeholder, 81 + name: identifiers.intersection, 82 + }), 83 + parameters: itemExpressions, 84 + }); 85 + } else { 86 + intersectionExpression = itemExpressions[0]!; 87 + for (let i = 1; i < itemExpressions.length; i++) { 88 + intersectionExpression = tsc.callExpression({ 89 + functionName: tsc.propertyAccessExpression({ 90 + expression: intersectionExpression, 91 + name: identifiers.and, 92 + }), 93 + parameters: [itemExpressions[i]!], 94 + }); 95 + } 96 + } 97 + 98 + arrayExpression = tsc.callExpression({ 99 + functionName, 100 + parameters: [intersectionExpression], 101 + }); 102 + } else { 103 + arrayExpression = tsc.callExpression({ 104 + functionName: tsc.propertyAccessExpression({ 105 + expression: z.placeholder, 106 + name: identifiers.array, 107 + }), 108 + parameters: [ 109 + tsc.callExpression({ 110 + functionName: tsc.propertyAccessExpression({ 111 + expression: z.placeholder, 112 + name: identifiers.union, 113 + }), 114 + parameters: [ 115 + tsc.arrayLiteralExpression({ 116 + elements: itemExpressions, 117 + }), 118 + ], 119 + }), 120 + ], 121 + }); 122 + } 123 + } 124 + } 125 + 126 + if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 127 + arrayExpression = tsc.callExpression({ 128 + functionName: tsc.propertyAccessExpression({ 129 + expression: arrayExpression, 130 + name: identifiers.length, 131 + }), 132 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 133 + }); 134 + } else { 135 + if (schema.minItems !== undefined) { 136 + arrayExpression = tsc.callExpression({ 137 + functionName: tsc.propertyAccessExpression({ 138 + expression: arrayExpression, 139 + name: identifiers.min, 140 + }), 141 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 142 + }); 143 + } 144 + 145 + if (schema.maxItems !== undefined) { 146 + arrayExpression = tsc.callExpression({ 147 + functionName: tsc.propertyAccessExpression({ 148 + expression: arrayExpression, 149 + name: identifiers.max, 150 + }), 151 + parameters: [tsc.valueToExpression({ value: schema.maxItems })], 152 + }); 153 + } 154 + } 155 + 156 + return { 157 + expression: arrayExpression, 158 + hasCircularReference, 159 + }; 160 + };
+32
packages/openapi-ts/src/plugins/zod/v3/toAst/boolean.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const booleanToAst = ({ 7 + plugin, 8 + schema, 9 + }: IrSchemaToAstOptions & { 10 + schema: SchemaWithType<'boolean'>; 11 + }) => { 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + 14 + if (typeof schema.const === 'boolean') { 15 + const expression = tsc.callExpression({ 16 + functionName: tsc.propertyAccessExpression({ 17 + expression: z.placeholder, 18 + name: identifiers.literal, 19 + }), 20 + parameters: [tsc.ots.boolean(schema.const)], 21 + }); 22 + return expression; 23 + } 24 + 25 + const expression = tsc.callExpression({ 26 + functionName: tsc.propertyAccessExpression({ 27 + expression: z.placeholder, 28 + name: identifiers.boolean, 29 + }), 30 + }); 31 + return expression; 32 + };
+125
packages/openapi-ts/src/plugins/zod/v3/toAst/enum.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { IrSchemaToAstOptions } from '../../shared/types'; 7 + import { unknownToAst } from './unknown'; 8 + 9 + export const enumToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'enum'>; 15 + }): ts.CallExpression => { 16 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 17 + 18 + const enumMembers: Array<ts.LiteralExpression> = []; 19 + const literalMembers: Array<ts.CallExpression> = []; 20 + 21 + let isNullable = false; 22 + let allStrings = true; 23 + 24 + for (const item of schema.items ?? []) { 25 + // Zod supports string, number, and boolean enums 26 + if (item.type === 'string' && typeof item.const === 'string') { 27 + const stringLiteral = tsc.stringLiteral({ 28 + text: item.const, 29 + }); 30 + enumMembers.push(stringLiteral); 31 + literalMembers.push( 32 + tsc.callExpression({ 33 + functionName: tsc.propertyAccessExpression({ 34 + expression: z.placeholder, 35 + name: identifiers.literal, 36 + }), 37 + parameters: [stringLiteral], 38 + }), 39 + ); 40 + } else if ( 41 + (item.type === 'number' || item.type === 'integer') && 42 + typeof item.const === 'number' 43 + ) { 44 + allStrings = false; 45 + const numberLiteral = tsc.ots.number(item.const); 46 + literalMembers.push( 47 + tsc.callExpression({ 48 + functionName: tsc.propertyAccessExpression({ 49 + expression: z.placeholder, 50 + name: identifiers.literal, 51 + }), 52 + parameters: [numberLiteral], 53 + }), 54 + ); 55 + } else if (item.type === 'boolean' && typeof item.const === 'boolean') { 56 + allStrings = false; 57 + const booleanLiteral = tsc.ots.boolean(item.const); 58 + literalMembers.push( 59 + tsc.callExpression({ 60 + functionName: tsc.propertyAccessExpression({ 61 + expression: z.placeholder, 62 + name: identifiers.literal, 63 + }), 64 + parameters: [booleanLiteral], 65 + }), 66 + ); 67 + } else if (item.type === 'null' || item.const === null) { 68 + isNullable = true; 69 + } 70 + } 71 + 72 + if (!literalMembers.length) { 73 + return unknownToAst({ 74 + plugin, 75 + schema: { 76 + type: 'unknown', 77 + }, 78 + state, 79 + }); 80 + } 81 + 82 + // Use z.enum() for pure string enums, z.union() for mixed or non-string types 83 + let enumExpression: ts.CallExpression; 84 + if (allStrings && enumMembers.length > 0) { 85 + enumExpression = tsc.callExpression({ 86 + functionName: tsc.propertyAccessExpression({ 87 + expression: z.placeholder, 88 + name: identifiers.enum, 89 + }), 90 + parameters: [ 91 + tsc.arrayLiteralExpression({ 92 + elements: enumMembers, 93 + multiLine: false, 94 + }), 95 + ], 96 + }); 97 + } else if (literalMembers.length === 1) { 98 + // For single-member unions, use the member directly instead of wrapping in z.union() 99 + enumExpression = literalMembers[0]!; 100 + } else { 101 + enumExpression = tsc.callExpression({ 102 + functionName: tsc.propertyAccessExpression({ 103 + expression: z.placeholder, 104 + name: identifiers.union, 105 + }), 106 + parameters: [ 107 + tsc.arrayLiteralExpression({ 108 + elements: literalMembers, 109 + multiLine: literalMembers.length > 3, 110 + }), 111 + ], 112 + }); 113 + } 114 + 115 + if (isNullable) { 116 + enumExpression = tsc.callExpression({ 117 + functionName: tsc.propertyAccessExpression({ 118 + expression: enumExpression, 119 + name: identifiers.nullable, 120 + }), 121 + }); 122 + } 123 + 124 + return enumExpression; 125 + };
+105
packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts
··· 1 + import type { SchemaWithType } from '../../../shared/types/schema'; 2 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 3 + import { arrayToAst } from './array'; 4 + import { booleanToAst } from './boolean'; 5 + import { enumToAst } from './enum'; 6 + import { neverToAst } from './never'; 7 + import { nullToAst } from './null'; 8 + import { numberToAst } from './number'; 9 + import { objectToAst } from './object'; 10 + import { stringToAst } from './string'; 11 + import { tupleToAst } from './tuple'; 12 + import { undefinedToAst } from './undefined'; 13 + import { unknownToAst } from './unknown'; 14 + import { voidToAst } from './void'; 15 + 16 + export const irSchemaWithTypeToAst = ({ 17 + schema, 18 + ...args 19 + }: IrSchemaToAstOptions & { 20 + schema: SchemaWithType; 21 + }): Omit<Ast, 'typeName'> & { 22 + anyType?: string; 23 + } => { 24 + switch (schema.type) { 25 + case 'array': 26 + return arrayToAst({ 27 + ...args, 28 + schema: schema as SchemaWithType<'array'>, 29 + }); 30 + case 'boolean': 31 + return { 32 + expression: booleanToAst({ 33 + ...args, 34 + schema: schema as SchemaWithType<'boolean'>, 35 + }), 36 + }; 37 + case 'enum': 38 + return { 39 + expression: enumToAst({ 40 + ...args, 41 + schema: schema as SchemaWithType<'enum'>, 42 + }), 43 + }; 44 + case 'integer': 45 + case 'number': 46 + return { 47 + expression: numberToAst({ 48 + ...args, 49 + schema: schema as SchemaWithType<'integer' | 'number'>, 50 + }), 51 + }; 52 + case 'never': 53 + return { 54 + expression: neverToAst({ 55 + ...args, 56 + schema: schema as SchemaWithType<'never'>, 57 + }), 58 + }; 59 + case 'null': 60 + return { 61 + expression: nullToAst({ 62 + ...args, 63 + schema: schema as SchemaWithType<'null'>, 64 + }), 65 + }; 66 + case 'object': 67 + return objectToAst({ 68 + ...args, 69 + schema: schema as SchemaWithType<'object'>, 70 + }); 71 + case 'string': 72 + return { 73 + expression: stringToAst({ 74 + ...args, 75 + schema: schema as SchemaWithType<'string'>, 76 + }), 77 + }; 78 + case 'tuple': 79 + return tupleToAst({ 80 + ...args, 81 + schema: schema as SchemaWithType<'tuple'>, 82 + }); 83 + case 'undefined': 84 + return { 85 + expression: undefinedToAst({ 86 + ...args, 87 + schema: schema as SchemaWithType<'undefined'>, 88 + }), 89 + }; 90 + case 'unknown': 91 + return { 92 + expression: unknownToAst({ 93 + ...args, 94 + schema: schema as SchemaWithType<'unknown'>, 95 + }), 96 + }; 97 + case 'void': 98 + return { 99 + expression: voidToAst({ 100 + ...args, 101 + schema: schema as SchemaWithType<'void'>, 102 + }), 103 + }; 104 + } 105 + };
+19
packages/openapi-ts/src/plugins/zod/v3/toAst/never.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const neverToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'never'>; 10 + }) => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const expression = tsc.callExpression({ 13 + functionName: tsc.propertyAccessExpression({ 14 + expression: z.placeholder, 15 + name: identifiers.never, 16 + }), 17 + }); 18 + return expression; 19 + };
+19
packages/openapi-ts/src/plugins/zod/v3/toAst/null.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const nullToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'null'>; 10 + }) => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const expression = tsc.callExpression({ 13 + functionName: tsc.propertyAccessExpression({ 14 + expression: z.placeholder, 15 + name: identifiers.null, 16 + }), 17 + }); 18 + return expression; 19 + };
+94
packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import { numberParameter } from '../../shared/numbers'; 5 + import type { IrSchemaToAstOptions } from '../../shared/types'; 6 + 7 + export const numberToAst = ({ 8 + plugin, 9 + schema, 10 + }: IrSchemaToAstOptions & { 11 + schema: SchemaWithType<'integer' | 'number'>; 12 + }) => { 13 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 14 + 15 + const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 16 + 17 + if (typeof schema.const === 'number') { 18 + // TODO: parser - handle bigint constants 19 + const expression = tsc.callExpression({ 20 + functionName: tsc.propertyAccessExpression({ 21 + expression: z.placeholder, 22 + name: identifiers.literal, 23 + }), 24 + parameters: [tsc.ots.number(schema.const)], 25 + }); 26 + return expression; 27 + } 28 + 29 + let numberExpression = tsc.callExpression({ 30 + functionName: isBigInt 31 + ? tsc.propertyAccessExpression({ 32 + expression: tsc.propertyAccessExpression({ 33 + expression: z.placeholder, 34 + name: identifiers.coerce, 35 + }), 36 + name: identifiers.bigint, 37 + }) 38 + : tsc.propertyAccessExpression({ 39 + expression: z.placeholder, 40 + name: identifiers.number, 41 + }), 42 + }); 43 + 44 + if (!isBigInt && schema.type === 'integer') { 45 + numberExpression = tsc.callExpression({ 46 + functionName: tsc.propertyAccessExpression({ 47 + expression: numberExpression, 48 + name: identifiers.int, 49 + }), 50 + }); 51 + } 52 + 53 + if (schema.exclusiveMinimum !== undefined) { 54 + numberExpression = tsc.callExpression({ 55 + functionName: tsc.propertyAccessExpression({ 56 + expression: numberExpression, 57 + name: identifiers.gt, 58 + }), 59 + parameters: [ 60 + numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 61 + ], 62 + }); 63 + } else if (schema.minimum !== undefined) { 64 + numberExpression = tsc.callExpression({ 65 + functionName: tsc.propertyAccessExpression({ 66 + expression: numberExpression, 67 + name: identifiers.gte, 68 + }), 69 + parameters: [numberParameter({ isBigInt, value: schema.minimum })], 70 + }); 71 + } 72 + 73 + if (schema.exclusiveMaximum !== undefined) { 74 + numberExpression = tsc.callExpression({ 75 + functionName: tsc.propertyAccessExpression({ 76 + expression: numberExpression, 77 + name: identifiers.lt, 78 + }), 79 + parameters: [ 80 + numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 81 + ], 82 + }); 83 + } else if (schema.maximum !== undefined) { 84 + numberExpression = tsc.callExpression({ 85 + functionName: tsc.propertyAccessExpression({ 86 + expression: numberExpression, 87 + name: identifiers.lte, 88 + }), 89 + parameters: [numberParameter({ isBigInt, value: schema.maximum })], 90 + }); 91 + } 92 + 93 + return numberExpression; 94 + };
+111
packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import { numberRegExp } from '../../../../utils/regexp'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import { identifiers } from '../../constants'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + import { irSchemaToAst } from '../plugin'; 9 + 10 + export const objectToAst = ({ 11 + plugin, 12 + schema, 13 + state, 14 + }: IrSchemaToAstOptions & { 15 + schema: SchemaWithType<'object'>; 16 + }): Omit<Ast, 'typeName'> & { 17 + anyType?: string; 18 + } => { 19 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 20 + 21 + let hasCircularReference = false; 22 + 23 + // TODO: parser - handle constants 24 + const properties: Array<ts.PropertyAssignment> = []; 25 + 26 + const required = schema.required ?? []; 27 + 28 + for (const name in schema.properties) { 29 + const property = schema.properties[name]!; 30 + const isRequired = required.includes(name); 31 + 32 + const propertyExpression = irSchemaToAst({ 33 + optional: !isRequired, 34 + plugin, 35 + schema: property, 36 + state: { 37 + ...state, 38 + _path: [...state._path, 'properties', name], 39 + }, 40 + }); 41 + 42 + if (propertyExpression.hasCircularReference) { 43 + hasCircularReference = true; 44 + } 45 + 46 + numberRegExp.lastIndex = 0; 47 + let propertyName; 48 + if (numberRegExp.test(name)) { 49 + // For numeric literals, we'll handle negative numbers by using a string literal 50 + // instead of trying to use a PrefixUnaryExpression 51 + propertyName = name.startsWith('-') 52 + ? ts.factory.createStringLiteral(name) 53 + : ts.factory.createNumericLiteral(name); 54 + } else { 55 + propertyName = name; 56 + } 57 + // TODO: parser - abstract safe property name logic 58 + if ( 59 + ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 60 + !name.startsWith("'") && 61 + !name.endsWith("'") 62 + ) { 63 + propertyName = `'${name}'`; 64 + } 65 + properties.push( 66 + tsc.propertyAssignment({ 67 + initializer: propertyExpression.expression, 68 + name: propertyName, 69 + }), 70 + ); 71 + } 72 + 73 + if ( 74 + schema.additionalProperties && 75 + (!schema.properties || !Object.keys(schema.properties).length) 76 + ) { 77 + const zodSchema = irSchemaToAst({ 78 + plugin, 79 + schema: schema.additionalProperties, 80 + state: { 81 + ...state, 82 + _path: [...state._path, 'additionalProperties'], 83 + }, 84 + }); 85 + const expression = tsc.callExpression({ 86 + functionName: tsc.propertyAccessExpression({ 87 + expression: z.placeholder, 88 + name: identifiers.record, 89 + }), 90 + parameters: [zodSchema.expression], 91 + }); 92 + return { 93 + anyType: 'AnyZodObject', 94 + expression, 95 + hasCircularReference: zodSchema.hasCircularReference, 96 + }; 97 + } 98 + 99 + const expression = tsc.callExpression({ 100 + functionName: tsc.propertyAccessExpression({ 101 + expression: z.placeholder, 102 + name: identifiers.object, 103 + }), 104 + parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 105 + }); 106 + return { 107 + anyType: 'AnyZodObject', 108 + expression, 109 + hasCircularReference, 110 + }; 111 + };
+152
packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const stringToAst = ({ 7 + plugin, 8 + schema, 9 + }: IrSchemaToAstOptions & { 10 + schema: SchemaWithType<'string'>; 11 + }) => { 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + 14 + if (typeof schema.const === 'string') { 15 + const expression = tsc.callExpression({ 16 + functionName: tsc.propertyAccessExpression({ 17 + expression: z.placeholder, 18 + name: identifiers.literal, 19 + }), 20 + parameters: [tsc.ots.string(schema.const)], 21 + }); 22 + return expression; 23 + } 24 + 25 + let stringExpression = tsc.callExpression({ 26 + functionName: tsc.propertyAccessExpression({ 27 + expression: z.placeholder, 28 + name: identifiers.string, 29 + }), 30 + }); 31 + 32 + const dateTimeOptions: { key: string; value: boolean }[] = []; 33 + 34 + if (plugin.config.dates.offset) { 35 + dateTimeOptions.push({ key: 'offset', value: true }); 36 + } 37 + if (plugin.config.dates.local) { 38 + dateTimeOptions.push({ key: 'local', value: true }); 39 + } 40 + 41 + if (schema.format) { 42 + switch (schema.format) { 43 + case 'date': 44 + stringExpression = tsc.callExpression({ 45 + functionName: tsc.propertyAccessExpression({ 46 + expression: stringExpression, 47 + name: identifiers.date, 48 + }), 49 + }); 50 + break; 51 + case 'date-time': 52 + stringExpression = tsc.callExpression({ 53 + functionName: tsc.propertyAccessExpression({ 54 + expression: stringExpression, 55 + name: identifiers.datetime, 56 + }), 57 + parameters: 58 + dateTimeOptions.length > 0 59 + ? [ 60 + tsc.objectExpression({ 61 + obj: dateTimeOptions, 62 + }), 63 + ] 64 + : [], 65 + }); 66 + break; 67 + case 'email': 68 + stringExpression = tsc.callExpression({ 69 + functionName: tsc.propertyAccessExpression({ 70 + expression: stringExpression, 71 + name: identifiers.email, 72 + }), 73 + }); 74 + break; 75 + case 'ipv4': 76 + case 'ipv6': 77 + stringExpression = tsc.callExpression({ 78 + functionName: tsc.propertyAccessExpression({ 79 + expression: stringExpression, 80 + name: identifiers.ip, 81 + }), 82 + }); 83 + break; 84 + case 'time': 85 + stringExpression = tsc.callExpression({ 86 + functionName: tsc.propertyAccessExpression({ 87 + expression: stringExpression, 88 + name: identifiers.time, 89 + }), 90 + }); 91 + break; 92 + case 'uri': 93 + stringExpression = tsc.callExpression({ 94 + functionName: tsc.propertyAccessExpression({ 95 + expression: stringExpression, 96 + name: identifiers.url, 97 + }), 98 + }); 99 + break; 100 + case 'uuid': 101 + stringExpression = tsc.callExpression({ 102 + functionName: tsc.propertyAccessExpression({ 103 + expression: stringExpression, 104 + name: identifiers.uuid, 105 + }), 106 + }); 107 + break; 108 + } 109 + } 110 + 111 + if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 112 + stringExpression = tsc.callExpression({ 113 + functionName: tsc.propertyAccessExpression({ 114 + expression: stringExpression, 115 + name: identifiers.length, 116 + }), 117 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 118 + }); 119 + } else { 120 + if (schema.minLength !== undefined) { 121 + stringExpression = tsc.callExpression({ 122 + functionName: tsc.propertyAccessExpression({ 123 + expression: stringExpression, 124 + name: identifiers.min, 125 + }), 126 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 127 + }); 128 + } 129 + 130 + if (schema.maxLength !== undefined) { 131 + stringExpression = tsc.callExpression({ 132 + functionName: tsc.propertyAccessExpression({ 133 + expression: stringExpression, 134 + name: identifiers.max, 135 + }), 136 + parameters: [tsc.valueToExpression({ value: schema.maxLength })], 137 + }); 138 + } 139 + } 140 + 141 + if (schema.pattern) { 142 + stringExpression = tsc.callExpression({ 143 + functionName: tsc.propertyAccessExpression({ 144 + expression: stringExpression, 145 + name: identifiers.regex, 146 + }), 147 + parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 148 + }); 149 + } 150 + 151 + return stringExpression; 152 + };
+83
packages/openapi-ts/src/plugins/zod/v3/toAst/tuple.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import { irSchemaToAst } from '../plugin'; 8 + 9 + export const tupleToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'tuple'>; 15 + }): Omit<Ast, 'typeName'> & { 16 + anyType?: string; 17 + } => { 18 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 19 + 20 + let hasCircularReference = false; 21 + 22 + if (schema.const && Array.isArray(schema.const)) { 23 + const tupleElements = schema.const.map((value) => 24 + tsc.callExpression({ 25 + functionName: tsc.propertyAccessExpression({ 26 + expression: z.placeholder, 27 + name: identifiers.literal, 28 + }), 29 + parameters: [tsc.valueToExpression({ value })], 30 + }), 31 + ); 32 + const expression = tsc.callExpression({ 33 + functionName: tsc.propertyAccessExpression({ 34 + expression: z.placeholder, 35 + name: identifiers.tuple, 36 + }), 37 + parameters: [ 38 + tsc.arrayLiteralExpression({ 39 + elements: tupleElements, 40 + }), 41 + ], 42 + }); 43 + return { 44 + expression, 45 + hasCircularReference, 46 + }; 47 + } 48 + 49 + const tupleElements: Array<ts.Expression> = []; 50 + 51 + if (schema.items) { 52 + schema.items.forEach((item, index) => { 53 + const itemSchema = irSchemaToAst({ 54 + plugin, 55 + schema: item, 56 + state: { 57 + ...state, 58 + _path: [...state._path, 'items', index], 59 + }, 60 + }); 61 + tupleElements.push(itemSchema.expression); 62 + if (itemSchema.hasCircularReference) { 63 + hasCircularReference = true; 64 + } 65 + }); 66 + } 67 + 68 + const expression = tsc.callExpression({ 69 + functionName: tsc.propertyAccessExpression({ 70 + expression: z.placeholder, 71 + name: identifiers.tuple, 72 + }), 73 + parameters: [ 74 + tsc.arrayLiteralExpression({ 75 + elements: tupleElements, 76 + }), 77 + ], 78 + }); 79 + return { 80 + expression, 81 + hasCircularReference, 82 + }; 83 + };
+19
packages/openapi-ts/src/plugins/zod/v3/toAst/undefined.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const undefinedToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'undefined'>; 10 + }) => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const expression = tsc.callExpression({ 13 + functionName: tsc.propertyAccessExpression({ 14 + expression: z.placeholder, 15 + name: identifiers.undefined, 16 + }), 17 + }); 18 + return expression; 19 + };
+19
packages/openapi-ts/src/plugins/zod/v3/toAst/unknown.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const unknownToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'unknown'>; 10 + }) => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const expression = tsc.callExpression({ 13 + functionName: tsc.propertyAccessExpression({ 14 + expression: z.placeholder, 15 + name: identifiers.unknown, 16 + }), 17 + }); 18 + return expression; 19 + };
+19
packages/openapi-ts/src/plugins/zod/v3/toAst/void.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const voidToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'void'>; 10 + }) => { 11 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 12 + const expression = tsc.callExpression({ 13 + functionName: tsc.propertyAccessExpression({ 14 + expression: z.placeholder, 15 + name: identifiers.void, 16 + }), 17 + }); 18 + return expression; 19 + };
+71
packages/openapi-ts/src/plugins/zod/v4/api.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../tsc'; 4 + import { identifiers } from '../constants'; 5 + import type { ValidatorArgs } from '../shared/types'; 6 + 7 + export const createRequestValidatorV4 = ({ 8 + operation, 9 + plugin, 10 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 11 + const symbol = plugin.getSymbol(plugin.api.getSelector('data', operation.id)); 12 + if (!symbol) return; 13 + 14 + const dataParameterName = 'data'; 15 + 16 + return tsc.arrowFunction({ 17 + async: true, 18 + parameters: [ 19 + { 20 + name: dataParameterName, 21 + }, 22 + ], 23 + statements: [ 24 + tsc.returnStatement({ 25 + expression: tsc.awaitExpression({ 26 + expression: tsc.callExpression({ 27 + functionName: tsc.propertyAccessExpression({ 28 + expression: symbol.placeholder, 29 + name: identifiers.parseAsync, 30 + }), 31 + parameters: [tsc.identifier({ text: dataParameterName })], 32 + }), 33 + }), 34 + }), 35 + ], 36 + }); 37 + }; 38 + 39 + export const createResponseValidatorV4 = ({ 40 + operation, 41 + plugin, 42 + }: ValidatorArgs): ts.ArrowFunction | undefined => { 43 + const symbol = plugin.getSymbol( 44 + plugin.api.getSelector('responses', operation.id), 45 + ); 46 + if (!symbol) return; 47 + 48 + const dataParameterName = 'data'; 49 + 50 + return tsc.arrowFunction({ 51 + async: true, 52 + parameters: [ 53 + { 54 + name: dataParameterName, 55 + }, 56 + ], 57 + statements: [ 58 + tsc.returnStatement({ 59 + expression: tsc.awaitExpression({ 60 + expression: tsc.callExpression({ 61 + functionName: tsc.propertyAccessExpression({ 62 + expression: symbol.placeholder, 63 + name: identifiers.parseAsync, 64 + }), 65 + parameters: [tsc.identifier({ text: dataParameterName })], 66 + }), 67 + }), 68 + }), 69 + ], 70 + }); 71 + };
+106 -1044
packages/openapi-ts/src/plugins/zod/v4/plugin.ts
··· 1 - import ts from 'typescript'; 2 - 3 1 import { deduplicateSchema } from '../../../ir/schema'; 4 2 import type { IR } from '../../../ir/types'; 5 3 import { buildName } from '../../../openApi/shared/utils/name'; 6 4 import { tsc } from '../../../tsc'; 7 - import { refToName } from '../../../utils/ref'; 8 - import { numberRegExp } from '../../../utils/regexp'; 5 + import { jsonPointerToPath, refToName } from '../../../utils/ref'; 6 + import type { SchemaWithType } from '../../shared/types/schema'; 7 + import { pathToSymbolResourceType } from '../../shared/utils/meta'; 9 8 import { identifiers } from '../constants'; 10 - import { exportZodSchema } from '../export'; 9 + import { exportAst } from '../shared/export'; 11 10 import { getZodModule } from '../shared/module'; 12 - import { operationToZodSchema } from '../shared/operation'; 13 - import type { SchemaWithType, State, ZodSchema } from '../shared/types'; 14 - import { webhookToZodSchema } from '../shared/webhook'; 11 + import { numberParameter } from '../shared/numbers'; 12 + import { irOperationToAst } from '../shared/operation'; 13 + import type { Ast, IrSchemaToAstOptions } from '../shared/types'; 14 + import { irWebhookToAst } from '../shared/webhook'; 15 15 import type { ZodPlugin } from '../types'; 16 - 17 - const arrayTypeToZodSchema = ({ 18 - plugin, 19 - schema, 20 - state, 21 - }: { 22 - plugin: ZodPlugin['Instance']; 23 - schema: SchemaWithType<'array'>; 24 - state: State; 25 - }): Omit<ZodSchema, 'typeName'> => { 26 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 27 - 28 - const zSymbol = plugin.referenceSymbol( 29 - plugin.api.getSelector('import', 'zod'), 30 - ); 31 - 32 - const functionName = tsc.propertyAccessExpression({ 33 - expression: zSymbol.placeholder, 34 - name: identifiers.array, 35 - }); 36 - 37 - if (!schema.items) { 38 - result.expression = tsc.callExpression({ 39 - functionName, 40 - parameters: [ 41 - unknownTypeToZodSchema({ 42 - plugin, 43 - schema: { 44 - type: 'unknown', 45 - }, 46 - }).expression, 47 - ], 48 - }); 49 - } else { 50 - schema = deduplicateSchema({ schema }); 51 - 52 - // at least one item is guaranteed 53 - const itemExpressions = schema.items!.map((item) => { 54 - const zodSchema = schemaToZodSchema({ 55 - plugin, 56 - schema: item, 57 - state, 58 - }); 59 - if (zodSchema.hasCircularReference) { 60 - result.hasCircularReference = true; 61 - } 62 - return zodSchema.expression; 63 - }); 64 - 65 - if (itemExpressions.length === 1) { 66 - result.expression = tsc.callExpression({ 67 - functionName, 68 - parameters: itemExpressions, 69 - }); 70 - } else { 71 - if (schema.logicalOperator === 'and') { 72 - const firstSchema = schema.items![0]!; 73 - // we want to add an intersection, but not every schema can use the same API. 74 - // if the first item contains another array or not an object, we cannot use 75 - // `.and()` as that does not exist on `.union()` and non-object schemas. 76 - let intersectionExpression: ts.Expression; 77 - if ( 78 - firstSchema.logicalOperator === 'or' || 79 - (firstSchema.type && firstSchema.type !== 'object') 80 - ) { 81 - intersectionExpression = tsc.callExpression({ 82 - functionName: tsc.propertyAccessExpression({ 83 - expression: zSymbol.placeholder, 84 - name: identifiers.intersection, 85 - }), 86 - parameters: itemExpressions, 87 - }); 88 - } else { 89 - intersectionExpression = itemExpressions[0]!; 90 - for (let i = 1; i < itemExpressions.length; i++) { 91 - intersectionExpression = tsc.callExpression({ 92 - functionName: tsc.propertyAccessExpression({ 93 - expression: intersectionExpression, 94 - name: identifiers.and, 95 - }), 96 - parameters: [itemExpressions[i]!], 97 - }); 98 - } 99 - } 100 - 101 - result.expression = tsc.callExpression({ 102 - functionName, 103 - parameters: [intersectionExpression], 104 - }); 105 - } else { 106 - result.expression = tsc.callExpression({ 107 - functionName: tsc.propertyAccessExpression({ 108 - expression: zSymbol.placeholder, 109 - name: identifiers.array, 110 - }), 111 - parameters: [ 112 - tsc.callExpression({ 113 - functionName: tsc.propertyAccessExpression({ 114 - expression: zSymbol.placeholder, 115 - name: identifiers.union, 116 - }), 117 - parameters: [ 118 - tsc.arrayLiteralExpression({ 119 - elements: itemExpressions, 120 - }), 121 - ], 122 - }), 123 - ], 124 - }); 125 - } 126 - } 127 - } 128 - 129 - if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 130 - result.expression = tsc.callExpression({ 131 - functionName: tsc.propertyAccessExpression({ 132 - expression: result.expression, 133 - name: identifiers.length, 134 - }), 135 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 136 - }); 137 - } else { 138 - if (schema.minItems !== undefined) { 139 - result.expression = tsc.callExpression({ 140 - functionName: tsc.propertyAccessExpression({ 141 - expression: result.expression, 142 - name: identifiers.min, 143 - }), 144 - parameters: [tsc.valueToExpression({ value: schema.minItems })], 145 - }); 146 - } 147 - 148 - if (schema.maxItems !== undefined) { 149 - result.expression = tsc.callExpression({ 150 - functionName: tsc.propertyAccessExpression({ 151 - expression: result.expression, 152 - name: identifiers.max, 153 - }), 154 - parameters: [tsc.valueToExpression({ value: schema.maxItems })], 155 - }); 156 - } 157 - } 158 - 159 - return result as Omit<ZodSchema, 'typeName'>; 160 - }; 161 - 162 - const booleanTypeToZodSchema = ({ 163 - plugin, 164 - schema, 165 - }: { 166 - plugin: ZodPlugin['Instance']; 167 - schema: SchemaWithType<'boolean'>; 168 - }): Omit<ZodSchema, 'typeName'> => { 169 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 170 - 171 - const zSymbol = plugin.referenceSymbol( 172 - plugin.api.getSelector('import', 'zod'), 173 - ); 174 - 175 - if (typeof schema.const === 'boolean') { 176 - result.expression = tsc.callExpression({ 177 - functionName: tsc.propertyAccessExpression({ 178 - expression: zSymbol.placeholder, 179 - name: identifiers.literal, 180 - }), 181 - parameters: [tsc.ots.boolean(schema.const)], 182 - }); 183 - return result as Omit<ZodSchema, 'typeName'>; 184 - } 185 - 186 - result.expression = tsc.callExpression({ 187 - functionName: tsc.propertyAccessExpression({ 188 - expression: zSymbol.placeholder, 189 - name: identifiers.boolean, 190 - }), 191 - }); 192 - return result as Omit<ZodSchema, 'typeName'>; 193 - }; 194 - 195 - const enumTypeToZodSchema = ({ 196 - plugin, 197 - schema, 198 - }: { 199 - plugin: ZodPlugin['Instance']; 200 - schema: SchemaWithType<'enum'>; 201 - }): Omit<ZodSchema, 'typeName'> => { 202 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 203 - 204 - const zSymbol = plugin.referenceSymbol( 205 - plugin.api.getSelector('import', 'zod'), 206 - ); 207 - 208 - const enumMembers: Array<ts.LiteralExpression> = []; 209 - const literalMembers: Array<ts.CallExpression> = []; 210 - 211 - let isNullable = false; 212 - let allStrings = true; 213 - 214 - for (const item of schema.items ?? []) { 215 - // Zod supports string, number, and boolean enums 216 - if (item.type === 'string' && typeof item.const === 'string') { 217 - const stringLiteral = tsc.stringLiteral({ 218 - text: item.const, 219 - }); 220 - enumMembers.push(stringLiteral); 221 - literalMembers.push( 222 - tsc.callExpression({ 223 - functionName: tsc.propertyAccessExpression({ 224 - expression: zSymbol.placeholder, 225 - name: identifiers.literal, 226 - }), 227 - parameters: [stringLiteral], 228 - }), 229 - ); 230 - } else if ( 231 - (item.type === 'number' || item.type === 'integer') && 232 - typeof item.const === 'number' 233 - ) { 234 - allStrings = false; 235 - const numberLiteral = tsc.ots.number(item.const); 236 - literalMembers.push( 237 - tsc.callExpression({ 238 - functionName: tsc.propertyAccessExpression({ 239 - expression: zSymbol.placeholder, 240 - name: identifiers.literal, 241 - }), 242 - parameters: [numberLiteral], 243 - }), 244 - ); 245 - } else if (item.type === 'boolean' && typeof item.const === 'boolean') { 246 - allStrings = false; 247 - const booleanLiteral = tsc.ots.boolean(item.const); 248 - literalMembers.push( 249 - tsc.callExpression({ 250 - functionName: tsc.propertyAccessExpression({ 251 - expression: zSymbol.placeholder, 252 - name: identifiers.literal, 253 - }), 254 - parameters: [booleanLiteral], 255 - }), 256 - ); 257 - } else if (item.type === 'null' || item.const === null) { 258 - isNullable = true; 259 - } 260 - } 261 - 262 - if (!literalMembers.length) { 263 - return unknownTypeToZodSchema({ 264 - plugin, 265 - schema: { 266 - type: 'unknown', 267 - }, 268 - }); 269 - } 270 - 271 - // Use z.enum() for pure string enums, z.union() for mixed or non-string types 272 - if (allStrings && enumMembers.length > 0) { 273 - result.expression = tsc.callExpression({ 274 - functionName: tsc.propertyAccessExpression({ 275 - expression: zSymbol.placeholder, 276 - name: identifiers.enum, 277 - }), 278 - parameters: [ 279 - tsc.arrayLiteralExpression({ 280 - elements: enumMembers, 281 - multiLine: false, 282 - }), 283 - ], 284 - }); 285 - } else if (literalMembers.length === 1) { 286 - // For single-member unions, use the member directly instead of wrapping in z.union() 287 - result.expression = literalMembers[0]; 288 - } else { 289 - result.expression = tsc.callExpression({ 290 - functionName: tsc.propertyAccessExpression({ 291 - expression: zSymbol.placeholder, 292 - name: identifiers.union, 293 - }), 294 - parameters: [ 295 - tsc.arrayLiteralExpression({ 296 - elements: literalMembers, 297 - multiLine: literalMembers.length > 3, 298 - }), 299 - ], 300 - }); 301 - } 302 - 303 - if (isNullable) { 304 - result.expression = tsc.callExpression({ 305 - functionName: tsc.propertyAccessExpression({ 306 - expression: zSymbol.placeholder, 307 - name: identifiers.nullable, 308 - }), 309 - parameters: [result.expression], 310 - }); 311 - } 312 - 313 - return result as Omit<ZodSchema, 'typeName'>; 314 - }; 315 - 316 - const neverTypeToZodSchema = ({ 317 - plugin, 318 - }: { 319 - plugin: ZodPlugin['Instance']; 320 - schema: SchemaWithType<'never'>; 321 - }): Omit<ZodSchema, 'typeName'> => { 322 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 323 - const zSymbol = plugin.referenceSymbol( 324 - plugin.api.getSelector('import', 'zod'), 325 - ); 326 - result.expression = tsc.callExpression({ 327 - functionName: tsc.propertyAccessExpression({ 328 - expression: zSymbol.placeholder, 329 - name: identifiers.never, 330 - }), 331 - }); 332 - return result as Omit<ZodSchema, 'typeName'>; 333 - }; 334 - 335 - const nullTypeToZodSchema = ({ 336 - plugin, 337 - }: { 338 - plugin: ZodPlugin['Instance']; 339 - schema: SchemaWithType<'null'>; 340 - }): Omit<ZodSchema, 'typeName'> => { 341 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 342 - const zSymbol = plugin.referenceSymbol( 343 - plugin.api.getSelector('import', 'zod'), 344 - ); 345 - result.expression = tsc.callExpression({ 346 - functionName: tsc.propertyAccessExpression({ 347 - expression: zSymbol.placeholder, 348 - name: identifiers.null, 349 - }), 350 - }); 351 - return result as Omit<ZodSchema, 'typeName'>; 352 - }; 353 - 354 - const numberParameter = ({ 355 - isBigInt, 356 - value, 357 - }: { 358 - isBigInt: boolean; 359 - value: unknown; 360 - }): ts.Expression | undefined => { 361 - const expression = tsc.valueToExpression({ value }); 362 - 363 - if ( 364 - isBigInt && 365 - (typeof value === 'bigint' || 366 - typeof value === 'number' || 367 - typeof value === 'string' || 368 - typeof value === 'boolean') 369 - ) { 370 - return tsc.callExpression({ 371 - functionName: 'BigInt', 372 - parameters: [expression], 373 - }); 374 - } 375 - 376 - return expression; 377 - }; 378 - 379 - const numberTypeToZodSchema = ({ 380 - plugin, 381 - schema, 382 - }: { 383 - plugin: ZodPlugin['Instance']; 384 - schema: SchemaWithType<'integer' | 'number'>; 385 - }): Omit<ZodSchema, 'typeName'> => { 386 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 387 - 388 - const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 389 - 390 - const zSymbol = plugin.referenceSymbol( 391 - plugin.api.getSelector('import', 'zod'), 392 - ); 393 - 394 - if (typeof schema.const === 'number') { 395 - // TODO: parser - handle bigint constants 396 - result.expression = tsc.callExpression({ 397 - functionName: tsc.propertyAccessExpression({ 398 - expression: zSymbol.placeholder, 399 - name: identifiers.literal, 400 - }), 401 - parameters: [tsc.ots.number(schema.const)], 402 - }); 403 - return result as Omit<ZodSchema, 'typeName'>; 404 - } 405 - 406 - result.expression = tsc.callExpression({ 407 - functionName: isBigInt 408 - ? tsc.propertyAccessExpression({ 409 - expression: tsc.propertyAccessExpression({ 410 - expression: zSymbol.placeholder, 411 - name: identifiers.coerce, 412 - }), 413 - name: identifiers.bigint, 414 - }) 415 - : tsc.propertyAccessExpression({ 416 - expression: zSymbol.placeholder, 417 - name: identifiers.number, 418 - }), 419 - }); 420 - 421 - if (!isBigInt && schema.type === 'integer') { 422 - result.expression = tsc.callExpression({ 423 - functionName: tsc.propertyAccessExpression({ 424 - expression: zSymbol.placeholder, 425 - name: identifiers.int, 426 - }), 427 - }); 428 - } 429 - 430 - if (schema.exclusiveMinimum !== undefined) { 431 - result.expression = tsc.callExpression({ 432 - functionName: tsc.propertyAccessExpression({ 433 - expression: result.expression, 434 - name: identifiers.gt, 435 - }), 436 - parameters: [ 437 - numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 438 - ], 439 - }); 440 - } else if (schema.minimum !== undefined) { 441 - result.expression = tsc.callExpression({ 442 - functionName: tsc.propertyAccessExpression({ 443 - expression: result.expression, 444 - name: identifiers.gte, 445 - }), 446 - parameters: [numberParameter({ isBigInt, value: schema.minimum })], 447 - }); 448 - } 449 - 450 - if (schema.exclusiveMaximum !== undefined) { 451 - result.expression = tsc.callExpression({ 452 - functionName: tsc.propertyAccessExpression({ 453 - expression: result.expression, 454 - name: identifiers.lt, 455 - }), 456 - parameters: [ 457 - numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 458 - ], 459 - }); 460 - } else if (schema.maximum !== undefined) { 461 - result.expression = tsc.callExpression({ 462 - functionName: tsc.propertyAccessExpression({ 463 - expression: result.expression, 464 - name: identifiers.lte, 465 - }), 466 - parameters: [numberParameter({ isBigInt, value: schema.maximum })], 467 - }); 468 - } 469 - 470 - return result as Omit<ZodSchema, 'typeName'>; 471 - }; 472 - 473 - const objectTypeToZodSchema = ({ 474 - plugin, 475 - schema, 476 - state, 477 - }: { 478 - plugin: ZodPlugin['Instance']; 479 - schema: SchemaWithType<'object'>; 480 - state: State; 481 - }): Omit<ZodSchema, 'typeName'> => { 482 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 483 - 484 - // TODO: parser - handle constants 485 - const properties: Array<ts.PropertyAssignment | ts.GetAccessorDeclaration> = 486 - []; 487 - 488 - const required = schema.required ?? []; 489 - 490 - const zSymbol = plugin.referenceSymbol( 491 - plugin.api.getSelector('import', 'zod'), 492 - ); 493 - 494 - for (const name in schema.properties) { 495 - const property = schema.properties[name]!; 496 - const isRequired = required.includes(name); 497 - 498 - const propertySchema = schemaToZodSchema({ 499 - optional: !isRequired, 500 - plugin, 501 - schema: property, 502 - state, 503 - }); 504 - if (propertySchema.hasCircularReference) { 505 - result.hasCircularReference = true; 506 - } 507 - 508 - numberRegExp.lastIndex = 0; 509 - let propertyName; 510 - if (numberRegExp.test(name)) { 511 - // For numeric literals, we'll handle negative numbers by using a string literal 512 - // instead of trying to use a PrefixUnaryExpression 513 - propertyName = name.startsWith('-') 514 - ? ts.factory.createStringLiteral(name) 515 - : ts.factory.createNumericLiteral(name); 516 - } else { 517 - propertyName = name; 518 - } 519 - // TODO: parser - abstract safe property name logic 520 - if ( 521 - ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 522 - !name.startsWith("'") && 523 - !name.endsWith("'") 524 - ) { 525 - propertyName = `'${name}'`; 526 - } 527 - 528 - if (propertySchema.hasCircularReference) { 529 - properties.push( 530 - tsc.getAccessorDeclaration({ 531 - name: propertyName, 532 - // @ts-expect-error 533 - returnType: propertySchema.typeName 534 - ? tsc.propertyAccessExpression({ 535 - expression: zSymbol.placeholder, 536 - name: propertySchema.typeName, 537 - }) 538 - : undefined, 539 - statements: [ 540 - tsc.returnStatement({ 541 - expression: propertySchema.expression, 542 - }), 543 - ], 544 - }), 545 - ); 546 - } else { 547 - properties.push( 548 - tsc.propertyAssignment({ 549 - initializer: propertySchema.expression, 550 - name: propertyName, 551 - }), 552 - ); 553 - } 554 - } 555 - 556 - if ( 557 - schema.additionalProperties && 558 - (!schema.properties || !Object.keys(schema.properties).length) 559 - ) { 560 - const zodSchema = schemaToZodSchema({ 561 - plugin, 562 - schema: schema.additionalProperties, 563 - state, 564 - }); 565 - result.expression = tsc.callExpression({ 566 - functionName: tsc.propertyAccessExpression({ 567 - expression: zSymbol.placeholder, 568 - name: identifiers.record, 569 - }), 570 - parameters: [ 571 - tsc.callExpression({ 572 - functionName: tsc.propertyAccessExpression({ 573 - expression: zSymbol.placeholder, 574 - name: identifiers.string, 575 - }), 576 - parameters: [], 577 - }), 578 - zodSchema.expression, 579 - ], 580 - }); 581 - if (zodSchema.hasCircularReference) { 582 - result.hasCircularReference = true; 583 - } 584 - 585 - // Return with typeName for circular references 586 - if (result.hasCircularReference) { 587 - return { 588 - ...result, 589 - typeName: 'ZodType', 590 - } as ZodSchema; 591 - } 592 - 593 - return result as Omit<ZodSchema, 'typeName'>; 594 - } 595 - 596 - result.expression = tsc.callExpression({ 597 - functionName: tsc.propertyAccessExpression({ 598 - expression: zSymbol.placeholder, 599 - name: identifiers.object, 600 - }), 601 - parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 602 - }); 603 - 604 - // Return with typeName for circular references (AnyZodObject doesn't exist in Zod v4, use ZodType) 605 - if (result.hasCircularReference) { 606 - return { 607 - ...result, 608 - typeName: 'ZodType', 609 - } as ZodSchema; 610 - } 611 - 612 - return result as Omit<ZodSchema, 'typeName'>; 613 - }; 614 - 615 - const stringTypeToZodSchema = ({ 616 - plugin, 617 - schema, 618 - }: { 619 - plugin: ZodPlugin['Instance']; 620 - schema: SchemaWithType<'string'>; 621 - }): Omit<ZodSchema, 'typeName'> => { 622 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 623 - 624 - const zSymbol = plugin.referenceSymbol( 625 - plugin.api.getSelector('import', 'zod'), 626 - ); 627 - 628 - if (typeof schema.const === 'string') { 629 - result.expression = tsc.callExpression({ 630 - functionName: tsc.propertyAccessExpression({ 631 - expression: zSymbol.placeholder, 632 - name: identifiers.literal, 633 - }), 634 - parameters: [tsc.ots.string(schema.const)], 635 - }); 636 - return result as Omit<ZodSchema, 'typeName'>; 637 - } 638 - 639 - result.expression = tsc.callExpression({ 640 - functionName: tsc.propertyAccessExpression({ 641 - expression: zSymbol.placeholder, 642 - name: identifiers.string, 643 - }), 644 - }); 645 - 646 - const dateTimeOptions: { key: string; value: boolean }[] = []; 647 - 648 - if (plugin.config.dates.offset) { 649 - dateTimeOptions.push({ key: 'offset', value: true }); 650 - } 651 - if (plugin.config.dates.local) { 652 - dateTimeOptions.push({ key: 'local', value: true }); 653 - } 654 - 655 - if (schema.format) { 656 - switch (schema.format) { 657 - case 'date': 658 - result.expression = tsc.callExpression({ 659 - functionName: tsc.propertyAccessExpression({ 660 - expression: tsc.propertyAccessExpression({ 661 - expression: zSymbol.placeholder, 662 - name: identifiers.iso, 663 - }), 664 - name: identifiers.date, 665 - }), 666 - }); 667 - break; 668 - case 'date-time': 669 - result.expression = tsc.callExpression({ 670 - functionName: tsc.propertyAccessExpression({ 671 - expression: tsc.propertyAccessExpression({ 672 - expression: zSymbol.placeholder, 673 - name: identifiers.iso, 674 - }), 675 - name: identifiers.datetime, 676 - }), 677 - parameters: 678 - dateTimeOptions.length > 0 679 - ? [ 680 - tsc.objectExpression({ 681 - obj: dateTimeOptions, 682 - }), 683 - ] 684 - : [], 685 - }); 686 - break; 687 - case 'email': 688 - result.expression = tsc.callExpression({ 689 - functionName: tsc.propertyAccessExpression({ 690 - expression: zSymbol.placeholder, 691 - name: identifiers.email, 692 - }), 693 - }); 694 - break; 695 - case 'ipv4': 696 - result.expression = tsc.callExpression({ 697 - functionName: tsc.propertyAccessExpression({ 698 - expression: zSymbol.placeholder, 699 - name: identifiers.ipv4, 700 - }), 701 - }); 702 - break; 703 - case 'ipv6': 704 - result.expression = tsc.callExpression({ 705 - functionName: tsc.propertyAccessExpression({ 706 - expression: zSymbol.placeholder, 707 - name: identifiers.ipv6, 708 - }), 709 - }); 710 - break; 711 - case 'time': 712 - result.expression = tsc.callExpression({ 713 - functionName: tsc.propertyAccessExpression({ 714 - expression: tsc.propertyAccessExpression({ 715 - expression: zSymbol.placeholder, 716 - name: identifiers.iso, 717 - }), 718 - name: identifiers.time, 719 - }), 720 - }); 721 - break; 722 - case 'uri': 723 - result.expression = tsc.callExpression({ 724 - functionName: tsc.propertyAccessExpression({ 725 - expression: zSymbol.placeholder, 726 - name: identifiers.url, 727 - }), 728 - }); 729 - break; 730 - case 'uuid': 731 - result.expression = tsc.callExpression({ 732 - functionName: tsc.propertyAccessExpression({ 733 - expression: zSymbol.placeholder, 734 - name: identifiers.uuid, 735 - }), 736 - }); 737 - break; 738 - } 739 - } 740 - 741 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 742 - result.expression = tsc.callExpression({ 743 - functionName: tsc.propertyAccessExpression({ 744 - expression: result.expression, 745 - name: identifiers.length, 746 - }), 747 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 748 - }); 749 - } else { 750 - if (schema.minLength !== undefined) { 751 - result.expression = tsc.callExpression({ 752 - functionName: tsc.propertyAccessExpression({ 753 - expression: result.expression, 754 - name: identifiers.min, 755 - }), 756 - parameters: [tsc.valueToExpression({ value: schema.minLength })], 757 - }); 758 - } 759 - 760 - if (schema.maxLength !== undefined) { 761 - result.expression = tsc.callExpression({ 762 - functionName: tsc.propertyAccessExpression({ 763 - expression: result.expression, 764 - name: identifiers.max, 765 - }), 766 - parameters: [tsc.valueToExpression({ value: schema.maxLength })], 767 - }); 768 - } 769 - } 770 - 771 - if (schema.pattern) { 772 - result.expression = tsc.callExpression({ 773 - functionName: tsc.propertyAccessExpression({ 774 - expression: result.expression, 775 - name: identifiers.regex, 776 - }), 777 - parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 778 - }); 779 - } 780 - 781 - return result as Omit<ZodSchema, 'typeName'>; 782 - }; 783 - 784 - const tupleTypeToZodSchema = ({ 785 - plugin, 786 - schema, 787 - state, 788 - }: { 789 - plugin: ZodPlugin['Instance']; 790 - schema: SchemaWithType<'tuple'>; 791 - state: State; 792 - }): Omit<ZodSchema, 'typeName'> => { 793 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 794 - 795 - const zSymbol = plugin.referenceSymbol( 796 - plugin.api.getSelector('import', 'zod'), 797 - ); 798 - 799 - if (schema.const && Array.isArray(schema.const)) { 800 - const tupleElements = schema.const.map((value) => 801 - tsc.callExpression({ 802 - functionName: tsc.propertyAccessExpression({ 803 - expression: zSymbol.placeholder, 804 - name: identifiers.literal, 805 - }), 806 - parameters: [tsc.valueToExpression({ value })], 807 - }), 808 - ); 809 - result.expression = tsc.callExpression({ 810 - functionName: tsc.propertyAccessExpression({ 811 - expression: zSymbol.placeholder, 812 - name: identifiers.tuple, 813 - }), 814 - parameters: [ 815 - tsc.arrayLiteralExpression({ 816 - elements: tupleElements, 817 - }), 818 - ], 819 - }); 820 - return result as Omit<ZodSchema, 'typeName'>; 821 - } 822 - 823 - const tupleElements: Array<ts.Expression> = []; 824 - 825 - for (const item of schema.items ?? []) { 826 - const itemSchema = schemaToZodSchema({ 827 - plugin, 828 - schema: item, 829 - state, 830 - }); 831 - tupleElements.push(itemSchema.expression); 832 - 833 - if (itemSchema.hasCircularReference) { 834 - result.hasCircularReference = true; 835 - } 836 - } 837 - 838 - result.expression = tsc.callExpression({ 839 - functionName: tsc.propertyAccessExpression({ 840 - expression: zSymbol.placeholder, 841 - name: identifiers.tuple, 842 - }), 843 - parameters: [ 844 - tsc.arrayLiteralExpression({ 845 - elements: tupleElements, 846 - }), 847 - ], 848 - }); 16 + import { irSchemaWithTypeToAst } from './toAst'; 849 17 850 - return result as Omit<ZodSchema, 'typeName'>; 851 - }; 852 - 853 - const undefinedTypeToZodSchema = ({ 854 - plugin, 855 - }: { 856 - plugin: ZodPlugin['Instance']; 857 - schema: SchemaWithType<'undefined'>; 858 - }): Omit<ZodSchema, 'typeName'> => { 859 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 860 - const zSymbol = plugin.referenceSymbol( 861 - plugin.api.getSelector('import', 'zod'), 862 - ); 863 - result.expression = tsc.callExpression({ 864 - functionName: tsc.propertyAccessExpression({ 865 - expression: zSymbol.placeholder, 866 - name: identifiers.undefined, 867 - }), 868 - }); 869 - return result as Omit<ZodSchema, 'typeName'>; 870 - }; 871 - 872 - const unknownTypeToZodSchema = ({ 873 - plugin, 874 - }: { 875 - plugin: ZodPlugin['Instance']; 876 - schema: SchemaWithType<'unknown'>; 877 - }): Omit<ZodSchema, 'typeName'> => { 878 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 879 - const zSymbol = plugin.referenceSymbol( 880 - plugin.api.getSelector('import', 'zod'), 881 - ); 882 - result.expression = tsc.callExpression({ 883 - functionName: tsc.propertyAccessExpression({ 884 - expression: zSymbol.placeholder, 885 - name: identifiers.unknown, 886 - }), 887 - }); 888 - return result as Omit<ZodSchema, 'typeName'>; 889 - }; 890 - 891 - const voidTypeToZodSchema = ({ 892 - plugin, 893 - }: { 894 - plugin: ZodPlugin['Instance']; 895 - schema: SchemaWithType<'void'>; 896 - }): Omit<ZodSchema, 'typeName'> => { 897 - const result: Partial<Omit<ZodSchema, 'typeName'>> = {}; 898 - const zSymbol = plugin.referenceSymbol( 899 - plugin.api.getSelector('import', 'zod'), 900 - ); 901 - result.expression = tsc.callExpression({ 902 - functionName: tsc.propertyAccessExpression({ 903 - expression: zSymbol.placeholder, 904 - name: identifiers.void, 905 - }), 906 - }); 907 - return result as Omit<ZodSchema, 'typeName'>; 908 - }; 909 - 910 - const schemaTypeToZodSchema = ({ 911 - plugin, 912 - schema, 913 - state, 914 - }: { 915 - plugin: ZodPlugin['Instance']; 916 - schema: IR.SchemaObject; 917 - state: State; 918 - }): Omit<ZodSchema, 'typeName'> => { 919 - switch (schema.type as Required<IR.SchemaObject>['type']) { 920 - case 'array': 921 - return arrayTypeToZodSchema({ 922 - plugin, 923 - schema: schema as SchemaWithType<'array'>, 924 - state, 925 - }); 926 - case 'boolean': 927 - return booleanTypeToZodSchema({ 928 - plugin, 929 - schema: schema as SchemaWithType<'boolean'>, 930 - }); 931 - case 'enum': 932 - return enumTypeToZodSchema({ 933 - plugin, 934 - schema: schema as SchemaWithType<'enum'>, 935 - }); 936 - case 'integer': 937 - case 'number': 938 - return numberTypeToZodSchema({ 939 - plugin, 940 - schema: schema as SchemaWithType<'integer' | 'number'>, 941 - }); 942 - case 'never': 943 - return neverTypeToZodSchema({ 944 - plugin, 945 - schema: schema as SchemaWithType<'never'>, 946 - }); 947 - case 'null': 948 - return nullTypeToZodSchema({ 949 - plugin, 950 - schema: schema as SchemaWithType<'null'>, 951 - }); 952 - case 'object': 953 - return objectTypeToZodSchema({ 954 - plugin, 955 - schema: schema as SchemaWithType<'object'>, 956 - state, 957 - }); 958 - case 'string': 959 - return stringTypeToZodSchema({ 960 - plugin, 961 - schema: schema as SchemaWithType<'string'>, 962 - }); 963 - case 'tuple': 964 - return tupleTypeToZodSchema({ 965 - plugin, 966 - schema: schema as SchemaWithType<'tuple'>, 967 - state, 968 - }); 969 - case 'undefined': 970 - return undefinedTypeToZodSchema({ 971 - plugin, 972 - schema: schema as SchemaWithType<'undefined'>, 973 - }); 974 - case 'unknown': 975 - return unknownTypeToZodSchema({ 976 - plugin, 977 - schema: schema as SchemaWithType<'unknown'>, 978 - }); 979 - case 'void': 980 - return voidTypeToZodSchema({ 981 - plugin, 982 - schema: schema as SchemaWithType<'void'>, 983 - }); 984 - } 985 - }; 986 - 987 - const schemaToZodSchema = ({ 18 + export const irSchemaToAst = ({ 988 19 optional, 989 20 plugin, 990 21 schema, 991 22 state, 992 - }: { 23 + }: IrSchemaToAstOptions & { 993 24 /** 994 25 * Accept `optional` to handle optional object properties. We can't handle 995 26 * this inside the object function because `.optional()` must come before 996 27 * `.default()` which is handled in this function. 997 28 */ 998 29 optional?: boolean; 999 - plugin: ZodPlugin['Instance']; 1000 30 schema: IR.SchemaObject; 1001 - state: State; 1002 - }): ZodSchema => { 1003 - let zodSchema: Partial<ZodSchema> = {}; 31 + }): Ast => { 32 + let ast: Partial<Ast> = {}; 1004 33 1005 - const zSymbol = plugin.referenceSymbol( 1006 - plugin.api.getSelector('import', 'zod'), 1007 - ); 34 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 1008 35 1009 36 if (schema.$ref) { 1010 37 const isCircularReference = state.circularReferenceTracker.includes( ··· 1023 50 } 1024 51 1025 52 if (isSelfReference) { 1026 - zodSchema.expression = tsc.callExpression({ 53 + ast.expression = tsc.callExpression({ 1027 54 functionName: tsc.propertyAccessExpression({ 1028 - expression: zSymbol.placeholder, 55 + expression: z.placeholder, 1029 56 name: identifiers.lazy, 1030 57 }), 1031 58 parameters: [ ··· 1040 67 ], 1041 68 }); 1042 69 } else { 1043 - zodSchema.expression = tsc.identifier({ text: symbol.placeholder }); 70 + ast.expression = tsc.identifier({ text: symbol.placeholder }); 1044 71 } 1045 - zodSchema.hasCircularReference = schema.circular; 72 + ast.hasCircularReference = schema.circular; 1046 73 } else { 1047 74 if (!symbol) { 1048 75 // if $ref hasn't been processed yet, inline it to avoid the ··· 1053 80 id: schema.$ref, 1054 81 plugin, 1055 82 schema: ref, 1056 - state, 83 + state: { 84 + ...state, 85 + _path: jsonPointerToPath(schema.$ref), 86 + }, 1057 87 }); 1058 88 } else { 1059 - zodSchema.hasCircularReference = schema.circular; 89 + ast.hasCircularReference = schema.circular; 1060 90 } 1061 91 1062 92 const refSymbol = plugin.referenceSymbol(selector); 1063 - zodSchema.expression = tsc.identifier({ text: refSymbol.placeholder }); 93 + ast.expression = tsc.identifier({ text: refSymbol.placeholder }); 1064 94 } 1065 95 1066 96 state.circularReferenceTracker.pop(); 1067 97 state.currentReferenceTracker.pop(); 1068 98 } else if (schema.type) { 1069 - const zSchema = schemaTypeToZodSchema({ plugin, schema, state }); 1070 - zodSchema.expression = zSchema.expression; 1071 - zodSchema.hasCircularReference = zSchema.hasCircularReference; 99 + const zSchema = irSchemaWithTypeToAst({ 100 + plugin, 101 + schema: schema as SchemaWithType, 102 + state, 103 + }); 104 + ast.expression = zSchema.expression; 105 + ast.hasCircularReference = zSchema.hasCircularReference; 1072 106 1073 107 if (plugin.config.metadata && schema.description) { 1074 - zodSchema.expression = tsc.callExpression({ 108 + ast.expression = tsc.callExpression({ 1075 109 functionName: tsc.propertyAccessExpression({ 1076 - expression: zodSchema.expression, 110 + expression: ast.expression, 1077 111 name: identifiers.register, 1078 112 }), 1079 113 parameters: [ 1080 114 tsc.propertyAccessExpression({ 1081 - expression: zSymbol.placeholder, 115 + expression: z.placeholder, 1082 116 name: identifiers.globalRegistry, 1083 117 }), 1084 118 tsc.objectExpression({ ··· 1096 130 schema = deduplicateSchema({ schema }); 1097 131 1098 132 if (schema.items) { 1099 - const itemSchemas = schema.items.map((item) => 1100 - schemaToZodSchema({ 133 + const itemSchemas = schema.items.map((item, index) => 134 + irSchemaToAst({ 1101 135 plugin, 1102 136 schema: item, 1103 - state, 137 + state: { 138 + ...state, 139 + _path: [...state._path, 'items', index], 140 + }, 1104 141 }), 1105 142 ); 1106 143 ··· 1113 150 firstSchema.logicalOperator === 'or' || 1114 151 (firstSchema.type && firstSchema.type !== 'object') 1115 152 ) { 1116 - zodSchema.expression = tsc.callExpression({ 153 + ast.expression = tsc.callExpression({ 1117 154 functionName: tsc.propertyAccessExpression({ 1118 - expression: zSymbol.placeholder, 155 + expression: z.placeholder, 1119 156 name: identifiers.intersection, 1120 157 }), 1121 158 parameters: itemSchemas.map((schema) => schema.expression), 1122 159 }); 1123 160 } else { 1124 - zodSchema.expression = itemSchemas[0]!.expression; 161 + ast.expression = itemSchemas[0]!.expression; 1125 162 itemSchemas.slice(1).forEach((schema) => { 1126 - zodSchema.expression = tsc.callExpression({ 163 + ast.expression = tsc.callExpression({ 1127 164 functionName: tsc.propertyAccessExpression({ 1128 - expression: zodSchema.expression!, 165 + expression: ast.expression!, 1129 166 name: identifiers.and, 1130 167 }), 1131 168 parameters: [ 1132 169 schema.hasCircularReference 1133 170 ? tsc.callExpression({ 1134 171 functionName: tsc.propertyAccessExpression({ 1135 - expression: zSymbol.placeholder, 172 + expression: z.placeholder, 1136 173 name: identifiers.lazy, 1137 174 }), 1138 175 parameters: [ ··· 1151 188 }); 1152 189 } 1153 190 } else { 1154 - zodSchema.expression = tsc.callExpression({ 191 + ast.expression = tsc.callExpression({ 1155 192 functionName: tsc.propertyAccessExpression({ 1156 - expression: zSymbol.placeholder, 193 + expression: z.placeholder, 1157 194 name: identifiers.union, 1158 195 }), 1159 196 parameters: [ ··· 1164 201 }); 1165 202 } 1166 203 } else { 1167 - zodSchema = schemaToZodSchema({ plugin, schema, state }); 204 + ast = irSchemaToAst({ 205 + plugin, 206 + schema, 207 + state, 208 + }); 1168 209 } 1169 210 } else { 1170 211 // catch-all fallback for failed schemas 1171 - const zSchema = schemaTypeToZodSchema({ 212 + const zSchema = irSchemaWithTypeToAst({ 1172 213 plugin, 1173 214 schema: { 1174 215 type: 'unknown', 1175 216 }, 1176 217 state, 1177 218 }); 1178 - zodSchema.expression = zSchema.expression; 219 + ast.expression = zSchema.expression; 1179 220 } 1180 221 1181 - if (zodSchema.expression) { 222 + if (ast.expression) { 1182 223 if (schema.accessScope === 'read') { 1183 - zodSchema.expression = tsc.callExpression({ 224 + ast.expression = tsc.callExpression({ 1184 225 functionName: tsc.propertyAccessExpression({ 1185 - expression: zodSchema.expression, 226 + expression: ast.expression, 1186 227 name: identifiers.readonly, 1187 228 }), 1188 229 }); 1189 230 } 1190 231 1191 232 if (optional) { 1192 - zodSchema.expression = tsc.callExpression({ 233 + ast.expression = tsc.callExpression({ 1193 234 functionName: tsc.propertyAccessExpression({ 1194 - expression: zSymbol.placeholder, 235 + expression: z.placeholder, 1195 236 name: identifiers.optional, 1196 237 }), 1197 - parameters: [zodSchema.expression], 238 + parameters: [ast.expression], 1198 239 }); 1199 - zodSchema.typeName = identifiers.ZodOptional; 240 + ast.typeName = identifiers.ZodOptional; 1200 241 } 1201 242 1202 243 if (schema.default !== undefined) { ··· 1206 247 value: schema.default, 1207 248 }); 1208 249 if (callParameter) { 1209 - zodSchema.expression = tsc.callExpression({ 250 + ast.expression = tsc.callExpression({ 1210 251 functionName: tsc.propertyAccessExpression({ 1211 - expression: zodSchema.expression, 252 + expression: ast.expression, 1212 253 name: identifiers.default, 1213 254 }), 1214 255 parameters: [callParameter], ··· 1217 258 } 1218 259 } 1219 260 1220 - return zodSchema as ZodSchema; 261 + return ast as Ast; 1221 262 }; 1222 263 1223 264 const handleComponent = ({ ··· 1225 266 plugin, 1226 267 schema, 1227 268 state: _state, 1228 - }: { 269 + }: Omit<IrSchemaToAstOptions, 'state'> & { 1229 270 id: string; 1230 - plugin: ZodPlugin['Instance']; 1231 271 schema: IR.SchemaObject; 1232 - state?: Omit<State, 'currentReferenceTracker'>; 272 + state?: Partial<IrSchemaToAstOptions['state']>; 1233 273 }): void => { 1234 - const state: State = { 1235 - circularReferenceTracker: [id], 1236 - hasCircularReference: false, 1237 - ..._state, 1238 - currentReferenceTracker: [id], 274 + const state: IrSchemaToAstOptions['state'] = { 275 + _path: _state?._path ?? [], 276 + circularReferenceTracker: _state?.circularReferenceTracker ?? [id], 277 + currentReferenceTracker: _state?.currentReferenceTracker ?? [id], 278 + hasCircularReference: _state?.hasCircularReference ?? false, 1239 279 }; 1240 280 1241 281 const selector = plugin.api.getSelector('ref', id); 1242 282 let symbol = plugin.getSymbol(selector); 1243 283 if (symbol && !plugin.getSymbolValue(symbol)) return; 1244 284 1245 - const zodSchema = schemaToZodSchema({ plugin, schema, state }); 285 + const ast = irSchemaToAst({ plugin, schema, state }); 1246 286 const baseName = refToName(id); 287 + const resourceType = pathToSymbolResourceType(state._path); 1247 288 symbol = plugin.registerSymbol({ 1248 289 exported: true, 290 + meta: { 291 + resourceType, 292 + }, 1249 293 name: buildName({ 1250 294 config: plugin.config.definitions, 1251 295 name: baseName, ··· 1257 301 exported: true, 1258 302 meta: { 1259 303 kind: 'type', 304 + resourceType, 1260 305 }, 1261 306 name: buildName({ 1262 307 config: plugin.config.definitions.types.infer, ··· 1265 310 selector: plugin.api.getSelector('type-infer-ref', id), 1266 311 }) 1267 312 : undefined; 1268 - exportZodSchema({ 313 + exportAst({ 314 + ast, 1269 315 plugin, 1270 316 schema, 1271 317 symbol, 1272 318 typeInferSymbol, 1273 - zodSchema, 1274 319 }); 1275 320 }; 1276 321 ··· 1278 323 plugin.registerSymbol({ 1279 324 external: getZodModule({ plugin }), 1280 325 name: 'z', 1281 - selector: plugin.api.getSelector('import', 'zod'), 326 + selector: plugin.api.getSelector('external', 'zod.z'), 1282 327 }); 1283 328 1284 329 plugin.forEach( ··· 1290 335 (event) => { 1291 336 switch (event.type) { 1292 337 case 'operation': 1293 - operationToZodSchema({ 1294 - getZodSchema: (schema) => { 1295 - const state: State = { 338 + irOperationToAst({ 339 + getAst: (schema, path) => { 340 + const state: IrSchemaToAstOptions['state'] = { 341 + _path: path, 1296 342 circularReferenceTracker: [], 1297 343 currentReferenceTracker: [], 1298 344 hasCircularReference: false, 1299 345 }; 1300 - return schemaToZodSchema({ plugin, schema, state }); 346 + return irSchemaToAst({ plugin, schema, state }); 1301 347 }, 1302 348 operation: event.operation, 1303 349 plugin, 350 + state: { 351 + _path: event._path, 352 + }, 1304 353 }); 1305 354 break; 1306 355 case 'parameter': ··· 1308 357 id: event.$ref, 1309 358 plugin, 1310 359 schema: event.parameter.schema, 360 + state: { 361 + _path: event._path, 362 + }, 1311 363 }); 1312 364 break; 1313 365 case 'requestBody': ··· 1315 367 id: event.$ref, 1316 368 plugin, 1317 369 schema: event.requestBody.schema, 370 + state: { 371 + _path: event._path, 372 + }, 1318 373 }); 1319 374 break; 1320 375 case 'schema': ··· 1322 377 id: event.$ref, 1323 378 plugin, 1324 379 schema: event.schema, 380 + state: { 381 + _path: event._path, 382 + }, 1325 383 }); 1326 384 break; 1327 385 case 'webhook': 1328 - webhookToZodSchema({ 1329 - getZodSchema: (schema) => { 1330 - const state: State = { 386 + irWebhookToAst({ 387 + getAst: (schema, path) => { 388 + const state: IrSchemaToAstOptions['state'] = { 389 + _path: path, 1331 390 circularReferenceTracker: [], 1332 391 currentReferenceTracker: [], 1333 392 hasCircularReference: false, 1334 393 }; 1335 - return schemaToZodSchema({ plugin, schema, state }); 394 + return irSchemaToAst({ plugin, schema, state }); 1336 395 }, 1337 396 operation: event.operation, 1338 397 plugin, 398 + state: { 399 + _path: event._path, 400 + }, 1339 401 }); 1340 402 break; 1341 403 }
+154
packages/openapi-ts/src/plugins/zod/v4/toAst/array.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { deduplicateSchema } from '../../../../ir/schema'; 4 + import { tsc } from '../../../../tsc'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import { identifiers } from '../../constants'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + import { irSchemaToAst } from '../plugin'; 9 + import { unknownToAst } from './unknown'; 10 + 11 + export const arrayToAst = ({ 12 + plugin, 13 + schema, 14 + state, 15 + }: IrSchemaToAstOptions & { 16 + schema: SchemaWithType<'array'>; 17 + }): Omit<Ast, 'typeName'> => { 18 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 19 + 20 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 21 + 22 + const functionName = tsc.propertyAccessExpression({ 23 + expression: z.placeholder, 24 + name: identifiers.array, 25 + }); 26 + 27 + if (!schema.items) { 28 + result.expression = tsc.callExpression({ 29 + functionName, 30 + parameters: [ 31 + unknownToAst({ 32 + plugin, 33 + schema: { 34 + type: 'unknown', 35 + }, 36 + state, 37 + }).expression, 38 + ], 39 + }); 40 + } else { 41 + schema = deduplicateSchema({ schema }); 42 + 43 + // at least one item is guaranteed 44 + const itemExpressions = schema.items!.map((item, index) => { 45 + const zodSchema = irSchemaToAst({ 46 + plugin, 47 + schema: item, 48 + state: { 49 + ...state, 50 + _path: [...state._path, 'items', index], 51 + }, 52 + }); 53 + if (zodSchema.hasCircularReference) { 54 + result.hasCircularReference = true; 55 + } 56 + return zodSchema.expression; 57 + }); 58 + 59 + if (itemExpressions.length === 1) { 60 + result.expression = tsc.callExpression({ 61 + functionName, 62 + parameters: itemExpressions, 63 + }); 64 + } else { 65 + if (schema.logicalOperator === 'and') { 66 + const firstSchema = schema.items![0]!; 67 + // we want to add an intersection, but not every schema can use the same API. 68 + // if the first item contains another array or not an object, we cannot use 69 + // `.and()` as that does not exist on `.union()` and non-object schemas. 70 + let intersectionExpression: ts.Expression; 71 + if ( 72 + firstSchema.logicalOperator === 'or' || 73 + (firstSchema.type && firstSchema.type !== 'object') 74 + ) { 75 + intersectionExpression = tsc.callExpression({ 76 + functionName: tsc.propertyAccessExpression({ 77 + expression: z.placeholder, 78 + name: identifiers.intersection, 79 + }), 80 + parameters: itemExpressions, 81 + }); 82 + } else { 83 + intersectionExpression = itemExpressions[0]!; 84 + for (let i = 1; i < itemExpressions.length; i++) { 85 + intersectionExpression = tsc.callExpression({ 86 + functionName: tsc.propertyAccessExpression({ 87 + expression: intersectionExpression, 88 + name: identifiers.and, 89 + }), 90 + parameters: [itemExpressions[i]!], 91 + }); 92 + } 93 + } 94 + 95 + result.expression = tsc.callExpression({ 96 + functionName, 97 + parameters: [intersectionExpression], 98 + }); 99 + } else { 100 + result.expression = tsc.callExpression({ 101 + functionName: tsc.propertyAccessExpression({ 102 + expression: z.placeholder, 103 + name: identifiers.array, 104 + }), 105 + parameters: [ 106 + tsc.callExpression({ 107 + functionName: tsc.propertyAccessExpression({ 108 + expression: z.placeholder, 109 + name: identifiers.union, 110 + }), 111 + parameters: [ 112 + tsc.arrayLiteralExpression({ 113 + elements: itemExpressions, 114 + }), 115 + ], 116 + }), 117 + ], 118 + }); 119 + } 120 + } 121 + } 122 + 123 + if (schema.minItems === schema.maxItems && schema.minItems !== undefined) { 124 + result.expression = tsc.callExpression({ 125 + functionName: tsc.propertyAccessExpression({ 126 + expression: result.expression, 127 + name: identifiers.length, 128 + }), 129 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 130 + }); 131 + } else { 132 + if (schema.minItems !== undefined) { 133 + result.expression = tsc.callExpression({ 134 + functionName: tsc.propertyAccessExpression({ 135 + expression: result.expression, 136 + name: identifiers.min, 137 + }), 138 + parameters: [tsc.valueToExpression({ value: schema.minItems })], 139 + }); 140 + } 141 + 142 + if (schema.maxItems !== undefined) { 143 + result.expression = tsc.callExpression({ 144 + functionName: tsc.propertyAccessExpression({ 145 + expression: result.expression, 146 + name: identifiers.max, 147 + }), 148 + parameters: [tsc.valueToExpression({ value: schema.maxItems })], 149 + }); 150 + } 151 + } 152 + 153 + return result as Omit<Ast, 'typeName'>; 154 + };
+34
packages/openapi-ts/src/plugins/zod/v4/toAst/boolean.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const booleanToAst = ({ 7 + plugin, 8 + schema, 9 + }: IrSchemaToAstOptions & { 10 + schema: SchemaWithType<'boolean'>; 11 + }): Omit<Ast, 'typeName'> => { 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + 14 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 15 + 16 + if (typeof schema.const === 'boolean') { 17 + result.expression = tsc.callExpression({ 18 + functionName: tsc.propertyAccessExpression({ 19 + expression: z.placeholder, 20 + name: identifiers.literal, 21 + }), 22 + parameters: [tsc.ots.boolean(schema.const)], 23 + }); 24 + return result as Omit<Ast, 'typeName'>; 25 + } 26 + 27 + result.expression = tsc.callExpression({ 28 + functionName: tsc.propertyAccessExpression({ 29 + expression: z.placeholder, 30 + name: identifiers.boolean, 31 + }), 32 + }); 33 + return result as Omit<Ast, 'typeName'>; 34 + };
+127
packages/openapi-ts/src/plugins/zod/v4/toAst/enum.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import { unknownToAst } from './unknown'; 8 + 9 + export const enumToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'enum'>; 15 + }): Omit<Ast, 'typeName'> => { 16 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 17 + 18 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 19 + 20 + const enumMembers: Array<ts.LiteralExpression> = []; 21 + const literalMembers: Array<ts.CallExpression> = []; 22 + 23 + let isNullable = false; 24 + let allStrings = true; 25 + 26 + for (const item of schema.items ?? []) { 27 + // Zod supports string, number, and boolean enums 28 + if (item.type === 'string' && typeof item.const === 'string') { 29 + const stringLiteral = tsc.stringLiteral({ 30 + text: item.const, 31 + }); 32 + enumMembers.push(stringLiteral); 33 + literalMembers.push( 34 + tsc.callExpression({ 35 + functionName: tsc.propertyAccessExpression({ 36 + expression: z.placeholder, 37 + name: identifiers.literal, 38 + }), 39 + parameters: [stringLiteral], 40 + }), 41 + ); 42 + } else if ( 43 + (item.type === 'number' || item.type === 'integer') && 44 + typeof item.const === 'number' 45 + ) { 46 + allStrings = false; 47 + const numberLiteral = tsc.ots.number(item.const); 48 + literalMembers.push( 49 + tsc.callExpression({ 50 + functionName: tsc.propertyAccessExpression({ 51 + expression: z.placeholder, 52 + name: identifiers.literal, 53 + }), 54 + parameters: [numberLiteral], 55 + }), 56 + ); 57 + } else if (item.type === 'boolean' && typeof item.const === 'boolean') { 58 + allStrings = false; 59 + const booleanLiteral = tsc.ots.boolean(item.const); 60 + literalMembers.push( 61 + tsc.callExpression({ 62 + functionName: tsc.propertyAccessExpression({ 63 + expression: z.placeholder, 64 + name: identifiers.literal, 65 + }), 66 + parameters: [booleanLiteral], 67 + }), 68 + ); 69 + } else if (item.type === 'null' || item.const === null) { 70 + isNullable = true; 71 + } 72 + } 73 + 74 + if (!literalMembers.length) { 75 + return unknownToAst({ 76 + plugin, 77 + schema: { 78 + type: 'unknown', 79 + }, 80 + state, 81 + }); 82 + } 83 + 84 + // Use z.enum() for pure string enums, z.union() for mixed or non-string types 85 + if (allStrings && enumMembers.length > 0) { 86 + result.expression = tsc.callExpression({ 87 + functionName: tsc.propertyAccessExpression({ 88 + expression: z.placeholder, 89 + name: identifiers.enum, 90 + }), 91 + parameters: [ 92 + tsc.arrayLiteralExpression({ 93 + elements: enumMembers, 94 + multiLine: false, 95 + }), 96 + ], 97 + }); 98 + } else if (literalMembers.length === 1) { 99 + // For single-member unions, use the member directly instead of wrapping in z.union() 100 + result.expression = literalMembers[0]; 101 + } else { 102 + result.expression = tsc.callExpression({ 103 + functionName: tsc.propertyAccessExpression({ 104 + expression: z.placeholder, 105 + name: identifiers.union, 106 + }), 107 + parameters: [ 108 + tsc.arrayLiteralExpression({ 109 + elements: literalMembers, 110 + multiLine: literalMembers.length > 3, 111 + }), 112 + ], 113 + }); 114 + } 115 + 116 + if (isNullable) { 117 + result.expression = tsc.callExpression({ 118 + functionName: tsc.propertyAccessExpression({ 119 + expression: z.placeholder, 120 + name: identifiers.nullable, 121 + }), 122 + parameters: [result.expression], 123 + }); 124 + } 125 + 126 + return result as Omit<Ast, 'typeName'>; 127 + };
+85
packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts
··· 1 + import type { SchemaWithType } from '../../../shared/types/schema'; 2 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 3 + import { arrayToAst } from './array'; 4 + import { booleanToAst } from './boolean'; 5 + import { enumToAst } from './enum'; 6 + import { neverToAst } from './never'; 7 + import { nullToAst } from './null'; 8 + import { numberToAst } from './number'; 9 + import { objectToAst } from './object'; 10 + import { stringToAst } from './string'; 11 + import { tupleToAst } from './tuple'; 12 + import { undefinedToAst } from './undefined'; 13 + import { unknownToAst } from './unknown'; 14 + import { voidToAst } from './void'; 15 + 16 + export const irSchemaWithTypeToAst = ({ 17 + schema, 18 + ...args 19 + }: IrSchemaToAstOptions & { 20 + schema: SchemaWithType; 21 + }): Omit<Ast, 'typeName'> => { 22 + switch (schema.type) { 23 + case 'array': 24 + return arrayToAst({ 25 + ...args, 26 + schema: schema as SchemaWithType<'array'>, 27 + }); 28 + case 'boolean': 29 + return booleanToAst({ 30 + ...args, 31 + schema: schema as SchemaWithType<'boolean'>, 32 + }); 33 + case 'enum': 34 + return enumToAst({ 35 + ...args, 36 + schema: schema as SchemaWithType<'enum'>, 37 + }); 38 + case 'integer': 39 + case 'number': 40 + return numberToAst({ 41 + ...args, 42 + schema: schema as SchemaWithType<'integer' | 'number'>, 43 + }); 44 + case 'never': 45 + return neverToAst({ 46 + ...args, 47 + schema: schema as SchemaWithType<'never'>, 48 + }); 49 + case 'null': 50 + return nullToAst({ 51 + ...args, 52 + schema: schema as SchemaWithType<'null'>, 53 + }); 54 + case 'object': 55 + return objectToAst({ 56 + ...args, 57 + schema: schema as SchemaWithType<'object'>, 58 + }); 59 + case 'string': 60 + return stringToAst({ 61 + ...args, 62 + schema: schema as SchemaWithType<'string'>, 63 + }); 64 + case 'tuple': 65 + return tupleToAst({ 66 + ...args, 67 + schema: schema as SchemaWithType<'tuple'>, 68 + }); 69 + case 'undefined': 70 + return undefinedToAst({ 71 + ...args, 72 + schema: schema as SchemaWithType<'undefined'>, 73 + }); 74 + case 'unknown': 75 + return unknownToAst({ 76 + ...args, 77 + schema: schema as SchemaWithType<'unknown'>, 78 + }); 79 + case 'void': 80 + return voidToAst({ 81 + ...args, 82 + schema: schema as SchemaWithType<'void'>, 83 + }); 84 + } 85 + };
+20
packages/openapi-ts/src/plugins/zod/v4/toAst/never.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const neverToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'never'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.never, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+20
packages/openapi-ts/src/plugins/zod/v4/toAst/null.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const nullToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'null'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.null, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+96
packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import { numberParameter } from '../../shared/numbers'; 5 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 6 + 7 + export const numberToAst = ({ 8 + plugin, 9 + schema, 10 + }: IrSchemaToAstOptions & { 11 + schema: SchemaWithType<'integer' | 'number'>; 12 + }): Omit<Ast, 'typeName'> => { 13 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 14 + 15 + const isBigInt = schema.type === 'integer' && schema.format === 'int64'; 16 + 17 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 18 + 19 + if (typeof schema.const === 'number') { 20 + // TODO: parser - handle bigint constants 21 + result.expression = tsc.callExpression({ 22 + functionName: tsc.propertyAccessExpression({ 23 + expression: z.placeholder, 24 + name: identifiers.literal, 25 + }), 26 + parameters: [tsc.ots.number(schema.const)], 27 + }); 28 + return result as Omit<Ast, 'typeName'>; 29 + } 30 + 31 + result.expression = tsc.callExpression({ 32 + functionName: isBigInt 33 + ? tsc.propertyAccessExpression({ 34 + expression: tsc.propertyAccessExpression({ 35 + expression: z.placeholder, 36 + name: identifiers.coerce, 37 + }), 38 + name: identifiers.bigint, 39 + }) 40 + : tsc.propertyAccessExpression({ 41 + expression: z.placeholder, 42 + name: identifiers.number, 43 + }), 44 + }); 45 + 46 + if (!isBigInt && schema.type === 'integer') { 47 + result.expression = tsc.callExpression({ 48 + functionName: tsc.propertyAccessExpression({ 49 + expression: z.placeholder, 50 + name: identifiers.int, 51 + }), 52 + }); 53 + } 54 + 55 + if (schema.exclusiveMinimum !== undefined) { 56 + result.expression = tsc.callExpression({ 57 + functionName: tsc.propertyAccessExpression({ 58 + expression: result.expression, 59 + name: identifiers.gt, 60 + }), 61 + parameters: [ 62 + numberParameter({ isBigInt, value: schema.exclusiveMinimum }), 63 + ], 64 + }); 65 + } else if (schema.minimum !== undefined) { 66 + result.expression = tsc.callExpression({ 67 + functionName: tsc.propertyAccessExpression({ 68 + expression: result.expression, 69 + name: identifiers.gte, 70 + }), 71 + parameters: [numberParameter({ isBigInt, value: schema.minimum })], 72 + }); 73 + } 74 + 75 + if (schema.exclusiveMaximum !== undefined) { 76 + result.expression = tsc.callExpression({ 77 + functionName: tsc.propertyAccessExpression({ 78 + expression: result.expression, 79 + name: identifiers.lt, 80 + }), 81 + parameters: [ 82 + numberParameter({ isBigInt, value: schema.exclusiveMaximum }), 83 + ], 84 + }); 85 + } else if (schema.maximum !== undefined) { 86 + result.expression = tsc.callExpression({ 87 + functionName: tsc.propertyAccessExpression({ 88 + expression: result.expression, 89 + name: identifiers.lte, 90 + }), 91 + parameters: [numberParameter({ isBigInt, value: schema.maximum })], 92 + }); 93 + } 94 + 95 + return result as Omit<Ast, 'typeName'>; 96 + };
+152
packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts
··· 1 + import ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import { numberRegExp } from '../../../../utils/regexp'; 5 + import type { SchemaWithType } from '../../../shared/types/schema'; 6 + import { identifiers } from '../../constants'; 7 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 + import { irSchemaToAst } from '../plugin'; 9 + 10 + export const objectToAst = ({ 11 + plugin, 12 + schema, 13 + state, 14 + }: IrSchemaToAstOptions & { 15 + schema: SchemaWithType<'object'>; 16 + }): Omit<Ast, 'typeName'> => { 17 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 18 + 19 + // TODO: parser - handle constants 20 + const properties: Array<ts.PropertyAssignment | ts.GetAccessorDeclaration> = 21 + []; 22 + 23 + const required = schema.required ?? []; 24 + 25 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 26 + 27 + for (const name in schema.properties) { 28 + const property = schema.properties[name]!; 29 + const isRequired = required.includes(name); 30 + 31 + const propertySchema = irSchemaToAst({ 32 + optional: !isRequired, 33 + plugin, 34 + schema: property, 35 + state: { 36 + ...state, 37 + _path: [...state._path, 'properties', name], 38 + }, 39 + }); 40 + if (propertySchema.hasCircularReference) { 41 + result.hasCircularReference = true; 42 + } 43 + 44 + numberRegExp.lastIndex = 0; 45 + let propertyName; 46 + if (numberRegExp.test(name)) { 47 + // For numeric literals, we'll handle negative numbers by using a string literal 48 + // instead of trying to use a PrefixUnaryExpression 49 + propertyName = name.startsWith('-') 50 + ? ts.factory.createStringLiteral(name) 51 + : ts.factory.createNumericLiteral(name); 52 + } else { 53 + propertyName = name; 54 + } 55 + // TODO: parser - abstract safe property name logic 56 + if ( 57 + ((name.match(/^[0-9]/) && name.match(/\D+/g)) || name.match(/\W/g)) && 58 + !name.startsWith("'") && 59 + !name.endsWith("'") 60 + ) { 61 + propertyName = `'${name}'`; 62 + } 63 + 64 + if (propertySchema.hasCircularReference) { 65 + properties.push( 66 + tsc.getAccessorDeclaration({ 67 + name: propertyName, 68 + // @ts-expect-error 69 + returnType: propertySchema.typeName 70 + ? tsc.propertyAccessExpression({ 71 + expression: z.placeholder, 72 + name: propertySchema.typeName, 73 + }) 74 + : undefined, 75 + statements: [ 76 + tsc.returnStatement({ 77 + expression: propertySchema.expression, 78 + }), 79 + ], 80 + }), 81 + ); 82 + } else { 83 + properties.push( 84 + tsc.propertyAssignment({ 85 + initializer: propertySchema.expression, 86 + name: propertyName, 87 + }), 88 + ); 89 + } 90 + } 91 + 92 + if ( 93 + schema.additionalProperties && 94 + (!schema.properties || !Object.keys(schema.properties).length) 95 + ) { 96 + const zodSchema = irSchemaToAst({ 97 + plugin, 98 + schema: schema.additionalProperties, 99 + state: { 100 + ...state, 101 + _path: [...state._path, 'additionalProperties'], 102 + }, 103 + }); 104 + result.expression = tsc.callExpression({ 105 + functionName: tsc.propertyAccessExpression({ 106 + expression: z.placeholder, 107 + name: identifiers.record, 108 + }), 109 + parameters: [ 110 + tsc.callExpression({ 111 + functionName: tsc.propertyAccessExpression({ 112 + expression: z.placeholder, 113 + name: identifiers.string, 114 + }), 115 + parameters: [], 116 + }), 117 + zodSchema.expression, 118 + ], 119 + }); 120 + if (zodSchema.hasCircularReference) { 121 + result.hasCircularReference = true; 122 + } 123 + 124 + // Return with typeName for circular references 125 + if (result.hasCircularReference) { 126 + return { 127 + ...result, 128 + typeName: 'ZodType', 129 + } as Ast; 130 + } 131 + 132 + return result as Omit<Ast, 'typeName'>; 133 + } 134 + 135 + result.expression = tsc.callExpression({ 136 + functionName: tsc.propertyAccessExpression({ 137 + expression: z.placeholder, 138 + name: identifiers.object, 139 + }), 140 + parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 141 + }); 142 + 143 + // Return with typeName for circular references (AnyZodObject doesn't exist in Zod v4, use ZodType) 144 + if (result.hasCircularReference) { 145 + return { 146 + ...result, 147 + typeName: 'ZodType', 148 + } as Ast; 149 + } 150 + 151 + return result as Omit<Ast, 'typeName'>; 152 + };
+170
packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const stringToAst = ({ 7 + plugin, 8 + schema, 9 + }: IrSchemaToAstOptions & { 10 + schema: SchemaWithType<'string'>; 11 + }): Omit<Ast, 'typeName'> => { 12 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 13 + 14 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 15 + 16 + if (typeof schema.const === 'string') { 17 + result.expression = tsc.callExpression({ 18 + functionName: tsc.propertyAccessExpression({ 19 + expression: z.placeholder, 20 + name: identifiers.literal, 21 + }), 22 + parameters: [tsc.ots.string(schema.const)], 23 + }); 24 + return result as Omit<Ast, 'typeName'>; 25 + } 26 + 27 + result.expression = tsc.callExpression({ 28 + functionName: tsc.propertyAccessExpression({ 29 + expression: z.placeholder, 30 + name: identifiers.string, 31 + }), 32 + }); 33 + 34 + const dateTimeOptions: { key: string; value: boolean }[] = []; 35 + 36 + if (plugin.config.dates.offset) { 37 + dateTimeOptions.push({ key: 'offset', value: true }); 38 + } 39 + if (plugin.config.dates.local) { 40 + dateTimeOptions.push({ key: 'local', value: true }); 41 + } 42 + 43 + if (schema.format) { 44 + switch (schema.format) { 45 + case 'date': 46 + result.expression = tsc.callExpression({ 47 + functionName: tsc.propertyAccessExpression({ 48 + expression: tsc.propertyAccessExpression({ 49 + expression: z.placeholder, 50 + name: identifiers.iso, 51 + }), 52 + name: identifiers.date, 53 + }), 54 + }); 55 + break; 56 + case 'date-time': 57 + result.expression = tsc.callExpression({ 58 + functionName: tsc.propertyAccessExpression({ 59 + expression: tsc.propertyAccessExpression({ 60 + expression: z.placeholder, 61 + name: identifiers.iso, 62 + }), 63 + name: identifiers.datetime, 64 + }), 65 + parameters: 66 + dateTimeOptions.length > 0 67 + ? [ 68 + tsc.objectExpression({ 69 + obj: dateTimeOptions, 70 + }), 71 + ] 72 + : [], 73 + }); 74 + break; 75 + case 'email': 76 + result.expression = tsc.callExpression({ 77 + functionName: tsc.propertyAccessExpression({ 78 + expression: z.placeholder, 79 + name: identifiers.email, 80 + }), 81 + }); 82 + break; 83 + case 'ipv4': 84 + result.expression = tsc.callExpression({ 85 + functionName: tsc.propertyAccessExpression({ 86 + expression: z.placeholder, 87 + name: identifiers.ipv4, 88 + }), 89 + }); 90 + break; 91 + case 'ipv6': 92 + result.expression = tsc.callExpression({ 93 + functionName: tsc.propertyAccessExpression({ 94 + expression: z.placeholder, 95 + name: identifiers.ipv6, 96 + }), 97 + }); 98 + break; 99 + case 'time': 100 + result.expression = tsc.callExpression({ 101 + functionName: tsc.propertyAccessExpression({ 102 + expression: tsc.propertyAccessExpression({ 103 + expression: z.placeholder, 104 + name: identifiers.iso, 105 + }), 106 + name: identifiers.time, 107 + }), 108 + }); 109 + break; 110 + case 'uri': 111 + result.expression = tsc.callExpression({ 112 + functionName: tsc.propertyAccessExpression({ 113 + expression: z.placeholder, 114 + name: identifiers.url, 115 + }), 116 + }); 117 + break; 118 + case 'uuid': 119 + result.expression = tsc.callExpression({ 120 + functionName: tsc.propertyAccessExpression({ 121 + expression: z.placeholder, 122 + name: identifiers.uuid, 123 + }), 124 + }); 125 + break; 126 + } 127 + } 128 + 129 + if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 130 + result.expression = tsc.callExpression({ 131 + functionName: tsc.propertyAccessExpression({ 132 + expression: result.expression, 133 + name: identifiers.length, 134 + }), 135 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 136 + }); 137 + } else { 138 + if (schema.minLength !== undefined) { 139 + result.expression = tsc.callExpression({ 140 + functionName: tsc.propertyAccessExpression({ 141 + expression: result.expression, 142 + name: identifiers.min, 143 + }), 144 + parameters: [tsc.valueToExpression({ value: schema.minLength })], 145 + }); 146 + } 147 + 148 + if (schema.maxLength !== undefined) { 149 + result.expression = tsc.callExpression({ 150 + functionName: tsc.propertyAccessExpression({ 151 + expression: result.expression, 152 + name: identifiers.max, 153 + }), 154 + parameters: [tsc.valueToExpression({ value: schema.maxLength })], 155 + }); 156 + } 157 + } 158 + 159 + if (schema.pattern) { 160 + result.expression = tsc.callExpression({ 161 + functionName: tsc.propertyAccessExpression({ 162 + expression: result.expression, 163 + name: identifiers.regex, 164 + }), 165 + parameters: [tsc.regularExpressionLiteral({ text: schema.pattern })], 166 + }); 167 + } 168 + 169 + return result as Omit<Ast, 'typeName'>; 170 + };
+76
packages/openapi-ts/src/plugins/zod/v4/toAst/tuple.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import { tsc } from '../../../../tsc'; 4 + import type { SchemaWithType } from '../../../shared/types/schema'; 5 + import { identifiers } from '../../constants'; 6 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import { irSchemaToAst } from '../plugin'; 8 + 9 + export const tupleToAst = ({ 10 + plugin, 11 + schema, 12 + state, 13 + }: IrSchemaToAstOptions & { 14 + schema: SchemaWithType<'tuple'>; 15 + }): Omit<Ast, 'typeName'> => { 16 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 17 + 18 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 19 + 20 + if (schema.const && Array.isArray(schema.const)) { 21 + const tupleElements = schema.const.map((value) => 22 + tsc.callExpression({ 23 + functionName: tsc.propertyAccessExpression({ 24 + expression: z.placeholder, 25 + name: identifiers.literal, 26 + }), 27 + parameters: [tsc.valueToExpression({ value })], 28 + }), 29 + ); 30 + result.expression = tsc.callExpression({ 31 + functionName: tsc.propertyAccessExpression({ 32 + expression: z.placeholder, 33 + name: identifiers.tuple, 34 + }), 35 + parameters: [ 36 + tsc.arrayLiteralExpression({ 37 + elements: tupleElements, 38 + }), 39 + ], 40 + }); 41 + return result as Omit<Ast, 'typeName'>; 42 + } 43 + 44 + const tupleElements: Array<ts.Expression> = []; 45 + 46 + if (schema.items) { 47 + schema.items.forEach((item, index) => { 48 + const itemSchema = irSchemaToAst({ 49 + plugin, 50 + schema: item, 51 + state: { 52 + ...state, 53 + _path: [...state._path, 'items', index], 54 + }, 55 + }); 56 + tupleElements.push(itemSchema.expression); 57 + if (itemSchema.hasCircularReference) { 58 + result.hasCircularReference = true; 59 + } 60 + }); 61 + } 62 + 63 + result.expression = tsc.callExpression({ 64 + functionName: tsc.propertyAccessExpression({ 65 + expression: z.placeholder, 66 + name: identifiers.tuple, 67 + }), 68 + parameters: [ 69 + tsc.arrayLiteralExpression({ 70 + elements: tupleElements, 71 + }), 72 + ], 73 + }); 74 + 75 + return result as Omit<Ast, 'typeName'>; 76 + };
+20
packages/openapi-ts/src/plugins/zod/v4/toAst/undefined.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const undefinedToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'undefined'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.undefined, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+20
packages/openapi-ts/src/plugins/zod/v4/toAst/unknown.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const unknownToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'unknown'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.unknown, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+20
packages/openapi-ts/src/plugins/zod/v4/toAst/void.ts
··· 1 + import { tsc } from '../../../../tsc'; 2 + import type { SchemaWithType } from '../../../shared/types/schema'; 3 + import { identifiers } from '../../constants'; 4 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 5 + 6 + export const voidToAst = ({ 7 + plugin, 8 + }: IrSchemaToAstOptions & { 9 + schema: SchemaWithType<'void'>; 10 + }): Omit<Ast, 'typeName'> => { 11 + const result: Partial<Omit<Ast, 'typeName'>> = {}; 12 + const z = plugin.referenceSymbol(plugin.api.getSelector('external', 'zod.z')); 13 + result.expression = tsc.callExpression({ 14 + functionName: tsc.propertyAccessExpression({ 15 + expression: z.placeholder, 16 + name: identifiers.void, 17 + }), 18 + }); 19 + return result as Omit<Ast, 'typeName'>; 20 + };
+1 -1
packages/openapi-ts/src/utils/escape.ts
··· 1 - import { EOL } from 'os'; 1 + import { EOL } from 'node:os'; 2 2 3 3 import { validTypescriptIdentifierRegExp } from './regexp'; 4 4
+174 -143
pnpm-lock.yaml
··· 1336 1336 version: 10.4.3 1337 1337 nuxt: 1338 1338 specifier: 3.14.1592 1339 - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.6.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.44)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 1339 + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.6.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.44)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.3)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 1340 1340 ofetch: 1341 1341 specifier: 1.4.1 1342 1342 version: 1.4.1 ··· 1427 1427 '@types/cross-spawn': 1428 1428 specifier: 6.0.6 1429 1429 version: 6.0.6 1430 + arktype: 1431 + specifier: 2.1.23 1432 + version: 2.1.23 1430 1433 axios: 1431 1434 specifier: 1.8.2 1432 1435 version: 1.8.2 ··· 2123 2126 '@arethetypeswrong/core@0.17.4': 2124 2127 resolution: {integrity: sha512-Izvir8iIoU+X4SKtDAa5kpb+9cpifclzsbA8x/AZY0k0gIfXYQ1fa1B6Epfe6vNA2YfDX8VtrZFgvnXB6aPEoQ==} 2125 2128 engines: {node: '>=18'} 2129 + 2130 + '@ark/regex@0.0.0': 2131 + resolution: {integrity: sha512-p4vsWnd/LRGOdGQglbwOguIVhPmCAf5UzquvnDoxqhhPWTP84wWgi1INea8MgJ4SnI2gp37f13oA4Waz9vwNYg==} 2132 + 2133 + '@ark/schema@0.50.0': 2134 + resolution: {integrity: sha512-hfmP82GltBZDadIOeR3argKNlYYyB2wyzHp0eeAqAOFBQguglMV/S7Ip2q007bRtKxIMLDqFY6tfPie1dtssaQ==} 2135 + 2136 + '@ark/util@0.50.0': 2137 + resolution: {integrity: sha512-tIkgIMVRpkfXRQIEf0G2CJryZVtHVrqcWHMDa5QKo0OEEBu0tHkRSIMm4Ln8cd8Bn9TPZtvc/kE2Gma8RESPSg==} 2126 2138 2127 2139 '@babel/code-frame@7.27.1': 2128 2140 resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} ··· 7267 7279 aria-query@5.3.2: 7268 7280 resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} 7269 7281 engines: {node: '>= 0.4'} 7282 + 7283 + arktype@2.1.23: 7284 + resolution: {integrity: sha512-tyxNWX6xJVMb2EPJJ3OjgQS1G/vIeQRrZuY4DeBNQmh8n7geS+czgbauQWB6Pr+RXiOO8ChEey44XdmxsqGmfQ==} 7270 7285 7271 7286 array-buffer-byte-length@1.0.2: 7272 7287 resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} ··· 14220 14235 dependencies: 14221 14236 '@ampproject/remapping': 2.3.0 14222 14237 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) 14223 - '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.0)) 14238 + '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0))(webpack@5.98.0) 14224 14239 '@angular-devkit/core': 19.2.0(chokidar@4.0.3) 14225 14240 '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.6.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) 14226 14241 '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) ··· 14234 14249 '@babel/preset-env': 7.26.9(@babel/core@7.26.9) 14235 14250 '@babel/runtime': 7.26.9 14236 14251 '@discoveryjs/json-ext': 0.6.3 14237 - '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14252 + '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0) 14238 14253 '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 14239 14254 ansi-colors: 4.1.3 14240 14255 autoprefixer: 10.4.20(postcss@8.5.2) 14241 - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) 14256 + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) 14242 14257 browserslist: 4.25.4 14243 14258 copy-webpack-plugin: 12.0.2(webpack@5.98.0) 14244 14259 css-loader: 7.1.2(webpack@5.98.0) ··· 14258 14273 picomatch: 4.0.2 14259 14274 piscina: 4.8.0 14260 14275 postcss: 8.5.2 14261 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14276 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) 14262 14277 resolve-url-loader: 5.0.0 14263 14278 rxjs: 7.8.1 14264 14279 sass: 1.85.0 ··· 14308 14323 dependencies: 14309 14324 '@ampproject/remapping': 2.3.0 14310 14325 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) 14311 - '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.0)) 14326 + '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0))(webpack@5.98.0) 14312 14327 '@angular-devkit/core': 19.2.0(chokidar@4.0.3) 14313 14328 '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.6.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) 14314 14329 '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) ··· 14322 14337 '@babel/preset-env': 7.26.9(@babel/core@7.26.9) 14323 14338 '@babel/runtime': 7.26.9 14324 14339 '@discoveryjs/json-ext': 0.6.3 14325 - '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14340 + '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0) 14326 14341 '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 14327 14342 ansi-colors: 4.1.3 14328 14343 autoprefixer: 10.4.20(postcss@8.5.2) 14329 - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) 14344 + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) 14330 14345 browserslist: 4.25.4 14331 14346 copy-webpack-plugin: 12.0.2(webpack@5.98.0) 14332 14347 css-loader: 7.1.2(webpack@5.98.0) ··· 14346 14361 picomatch: 4.0.2 14347 14362 piscina: 4.8.0 14348 14363 postcss: 8.5.2 14349 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14364 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) 14350 14365 resolve-url-loader: 5.0.0 14351 14366 rxjs: 7.8.1 14352 14367 sass: 1.85.0 ··· 14434 14449 picomatch: 4.0.2 14435 14450 piscina: 4.8.0 14436 14451 postcss: 8.5.2 14437 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14452 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) 14438 14453 resolve-url-loader: 5.0.0 14439 14454 rxjs: 7.8.1 14440 14455 sass: 1.85.0 ··· 14484 14499 dependencies: 14485 14500 '@ampproject/remapping': 2.3.0 14486 14501 '@angular-devkit/architect': 0.1902.18(chokidar@4.0.3) 14487 - '@angular-devkit/build-webpack': 0.1902.18(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0) 14502 + '@angular-devkit/build-webpack': 0.1902.18(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.4)) 14488 14503 '@angular-devkit/core': 19.2.18(chokidar@4.0.3) 14489 14504 '@angular/build': 19.2.18(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.6.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.9.3)))(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.0) 14490 14505 '@angular/compiler-cli': 19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3) ··· 14498 14513 '@babel/preset-env': 7.26.9(@babel/core@7.26.10) 14499 14514 '@babel/runtime': 7.26.10 14500 14515 '@discoveryjs/json-ext': 0.6.3 14501 - '@ngtools/webpack': 19.2.18(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.98.0) 14516 + '@ngtools/webpack': 19.2.18(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.98.0(esbuild@0.25.4)) 14502 14517 '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 14503 14518 ansi-colors: 4.1.3 14504 14519 autoprefixer: 10.4.20(postcss@8.5.2) ··· 14522 14537 picomatch: 4.0.2 14523 14538 piscina: 4.8.0 14524 14539 postcss: 8.5.2 14525 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.9.3)(webpack@5.98.0) 14540 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.9.3)(webpack@5.98.0(esbuild@0.25.4)) 14526 14541 resolve-url-loader: 5.0.0 14527 14542 rxjs: 7.8.1 14528 14543 sass: 1.85.0 ··· 14567 14582 - webpack-cli 14568 14583 - yaml 14569 14584 14570 - '@angular-devkit/build-webpack@0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.0))': 14585 + '@angular-devkit/build-webpack@0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0))(webpack@5.98.0)': 14571 14586 dependencies: 14572 14587 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) 14573 14588 rxjs: 7.8.1 14574 - webpack: 5.98.0(esbuild@0.25.0) 14589 + webpack: 5.98.0(esbuild@0.25.4) 14575 14590 webpack-dev-server: 5.2.0(webpack@5.98.0) 14576 14591 transitivePeerDependencies: 14577 14592 - chokidar ··· 14580 14595 dependencies: 14581 14596 '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) 14582 14597 rxjs: 7.8.1 14583 - webpack: 5.98.0(esbuild@0.25.0) 14598 + webpack: 5.98.0(esbuild@0.25.4) 14584 14599 webpack-dev-server: 5.2.2(webpack@5.98.0) 14585 14600 transitivePeerDependencies: 14586 14601 - chokidar 14587 14602 14588 - '@angular-devkit/build-webpack@0.1902.18(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0)': 14603 + '@angular-devkit/build-webpack@0.1902.18(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.4))': 14589 14604 dependencies: 14590 14605 '@angular-devkit/architect': 0.1902.18(chokidar@4.0.3) 14591 14606 rxjs: 7.8.1 14592 - webpack: 5.98.0(esbuild@0.25.0) 14607 + webpack: 5.98.0(esbuild@0.25.4) 14593 14608 webpack-dev-server: 5.2.2(webpack@5.98.0) 14594 14609 transitivePeerDependencies: 14595 14610 - chokidar ··· 15182 15197 typescript: 5.6.1-rc 15183 15198 validate-npm-package-name: 5.0.1 15184 15199 15200 + '@ark/regex@0.0.0': 15201 + dependencies: 15202 + '@ark/util': 0.50.0 15203 + 15204 + '@ark/schema@0.50.0': 15205 + dependencies: 15206 + '@ark/util': 0.50.0 15207 + 15208 + '@ark/util@0.50.0': {} 15209 + 15185 15210 '@babel/code-frame@7.27.1': 15186 15211 dependencies: 15187 15212 '@babel/helper-validator-identifier': 7.27.1 ··· 17975 18000 '@next/swc-win32-x64-msvc@15.2.4': 17976 18001 optional: true 17977 18002 17978 - '@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0))': 18003 + '@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0)': 17979 18004 dependencies: 17980 18005 '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) 17981 18006 typescript: 5.8.3 17982 - webpack: 5.98.0(esbuild@0.25.0) 18007 + webpack: 5.98.0(esbuild@0.25.4) 17983 18008 17984 18009 '@ngtools/webpack@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0)': 17985 18010 dependencies: 17986 18011 '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) 17987 18012 typescript: 5.8.3 17988 - webpack: 5.98.0(esbuild@0.25.0) 18013 + webpack: 5.98.0(esbuild@0.25.4) 17989 18014 17990 - '@ngtools/webpack@19.2.18(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.98.0)': 18015 + '@ngtools/webpack@19.2.18(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.98.0(esbuild@0.25.4))': 17991 18016 dependencies: 17992 18017 '@angular/compiler-cli': 19.2.15(@angular/compiler@19.2.15)(typescript@5.9.3) 17993 18018 typescript: 5.9.3 17994 - webpack: 5.98.0(esbuild@0.25.0) 18019 + webpack: 5.98.0(esbuild@0.25.4) 17995 18020 17996 18021 '@nodelib/fs.scandir@2.1.5': 17997 18022 dependencies: ··· 18068 18093 18069 18094 '@nuxt/devalue@2.0.2': {} 18070 18095 18071 - '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 18096 + '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 18072 18097 dependencies: 18073 18098 '@nuxt/kit': 3.15.4(magicast@0.3.5) 18074 18099 '@nuxt/schema': 3.16.2 18075 18100 execa: 7.2.0 18076 - vite: 7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18101 + vite: 6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18077 18102 transitivePeerDependencies: 18078 18103 - magicast 18079 18104 - supports-color 18080 18105 18081 - '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))': 18106 + '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 18082 18107 dependencies: 18083 18108 '@nuxt/kit': 3.15.4(magicast@0.3.5) 18084 18109 '@nuxt/schema': 3.16.2 18085 18110 execa: 7.2.0 18086 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 18111 + vite: 7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18087 18112 transitivePeerDependencies: 18088 18113 - magicast 18089 18114 - supports-color 18090 18115 18091 - '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 18116 + '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))': 18092 18117 dependencies: 18093 18118 '@nuxt/kit': 3.15.4(magicast@0.3.5) 18094 18119 '@nuxt/schema': 3.16.2 18095 18120 execa: 7.2.0 18096 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18121 + vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 18097 18122 transitivePeerDependencies: 18098 18123 - magicast 18099 18124 - supports-color ··· 18158 18183 - utf-8-validate 18159 18184 - vue 18160 18185 18161 - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 18186 + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 18162 18187 dependencies: 18163 18188 '@antfu/utils': 0.7.10 18189 + '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18190 + '@nuxt/devtools-wizard': 1.7.0 18191 + '@nuxt/kit': 3.15.4(magicast@0.3.5) 18192 + '@vue/devtools-core': 7.6.8(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3)) 18193 + '@vue/devtools-kit': 7.6.8 18194 + birpc: 0.2.19 18195 + consola: 3.4.2 18196 + cronstrue: 2.59.0 18197 + destr: 2.0.5 18198 + error-stack-parser-es: 0.1.5 18199 + execa: 7.2.0 18200 + fast-npm-meta: 0.2.2 18201 + flatted: 3.3.3 18202 + get-port-please: 3.2.0 18203 + hookable: 5.5.3 18204 + image-meta: 0.2.1 18205 + is-installed-globally: 1.0.0 18206 + launch-editor: 2.11.1 18207 + local-pkg: 0.5.1 18208 + magicast: 0.3.5 18209 + nypm: 0.4.1 18210 + ohash: 1.1.6 18211 + pathe: 1.1.2 18212 + perfect-debounce: 1.0.0 18213 + pkg-types: 1.3.1 18214 + rc9: 2.1.2 18215 + scule: 1.3.0 18216 + semver: 7.7.2 18217 + simple-git: 3.28.0 18218 + sirv: 3.0.1 18219 + tinyglobby: 0.2.14 18220 + unimport: 3.14.6(rollup@4.50.0) 18221 + vite: 6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18222 + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18223 + vite-plugin-vue-inspector: 5.3.2(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18224 + which: 3.0.1 18225 + ws: 8.18.3 18226 + transitivePeerDependencies: 18227 + - bufferutil 18228 + - rollup 18229 + - supports-color 18230 + - utf-8-validate 18231 + - vue 18232 + 18233 + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 18234 + dependencies: 18235 + '@antfu/utils': 0.7.10 18164 18236 '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18165 18237 '@nuxt/devtools-wizard': 1.7.0 18166 18238 '@nuxt/kit': 3.15.4(magicast@0.3.5) ··· 18242 18315 vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 18243 18316 vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18244 18317 vite-plugin-vue-inspector: 5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18245 - which: 3.0.1 18246 - ws: 8.18.3 18247 - transitivePeerDependencies: 18248 - - bufferutil 18249 - - rollup 18250 - - supports-color 18251 - - utf-8-validate 18252 - - vue 18253 - 18254 - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 18255 - dependencies: 18256 - '@antfu/utils': 0.7.10 18257 - '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18258 - '@nuxt/devtools-wizard': 1.7.0 18259 - '@nuxt/kit': 3.15.4(magicast@0.3.5) 18260 - '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3)) 18261 - '@vue/devtools-kit': 7.6.8 18262 - birpc: 0.2.19 18263 - consola: 3.4.2 18264 - cronstrue: 2.59.0 18265 - destr: 2.0.5 18266 - error-stack-parser-es: 0.1.5 18267 - execa: 7.2.0 18268 - fast-npm-meta: 0.2.2 18269 - flatted: 3.3.3 18270 - get-port-please: 3.2.0 18271 - hookable: 5.5.3 18272 - image-meta: 0.2.1 18273 - is-installed-globally: 1.0.0 18274 - launch-editor: 2.11.1 18275 - local-pkg: 0.5.1 18276 - magicast: 0.3.5 18277 - nypm: 0.4.1 18278 - ohash: 1.1.6 18279 - pathe: 1.1.2 18280 - perfect-debounce: 1.0.0 18281 - pkg-types: 1.3.1 18282 - rc9: 2.1.2 18283 - scule: 1.3.0 18284 - semver: 7.7.2 18285 - simple-git: 3.28.0 18286 - sirv: 3.0.1 18287 - tinyglobby: 0.2.14 18288 - unimport: 3.14.6(rollup@4.50.0) 18289 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18290 - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18291 - vite-plugin-vue-inspector: 5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18292 18318 which: 3.0.1 18293 18319 ws: 8.18.3 18294 18320 transitivePeerDependencies: ··· 21041 21066 dependencies: 21042 21067 '@vue/devtools-kit': 8.0.2 21043 21068 21069 + '@vue/devtools-core@7.6.8(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 21070 + dependencies: 21071 + '@vue/devtools-kit': 7.7.7 21072 + '@vue/devtools-shared': 7.7.7 21073 + mitt: 3.0.1 21074 + nanoid: 5.1.5 21075 + pathe: 1.1.2 21076 + vite-hot-client: 0.2.4(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 21077 + vue: 3.5.13(typescript@5.9.3) 21078 + transitivePeerDependencies: 21079 + - vite 21080 + 21044 21081 '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 21045 21082 dependencies: 21046 21083 '@vue/devtools-kit': 7.7.7 ··· 21061 21098 nanoid: 5.1.5 21062 21099 pathe: 1.1.2 21063 21100 vite-hot-client: 0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 21064 - vue: 3.5.13(typescript@5.9.3) 21065 - transitivePeerDependencies: 21066 - - vite 21067 - 21068 - '@vue/devtools-core@7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3))': 21069 - dependencies: 21070 - '@vue/devtools-kit': 7.7.7 21071 - '@vue/devtools-shared': 7.7.7 21072 - mitt: 3.0.1 21073 - nanoid: 5.1.5 21074 - pathe: 1.1.2 21075 - vite-hot-client: 0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 21076 21101 vue: 3.5.13(typescript@5.9.3) 21077 21102 transitivePeerDependencies: 21078 21103 - vite ··· 21524 21549 21525 21550 aria-query@5.3.2: {} 21526 21551 21552 + arktype@2.1.23: 21553 + dependencies: 21554 + '@ark/regex': 0.0.0 21555 + '@ark/schema': 0.50.0 21556 + '@ark/util': 0.50.0 21557 + 21527 21558 array-buffer-byte-length@1.0.2: 21528 21559 dependencies: 21529 21560 call-bound: 1.0.4 ··· 21694 21725 '@babel/core': 7.26.10 21695 21726 find-cache-dir: 4.0.0 21696 21727 schema-utils: 4.3.2 21697 - webpack: 5.98.0(esbuild@0.25.0) 21728 + webpack: 5.98.0(esbuild@0.25.4) 21698 21729 21699 - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): 21730 + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): 21700 21731 dependencies: 21701 21732 '@babel/core': 7.26.9 21702 21733 find-cache-dir: 4.0.0 21703 21734 schema-utils: 4.3.2 21704 - webpack: 5.98.0(esbuild@0.25.0) 21735 + webpack: 5.98.0(esbuild@0.25.4) 21705 21736 21706 21737 babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.10): 21707 21738 dependencies: ··· 22285 22316 normalize-path: 3.0.0 22286 22317 schema-utils: 4.3.2 22287 22318 serialize-javascript: 6.0.2 22288 - webpack: 5.98.0(esbuild@0.25.0) 22319 + webpack: 5.98.0(esbuild@0.25.4) 22289 22320 22290 22321 core-js-compat@3.45.1: 22291 22322 dependencies: ··· 22364 22395 postcss-value-parser: 4.2.0 22365 22396 semver: 7.7.2 22366 22397 optionalDependencies: 22367 - webpack: 5.98.0(esbuild@0.25.0) 22398 + webpack: 5.98.0(esbuild@0.25.4) 22368 22399 22369 22400 css-select@5.2.2: 22370 22401 dependencies: ··· 23128 23159 '@typescript-eslint/parser': 8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3) 23129 23160 eslint: 9.17.0(jiti@2.6.1) 23130 23161 eslint-import-resolver-node: 0.3.9 23131 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)) 23162 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.6.1)) 23132 23163 eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.6.1)) 23133 23164 eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.6.1)) 23134 23165 eslint-plugin-react: 7.37.5(eslint@9.17.0(jiti@2.6.1)) ··· 23156 23187 transitivePeerDependencies: 23157 23188 - supports-color 23158 23189 23159 - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)): 23190 + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.6.1)): 23160 23191 dependencies: 23161 23192 '@nolyfill/is-core-module': 1.0.39 23162 23193 debug: 4.4.1 ··· 23171 23202 transitivePeerDependencies: 23172 23203 - supports-color 23173 23204 23174 - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)): 23205 + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.6.1)): 23175 23206 dependencies: 23176 23207 debug: 3.2.7 23177 23208 optionalDependencies: 23178 23209 '@typescript-eslint/parser': 8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3) 23179 23210 eslint: 9.17.0(jiti@2.6.1) 23180 23211 eslint-import-resolver-node: 0.3.9 23181 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)) 23212 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.6.1)) 23182 23213 transitivePeerDependencies: 23183 23214 - supports-color 23184 23215 ··· 23193 23224 doctrine: 2.1.0 23194 23225 eslint: 9.17.0(jiti@2.6.1) 23195 23226 eslint-import-resolver-node: 0.3.9 23196 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)) 23227 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.6.1)) 23197 23228 hasown: 2.0.2 23198 23229 is-core-module: 2.16.1 23199 23230 is-glob: 4.0.3 ··· 24901 24932 dependencies: 24902 24933 less: 4.2.2 24903 24934 optionalDependencies: 24904 - webpack: 5.98.0(esbuild@0.25.0) 24935 + webpack: 5.98.0(esbuild@0.25.4) 24905 24936 24906 24937 less@4.2.2: 24907 24938 dependencies: ··· 24926 24957 dependencies: 24927 24958 webpack-sources: 3.3.3 24928 24959 optionalDependencies: 24929 - webpack: 5.98.0(esbuild@0.25.0) 24960 + webpack: 5.98.0(esbuild@0.25.4) 24930 24961 24931 24962 light-my-request@6.6.0: 24932 24963 dependencies: ··· 25467 25498 dependencies: 25468 25499 schema-utils: 4.3.2 25469 25500 tapable: 2.2.3 25470 - webpack: 5.98.0(esbuild@0.25.0) 25501 + webpack: 5.98.0(esbuild@0.25.4) 25471 25502 25472 25503 minimalistic-assert@1.0.1: {} 25473 25504 ··· 26179 26210 - vue-tsc 26180 26211 - xml2js 26181 26212 26182 - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.6.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.44)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 26213 + nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.6.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.44)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.3)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 26183 26214 dependencies: 26184 26215 '@nuxt/devalue': 2.0.2 26185 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3)) 26216 + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3)) 26186 26217 '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) 26187 26218 '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) 26188 26219 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) ··· 26300 26331 - vue-tsc 26301 26332 - xml2js 26302 26333 26303 - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.6.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.44)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 26334 + nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.6.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.44)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 26304 26335 dependencies: 26305 26336 '@nuxt/devalue': 2.0.2 26306 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3)) 26337 + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.3)) 26307 26338 '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) 26308 26339 '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) 26309 26340 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) ··· 26943 26974 ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) 26944 26975 optional: true 26945 26976 26946 - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): 26977 + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): 26947 26978 dependencies: 26948 26979 cosmiconfig: 9.0.0(typescript@5.8.3) 26949 26980 jiti: 1.21.7 26950 26981 postcss: 8.5.2 26951 26982 semver: 7.7.2 26952 26983 optionalDependencies: 26953 - webpack: 5.98.0(esbuild@0.25.0) 26984 + webpack: 5.98.0(esbuild@0.25.4) 26954 26985 transitivePeerDependencies: 26955 26986 - typescript 26956 26987 26957 - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.9.3)(webpack@5.98.0): 26988 + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.9.3)(webpack@5.98.0(esbuild@0.25.4)): 26958 26989 dependencies: 26959 26990 cosmiconfig: 9.0.0(typescript@5.9.3) 26960 26991 jiti: 1.21.7 26961 26992 postcss: 8.5.2 26962 26993 semver: 7.7.2 26963 26994 optionalDependencies: 26964 - webpack: 5.98.0(esbuild@0.25.0) 26995 + webpack: 5.98.0(esbuild@0.25.4) 26965 26996 transitivePeerDependencies: 26966 26997 - typescript 26967 26998 ··· 27786 27817 neo-async: 2.6.2 27787 27818 optionalDependencies: 27788 27819 sass: 1.85.0 27789 - webpack: 5.98.0(esbuild@0.25.0) 27820 + webpack: 5.98.0(esbuild@0.25.4) 27790 27821 27791 27822 sass@1.85.0: 27792 27823 dependencies: ··· 28153 28184 dependencies: 28154 28185 iconv-lite: 0.6.3 28155 28186 source-map-js: 1.2.1 28156 - webpack: 5.98.0(esbuild@0.25.0) 28187 + webpack: 5.98.0(esbuild@0.25.4) 28157 28188 28158 28189 source-map-support@0.5.21: 28159 28190 dependencies: ··· 28586 28617 schema-utils: 4.3.2 28587 28618 serialize-javascript: 6.0.2 28588 28619 terser: 5.43.1 28589 - webpack: 5.98.0(esbuild@0.25.0) 28620 + webpack: 5.98.0(esbuild@0.25.4) 28590 28621 optionalDependencies: 28591 28622 esbuild: 0.25.0 28592 28623 ··· 28597 28628 schema-utils: 4.3.2 28598 28629 serialize-javascript: 6.0.2 28599 28630 terser: 5.43.1 28600 - webpack: 5.98.0(esbuild@0.25.0) 28631 + webpack: 5.98.0(esbuild@0.25.4) 28601 28632 optionalDependencies: 28602 28633 esbuild: 0.25.4 28603 28634 ··· 29399 29430 vite: 7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29400 29431 vite-hot-client: 2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 29401 29432 29433 + vite-hot-client@0.2.4(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29434 + dependencies: 29435 + vite: 6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29436 + 29402 29437 vite-hot-client@0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29403 29438 dependencies: 29404 29439 vite: 7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) ··· 29406 29441 vite-hot-client@0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)): 29407 29442 dependencies: 29408 29443 vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29409 - 29410 - vite-hot-client@0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29411 - dependencies: 29412 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29413 29444 29414 29445 vite-hot-client@2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29415 29446 dependencies: ··· 29555 29586 - rollup 29556 29587 - supports-color 29557 29588 29558 - vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29589 + vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29559 29590 dependencies: 29560 29591 '@antfu/utils': 0.7.10 29561 29592 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) ··· 29566 29597 perfect-debounce: 1.0.0 29567 29598 picocolors: 1.1.1 29568 29599 sirv: 3.0.1 29569 - vite: 7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29600 + vite: 6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29570 29601 optionalDependencies: 29571 29602 '@nuxt/kit': 3.15.4(magicast@0.3.5) 29572 29603 transitivePeerDependencies: 29573 29604 - rollup 29574 29605 - supports-color 29575 29606 29576 - vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)): 29607 + vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29577 29608 dependencies: 29578 29609 '@antfu/utils': 0.7.10 29579 29610 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) ··· 29584 29615 perfect-debounce: 1.0.0 29585 29616 picocolors: 1.1.1 29586 29617 sirv: 3.0.1 29587 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29618 + vite: 7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29588 29619 optionalDependencies: 29589 29620 '@nuxt/kit': 3.15.4(magicast@0.3.5) 29590 29621 transitivePeerDependencies: 29591 29622 - rollup 29592 29623 - supports-color 29593 29624 29594 - vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29625 + vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)): 29595 29626 dependencies: 29596 29627 '@antfu/utils': 0.7.10 29597 29628 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) ··· 29602 29633 perfect-debounce: 1.0.0 29603 29634 picocolors: 1.1.1 29604 29635 sirv: 3.0.1 29605 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29636 + vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29606 29637 optionalDependencies: 29607 29638 '@nuxt/kit': 3.15.4(magicast@0.3.5) 29608 29639 transitivePeerDependencies: ··· 29638 29670 - supports-color 29639 29671 - vue 29640 29672 29673 + vite-plugin-vue-inspector@5.3.2(vite@6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29674 + dependencies: 29675 + '@babel/core': 7.28.3 29676 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) 29677 + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) 29678 + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) 29679 + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) 29680 + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) 29681 + '@vue/compiler-dom': 3.5.21 29682 + kolorist: 1.8.0 29683 + magic-string: 0.30.18 29684 + vite: 6.3.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29685 + transitivePeerDependencies: 29686 + - supports-color 29687 + 29641 29688 vite-plugin-vue-inspector@5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29642 29689 dependencies: 29643 29690 '@babel/core': 7.28.3 ··· 29665 29712 kolorist: 1.8.0 29666 29713 magic-string: 0.30.18 29667 29714 vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29668 - transitivePeerDependencies: 29669 - - supports-color 29670 - 29671 - vite-plugin-vue-inspector@5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29672 - dependencies: 29673 - '@babel/core': 7.28.3 29674 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) 29675 - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) 29676 - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) 29677 - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) 29678 - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) 29679 - '@vue/compiler-dom': 3.5.21 29680 - kolorist: 1.8.0 29681 - magic-string: 0.30.18 29682 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29683 29715 transitivePeerDependencies: 29684 29716 - supports-color 29685 29717 ··· 30147 30179 range-parser: 1.2.1 30148 30180 schema-utils: 4.3.2 30149 30181 optionalDependencies: 30150 - webpack: 5.98.0(esbuild@0.25.0) 30182 + webpack: 5.98.0(esbuild@0.25.4) 30151 30183 30152 30184 webpack-dev-server@5.2.0(webpack@5.98.0): 30153 30185 dependencies: ··· 30179 30211 webpack-dev-middleware: 7.4.2(webpack@5.98.0) 30180 30212 ws: 8.18.3 30181 30213 optionalDependencies: 30182 - webpack: 5.98.0(esbuild@0.25.0) 30214 + webpack: 5.98.0(esbuild@0.25.4) 30183 30215 transitivePeerDependencies: 30184 30216 - bufferutil 30185 30217 - debug ··· 30217 30249 webpack-dev-middleware: 7.4.2(webpack@5.98.0) 30218 30250 ws: 8.18.3 30219 30251 optionalDependencies: 30220 - webpack: 5.98.0(esbuild@0.25.0) 30252 + webpack: 5.98.0(esbuild@0.25.4) 30221 30253 transitivePeerDependencies: 30222 30254 - bufferutil 30223 30255 - debug ··· 30235 30267 webpack-subresource-integrity@5.1.0(webpack@5.98.0): 30236 30268 dependencies: 30237 30269 typed-assert: 1.0.9 30238 - webpack: 5.98.0(esbuild@0.25.0) 30270 + webpack: 5.98.0(esbuild@0.25.4) 30239 30271 30240 30272 webpack-virtual-modules@0.6.2: {} 30241 30273