orval 8.12.3 → 8.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SupportedFormatter, asyncReduce, collectReferencedComponents, conventionName, createSuccessMessage, dynamicImport, fixCrossDirectoryImports, fixRegularSchemaImports, generateComponentDefinition, generateDependencyImports, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getBaseUrlRuntimeImports, getFileInfo, getFullRoute, getMockFileExtensionByTypeName, getRoute, isBoolean, isFunction, isNullish, isObject, isReference, isString, isUrl, jsDoc, log, logError, logVerbose, logWarning, pascal, removeFilesAndEmptyFolders, resolveInstalledVersions, resolveRef, splitSchemasByType, upath, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode } from "@orval/core";
|
|
2
|
+
import { FormDataArrayHandling, GetterPropType, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SupportedFormatter, asyncReduce, collectReferencedComponents, conventionName, createSuccessMessage, dynamicImport, fixCrossDirectoryImports, fixRegularSchemaImports, generateComponentDefinition, generateDependencyImports, generateMutator, generateParameterDefinition, generateSchemasDefinition, generateVerbsOptions, getBaseUrlRuntimeImports, getFileInfo, getFullRoute, getImportExtension, getMockFileExtensionByTypeName, getRefInfo, getRoute, isBoolean, isComponentRef, isFunction, isNullish, isObject, isReference, isString, isUrl, jsDoc, log, logError, logVerbose, logWarning, pascal, removeFilesAndEmptyFolders, resolveInstalledVersions, resolveRef, resolveValue, splitSchemasByType, upath, writeGeneratedFile, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode } from "@orval/core";
|
|
3
3
|
import { bundle } from "@scalar/json-magic/bundle";
|
|
4
4
|
import { fetchUrls, parseJson, parseYaml, readFiles } from "@scalar/json-magic/bundle/plugins/node";
|
|
5
5
|
import { upgrade, validate } from "@scalar/openapi-parser";
|
|
6
6
|
import { isNullish as isNullish$1, pick, unique } from "remeda";
|
|
7
7
|
import * as mock from "@orval/mock";
|
|
8
|
-
import { generateMockImports, getDefaultMockOptionsForType } from "@orval/mock";
|
|
8
|
+
import { generateFakerForSchemas, generateMockImports, getDefaultMockOptionsForType } from "@orval/mock";
|
|
9
9
|
import angular from "@orval/angular";
|
|
10
10
|
import axios from "@orval/axios";
|
|
11
|
+
import effect from "@orval/effect";
|
|
11
12
|
import fetchClient from "@orval/fetch";
|
|
12
13
|
import hono from "@orval/hono";
|
|
13
14
|
import mcp from "@orval/mcp";
|
|
@@ -28,7 +29,7 @@ import { createJiti } from "jiti";
|
|
|
28
29
|
//#region package.json
|
|
29
30
|
var name = "orval";
|
|
30
31
|
var description = "A swagger client generator for typescript";
|
|
31
|
-
var version = "8.
|
|
32
|
+
var version = "8.14.0";
|
|
32
33
|
//#endregion
|
|
33
34
|
//#region src/client.ts
|
|
34
35
|
const DEFAULT_CLIENT = OutputClient.AXIOS;
|
|
@@ -61,6 +62,7 @@ const getGeneratorClient = (outputClient, output) => {
|
|
|
61
62
|
})(),
|
|
62
63
|
swr: swr()(),
|
|
63
64
|
zod: zod()(),
|
|
65
|
+
effect: effect()(),
|
|
64
66
|
hono: hono()(),
|
|
65
67
|
fetch: fetchClient()(),
|
|
66
68
|
mcp: mcp()()
|
|
@@ -162,7 +164,10 @@ const generateOperations = (outputClient = DEFAULT_CLIENT, verbsOptions, options
|
|
|
162
164
|
const { client: generatorClient } = getGeneratorClient(outputClient, output);
|
|
163
165
|
const client = await generatorClient(verbOption, options, outputClient, output);
|
|
164
166
|
if (!client.implementation) return acc;
|
|
165
|
-
const mockOutputs = output.mock.generators.
|
|
167
|
+
const mockOutputs = output.mock.generators.filter((entry) => {
|
|
168
|
+
if (!isFunction(entry) && entry.type === OutputMockType.FAKER && entry.operationResponses === false) return false;
|
|
169
|
+
return true;
|
|
170
|
+
}).map((entry) => {
|
|
166
171
|
const generated = invokeMockGenerator(verbOption, options, entry);
|
|
167
172
|
return {
|
|
168
173
|
type: isFunction(entry) ? OutputMockType.MSW : entry.type,
|
|
@@ -851,6 +856,26 @@ function normalizeSchemasOption(schemas, workspace) {
|
|
|
851
856
|
type: schemas.type
|
|
852
857
|
};
|
|
853
858
|
}
|
|
859
|
+
function normalizeEffectOptions(effect) {
|
|
860
|
+
return {
|
|
861
|
+
strict: {
|
|
862
|
+
param: effect?.strict?.param ?? false,
|
|
863
|
+
query: effect?.strict?.query ?? false,
|
|
864
|
+
header: effect?.strict?.header ?? false,
|
|
865
|
+
body: effect?.strict?.body ?? false,
|
|
866
|
+
response: effect?.strict?.response ?? false
|
|
867
|
+
},
|
|
868
|
+
generate: {
|
|
869
|
+
param: effect?.generate?.param ?? true,
|
|
870
|
+
query: effect?.generate?.query ?? true,
|
|
871
|
+
header: effect?.generate?.header ?? true,
|
|
872
|
+
body: effect?.generate?.body ?? true,
|
|
873
|
+
response: effect?.generate?.response ?? true
|
|
874
|
+
},
|
|
875
|
+
generateEachHttpStatus: effect?.generateEachHttpStatus ?? false,
|
|
876
|
+
useBrandedTypes: effect?.useBrandedTypes ?? false
|
|
877
|
+
};
|
|
878
|
+
}
|
|
854
879
|
async function normalizeOptions(optionsExport, workspace = process.cwd(), globalOptions = {}) {
|
|
855
880
|
const options = await (isFunction(optionsExport) ? optionsExport() : optionsExport);
|
|
856
881
|
if (!options.input) throw new Error(styleText("red", `Config requires an input.`));
|
|
@@ -891,11 +916,21 @@ async function normalizeOptions(optionsExport, workspace = process.cwd(), global
|
|
|
891
916
|
seenMockTypes.add(entry.type);
|
|
892
917
|
}
|
|
893
918
|
const defaultFileExtension = ".ts";
|
|
919
|
+
const defaultSchemaFileExtension = !!outputOptions.schemas && (!isString(outputOptions.schemas) && outputOptions.schemas.type === "zod" || isString(outputOptions.schemas) && (outputOptions.client ?? client) === "zod" && outputOptions.override?.zod?.generateReusableSchemas === true) ? ".zod.ts" : defaultFileExtension;
|
|
920
|
+
const factoryMethodsConfig = outputOptions.factoryMethods;
|
|
921
|
+
let factoryMethods = void 0;
|
|
922
|
+
if (factoryMethodsConfig) factoryMethods = {
|
|
923
|
+
functionNamePrefix: factoryMethodsConfig.functionNamePrefix ?? "create",
|
|
924
|
+
mode: factoryMethodsConfig.mode ?? "split",
|
|
925
|
+
outputDirectory: factoryMethodsConfig.outputDirectory ? normalizePath(factoryMethodsConfig.outputDirectory, outputWorkspace) : outputOptions.schemas ? normalizePath(isString(outputOptions.schemas) ? outputOptions.schemas : outputOptions.schemas.path, outputWorkspace) : normalizePath(outputWorkspace, outputWorkspace),
|
|
926
|
+
includeOptionalProperty: factoryMethodsConfig.includeOptionalProperty ?? true
|
|
927
|
+
};
|
|
894
928
|
const globalQueryOptions = {
|
|
895
929
|
signal: true,
|
|
896
930
|
shouldExportMutatorHooks: true,
|
|
897
931
|
shouldExportHttpClient: true,
|
|
898
932
|
shouldExportQueryKey: true,
|
|
933
|
+
shouldFilterQueryKey: false,
|
|
899
934
|
shouldSplitQueryKey: false,
|
|
900
935
|
...normalizeQueryOptions(outputOptions.override?.query, workspace)
|
|
901
936
|
};
|
|
@@ -913,6 +948,7 @@ async function normalizeOptions(optionsExport, workspace = process.cwd(), global
|
|
|
913
948
|
operationSchemas: outputOptions.operationSchemas ? normalizePath(outputOptions.operationSchemas, outputWorkspace) : void 0,
|
|
914
949
|
namingConvention: outputOptions.namingConvention ?? NamingConvention.CAMEL_CASE,
|
|
915
950
|
fileExtension: outputOptions.fileExtension ?? defaultFileExtension,
|
|
951
|
+
schemaFileExtension: outputOptions.schemaFileExtension ?? outputOptions.fileExtension ?? defaultSchemaFileExtension,
|
|
916
952
|
workspace: outputOptions.workspace ? outputWorkspace : void 0,
|
|
917
953
|
client: outputOptions.client ?? client ?? OutputClient.AXIOS_FUNCTIONS,
|
|
918
954
|
httpClient: outputOptions.httpClient ?? httpClient ?? ((outputOptions.client ?? client) === OutputClient.ANGULAR_QUERY ? OutputHttpClient.ANGULAR : OutputHttpClient.FETCH),
|
|
@@ -927,6 +963,7 @@ async function normalizeOptions(optionsExport, workspace = process.cwd(), global
|
|
|
927
963
|
indexFiles: outputOptions.indexFiles ?? true,
|
|
928
964
|
baseUrl: outputOptions.baseUrl,
|
|
929
965
|
unionAddMissingProperties: outputOptions.unionAddMissingProperties ?? false,
|
|
966
|
+
factoryMethods,
|
|
930
967
|
override: {
|
|
931
968
|
...outputOptions.override,
|
|
932
969
|
mock: {
|
|
@@ -999,11 +1036,15 @@ async function normalizeOptions(optionsExport, workspace = process.cwd(), global
|
|
|
999
1036
|
...outputOptions.override?.zod?.preprocess?.body ? { body: normalizeMutator(workspace, outputOptions.override.zod.preprocess.body) } : {},
|
|
1000
1037
|
...outputOptions.override?.zod?.preprocess?.response ? { response: normalizeMutator(workspace, outputOptions.override.zod.preprocess.response) } : {}
|
|
1001
1038
|
},
|
|
1039
|
+
...outputOptions.override?.zod?.params ? { params: normalizeMutator(workspace, outputOptions.override.zod.params) } : {},
|
|
1002
1040
|
generateEachHttpStatus: outputOptions.override?.zod?.generateEachHttpStatus ?? false,
|
|
1003
1041
|
useBrandedTypes: outputOptions.override?.zod?.useBrandedTypes ?? false,
|
|
1042
|
+
generateReusableSchemas: outputOptions.override?.zod?.generateReusableSchemas ?? false,
|
|
1043
|
+
generateMeta: outputOptions.override?.zod?.generateMeta ?? false,
|
|
1004
1044
|
dateTimeOptions: outputOptions.override?.zod?.dateTimeOptions ?? { offset: true },
|
|
1005
1045
|
timeOptions: outputOptions.override?.zod?.timeOptions ?? {}
|
|
1006
1046
|
},
|
|
1047
|
+
effect: normalizeEffectOptions(outputOptions.override?.effect),
|
|
1007
1048
|
swr: {
|
|
1008
1049
|
generateErrorTypes: false,
|
|
1009
1050
|
...outputOptions.override?.swr
|
|
@@ -1129,7 +1170,7 @@ function normalizePath(path$1, workspace) {
|
|
|
1129
1170
|
return path.resolve(workspace, path$1);
|
|
1130
1171
|
}
|
|
1131
1172
|
function normalizeOperationsAndTags(operationsOrTags, workspace, global) {
|
|
1132
|
-
return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, paramsFilter, query, angular, zod, ...rest }]) => {
|
|
1173
|
+
return Object.fromEntries(Object.entries(operationsOrTags).map(([key, { transformer, mutator, formData, formUrlEncoded, paramsSerializer, paramsFilter, query, angular, zod, effect, ...rest }]) => {
|
|
1133
1174
|
return [key, {
|
|
1134
1175
|
...rest,
|
|
1135
1176
|
...angular ? { angular: {
|
|
@@ -1168,11 +1209,15 @@ function normalizeOperationsAndTags(operationsOrTags, workspace, global) {
|
|
|
1168
1209
|
...zod.preprocess?.body ? { body: normalizeMutator(workspace, zod.preprocess.body) } : {},
|
|
1169
1210
|
...zod.preprocess?.response ? { response: normalizeMutator(workspace, zod.preprocess.response) } : {}
|
|
1170
1211
|
},
|
|
1212
|
+
...zod.params ? { params: normalizeMutator(workspace, zod.params) } : {},
|
|
1171
1213
|
generateEachHttpStatus: zod.generateEachHttpStatus ?? false,
|
|
1172
1214
|
useBrandedTypes: zod.useBrandedTypes ?? false,
|
|
1215
|
+
generateReusableSchemas: zod.generateReusableSchemas ?? false,
|
|
1216
|
+
generateMeta: zod.generateMeta ?? false,
|
|
1173
1217
|
dateTimeOptions: zod.dateTimeOptions ?? { offset: true },
|
|
1174
1218
|
timeOptions: zod.timeOptions ?? {}
|
|
1175
1219
|
} } : {},
|
|
1220
|
+
...effect ? { effect: normalizeEffectOptions(effect) } : {},
|
|
1176
1221
|
...transformer ? { transformer: normalizePath(transformer, workspace) } : {},
|
|
1177
1222
|
...mutator ? { mutator: normalizeMutator(workspace, mutator) } : {},
|
|
1178
1223
|
...formData === void 0 ? {} : { formData: createFormData(workspace, formData) },
|
|
@@ -1242,6 +1287,8 @@ function normalizeQueryOptions(queryOptions = {}, outputWorkspace, globalOptions
|
|
|
1242
1287
|
...queryOptions.mutationOptions ? { mutationOptions: normalizeMutator(outputWorkspace, queryOptions.mutationOptions) } : {},
|
|
1243
1288
|
...isNullish(globalOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: globalOptions.shouldExportQueryKey },
|
|
1244
1289
|
...isNullish(queryOptions.shouldExportQueryKey) ? {} : { shouldExportQueryKey: queryOptions.shouldExportQueryKey },
|
|
1290
|
+
...isNullish(globalOptions.shouldFilterQueryKey) ? {} : { shouldFilterQueryKey: globalOptions.shouldFilterQueryKey },
|
|
1291
|
+
...isNullish(queryOptions.shouldFilterQueryKey) ? {} : { shouldFilterQueryKey: queryOptions.shouldFilterQueryKey },
|
|
1245
1292
|
...isNullish(globalOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: globalOptions.shouldExportHttpClient },
|
|
1246
1293
|
...isNullish(queryOptions.shouldExportHttpClient) ? {} : { shouldExportHttpClient: queryOptions.shouldExportHttpClient },
|
|
1247
1294
|
...isNullish(globalOptions.shouldExportMutatorHooks) ? {} : { shouldExportMutatorHooks: globalOptions.shouldExportMutatorHooks },
|
|
@@ -1308,19 +1355,277 @@ async function startWatcher(watchOptions, watchFn, defaultTarget = ".") {
|
|
|
1308
1355
|
});
|
|
1309
1356
|
}
|
|
1310
1357
|
//#endregion
|
|
1358
|
+
//#region src/reusable-schemas.ts
|
|
1359
|
+
/**
|
|
1360
|
+
* Resolve the export identifier for a `#/components/schemas/X` ref. We reuse
|
|
1361
|
+
* `@orval/core`'s `getRefInfo(...).name` (`pascal` + sanitize + component
|
|
1362
|
+
* suffix) so reusable zod schema exports match the operation wrappers and the
|
|
1363
|
+
* TS model types exactly. `namingConvention` deliberately does NOT influence
|
|
1364
|
+
* the identifier — it governs file names only, consistent with the rest of
|
|
1365
|
+
* orval. The same call powers the generator's `namedRef` emission, so the
|
|
1366
|
+
* definition name and every reference stay in sync.
|
|
1367
|
+
*/
|
|
1368
|
+
const resolveSchemaName = (ref, context) => getRefInfo(ref, context).name;
|
|
1369
|
+
/**
|
|
1370
|
+
* Resolve names for a set of refs, throwing on conflicts (two distinct refs
|
|
1371
|
+
* collapsing to the same identifier). The mapping is the single source of
|
|
1372
|
+
* truth for cross-schema references — the generator, the orchestrator's graph,
|
|
1373
|
+
* and the sentinel rewriter all consult it.
|
|
1374
|
+
*/
|
|
1375
|
+
const resolveSchemaNames = (refs, context) => {
|
|
1376
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1377
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
1378
|
+
for (const ref of refs) {
|
|
1379
|
+
const name = resolveSchemaName(ref, context);
|
|
1380
|
+
const previous = reverse.get(name);
|
|
1381
|
+
if (previous !== void 0 && previous !== ref) throw new Error(`[orval/zod] generateReusableSchemas: refs ${previous} and ${ref} both resolve to the export name "${name}". Rename one in the OpenAPI source.`);
|
|
1382
|
+
resolved.set(ref, name);
|
|
1383
|
+
reverse.set(name, ref);
|
|
1384
|
+
}
|
|
1385
|
+
return resolved;
|
|
1386
|
+
};
|
|
1387
|
+
/**
|
|
1388
|
+
* For each component-schema ref, run the Zod generator + parser with
|
|
1389
|
+
* `useReusableSchemas: true`. The resulting `zod` strings contain
|
|
1390
|
+
* `__REF_<name>__` sentinels at every site that references another schema;
|
|
1391
|
+
* the SCC step (Task 10) decides whether each sentinel becomes a direct
|
|
1392
|
+
* identifier or a `z.lazy(() => Name)` wrapper.
|
|
1393
|
+
*/
|
|
1394
|
+
const generateReusableSchemaSet = (refs, context, options) => {
|
|
1395
|
+
const componentSchemas = context.spec.components?.schemas ?? {};
|
|
1396
|
+
const nameToRef = /* @__PURE__ */ new Map();
|
|
1397
|
+
for (const schemaName of Object.keys(componentSchemas)) {
|
|
1398
|
+
const ref = `#/components/schemas/${schemaName}`;
|
|
1399
|
+
nameToRef.set(resolveSchemaName(ref, context), ref);
|
|
1400
|
+
}
|
|
1401
|
+
const queue = [...refs];
|
|
1402
|
+
const seen = new Set(refs);
|
|
1403
|
+
const entries = [];
|
|
1404
|
+
for (const ref of queue) {
|
|
1405
|
+
const schema = componentSchemas[ref.slice(21)];
|
|
1406
|
+
if (!schema) continue;
|
|
1407
|
+
const name = resolveSchemaName(ref, context);
|
|
1408
|
+
const parsed = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(schema, context, name, options.strict, options.isZodV4, {
|
|
1409
|
+
required: true,
|
|
1410
|
+
useReusableSchemas: true,
|
|
1411
|
+
emitMeta: options.generateMeta
|
|
1412
|
+
}), context, options.coerce ?? false, options.strict, options.isZodV4, void 0, options.paramsMutator ? {
|
|
1413
|
+
mutator: options.paramsMutator,
|
|
1414
|
+
operationId: "",
|
|
1415
|
+
location: "schema",
|
|
1416
|
+
schemaName: name
|
|
1417
|
+
} : void 0);
|
|
1418
|
+
entries.push({
|
|
1419
|
+
ref,
|
|
1420
|
+
name,
|
|
1421
|
+
zod: parsed.zod,
|
|
1422
|
+
consts: parsed.consts,
|
|
1423
|
+
usedRefs: parsed.usedRefs
|
|
1424
|
+
});
|
|
1425
|
+
for (const usedName of parsed.usedRefs) {
|
|
1426
|
+
const usedRef = nameToRef.get(usedName);
|
|
1427
|
+
if (usedRef !== void 0 && !seen.has(usedRef)) {
|
|
1428
|
+
seen.add(usedRef);
|
|
1429
|
+
queue.push(usedRef);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return entries;
|
|
1434
|
+
};
|
|
1435
|
+
const edgeKey = (from, to) => `${from}->${to}`;
|
|
1436
|
+
const tarjan = (graph) => {
|
|
1437
|
+
let index = 0;
|
|
1438
|
+
const stack = [];
|
|
1439
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
1440
|
+
const indices = /* @__PURE__ */ new Map();
|
|
1441
|
+
const lowlinks = /* @__PURE__ */ new Map();
|
|
1442
|
+
const sccs = [];
|
|
1443
|
+
const lazyEdges = /* @__PURE__ */ new Set();
|
|
1444
|
+
const strongconnect = (v) => {
|
|
1445
|
+
indices.set(v, index);
|
|
1446
|
+
lowlinks.set(v, index);
|
|
1447
|
+
index += 1;
|
|
1448
|
+
stack.push(v);
|
|
1449
|
+
onStack.add(v);
|
|
1450
|
+
const neighbors = graph.get(v) ?? /* @__PURE__ */ new Set();
|
|
1451
|
+
for (const w of neighbors) {
|
|
1452
|
+
if (v === w) {
|
|
1453
|
+
lazyEdges.add(edgeKey(v, w));
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
if (!indices.has(w)) {
|
|
1457
|
+
strongconnect(w);
|
|
1458
|
+
lowlinks.set(v, Math.min(lowlinks.get(v) ?? -1, lowlinks.get(w) ?? -1));
|
|
1459
|
+
} else if (onStack.has(w)) {
|
|
1460
|
+
lazyEdges.add(edgeKey(v, w));
|
|
1461
|
+
lowlinks.set(v, Math.min(lowlinks.get(v) ?? -1, indices.get(w) ?? -1));
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
1465
|
+
const scc = [];
|
|
1466
|
+
let w;
|
|
1467
|
+
do {
|
|
1468
|
+
w = stack.pop();
|
|
1469
|
+
if (w === void 0) break;
|
|
1470
|
+
onStack.delete(w);
|
|
1471
|
+
scc.push(w);
|
|
1472
|
+
} while (w !== v);
|
|
1473
|
+
sccs.push(scc);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
for (const node of graph.keys()) if (!indices.has(node)) strongconnect(node);
|
|
1477
|
+
return {
|
|
1478
|
+
sccs,
|
|
1479
|
+
lazyEdges
|
|
1480
|
+
};
|
|
1481
|
+
};
|
|
1482
|
+
const SENTINEL_PATTERN = /__REF_([A-Za-z_$][A-Za-z0-9_$]*)__/g;
|
|
1483
|
+
/**
|
|
1484
|
+
* Replace every `__REF_<name>__` sentinel with the bare identifier. Use this
|
|
1485
|
+
* for schemas that sit at the top of the dependency graph (operation params,
|
|
1486
|
+
* bodies, responses) — they can never participate in a cycle with the
|
|
1487
|
+
* component schemas they reference, so every ref is a direct (non-lazy) one.
|
|
1488
|
+
*/
|
|
1489
|
+
const rewriteSentinelsToDirect = (zod) => zod.replaceAll(SENTINEL_PATTERN, (_match, refName) => refName);
|
|
1490
|
+
/**
|
|
1491
|
+
* Replace every `__REF_<name>__` sentinel with either the bare identifier or
|
|
1492
|
+
* `zod.lazy(() => <name>)` based on whether the edge closes a cycle, then
|
|
1493
|
+
* reorder entries so that every non-lazy reference is emitted AFTER its
|
|
1494
|
+
* target. This avoids TDZ errors at module load.
|
|
1495
|
+
*
|
|
1496
|
+
* Entries that sit in a cycle (SCC of size > 1, or a self-loop) are flagged
|
|
1497
|
+
* `isRecursive`. Their generated `const` reads its own binding inside the
|
|
1498
|
+
* initializer (through the `zod.lazy` wrapper), which TypeScript rejects with
|
|
1499
|
+
* TS7022 ("'X' implicitly has type 'any' ... referenced directly or indirectly
|
|
1500
|
+
* in its own initializer") unless the `const` carries an explicit type
|
|
1501
|
+
* annotation. The writer (`write-zod-specs`) supplies that annotation —
|
|
1502
|
+
* `const X: zod.ZodType<X>` — backed by a generated TS type, which both
|
|
1503
|
+
* silences TS7022 and preserves full `z.infer` typing through the recursion.
|
|
1504
|
+
*
|
|
1505
|
+
* Both the lazy classification and the emit order come from a single Tarjan
|
|
1506
|
+
* run, guaranteeing they agree: a non-lazy edge u→v means v is visited (and
|
|
1507
|
+
* popped) before u in DFS, so v appears earlier in the SCC array → emitted
|
|
1508
|
+
* before u → safe.
|
|
1509
|
+
*/
|
|
1510
|
+
const rewriteReusableSchemas = (entries) => {
|
|
1511
|
+
const graph = new Map(entries.map((e) => [e.name, new Set(e.usedRefs)]));
|
|
1512
|
+
for (const e of entries) for (const ref of e.usedRefs) if (!graph.has(ref)) graph.set(ref, /* @__PURE__ */ new Set());
|
|
1513
|
+
const { sccs, lazyEdges } = tarjan(graph);
|
|
1514
|
+
const recursiveNames = /* @__PURE__ */ new Set();
|
|
1515
|
+
for (const scc of sccs) if (scc.length > 1) for (const name of scc) recursiveNames.add(name);
|
|
1516
|
+
else if (lazyEdges.has(edgeKey(scc[0], scc[0]))) recursiveNames.add(scc[0]);
|
|
1517
|
+
const rewritten = new Map(entries.map((entry) => {
|
|
1518
|
+
const newZod = entry.zod.replaceAll(SENTINEL_PATTERN, (_match, refName) => {
|
|
1519
|
+
return lazyEdges.has(edgeKey(entry.name, refName)) ? `zod.lazy(() => ${refName})` : refName;
|
|
1520
|
+
});
|
|
1521
|
+
return [entry.name, {
|
|
1522
|
+
...entry,
|
|
1523
|
+
zod: newZod,
|
|
1524
|
+
isRecursive: recursiveNames.has(entry.name)
|
|
1525
|
+
}];
|
|
1526
|
+
}));
|
|
1527
|
+
const out = [];
|
|
1528
|
+
for (const scc of sccs) for (const name of scc) {
|
|
1529
|
+
const entry = rewritten.get(name);
|
|
1530
|
+
if (entry !== void 0) out.push(entry);
|
|
1531
|
+
}
|
|
1532
|
+
return out;
|
|
1533
|
+
};
|
|
1534
|
+
//#endregion
|
|
1311
1535
|
//#region src/write-zod-specs.ts
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1536
|
+
/**
|
|
1537
|
+
* Render the `import { ... } from '...'` line for a resolved
|
|
1538
|
+
* `GeneratorMutator`. Mirrors the format produced by
|
|
1539
|
+
* `generateMutatorImports` in `@orval/core` but inlined to avoid pulling in
|
|
1540
|
+
* its full surface area for a single statement.
|
|
1541
|
+
*/
|
|
1542
|
+
function buildMutatorImportStatement(mutator) {
|
|
1543
|
+
return `import ${mutator.default ? mutator.name : `{ ${mutator.name} }`} from '${mutator.path}';`;
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Whole-word substring check for a resolved mutator alias inside generated
|
|
1547
|
+
* code. Plain `String.includes` would false-positive when the user names the
|
|
1548
|
+
* mutator something like `min` against `.min(1)`.
|
|
1549
|
+
*/
|
|
1550
|
+
function bodyReferencesMutator(body, mutator) {
|
|
1551
|
+
return new RegExp(String.raw`\b${mutator.name}\b`).test(body);
|
|
1552
|
+
}
|
|
1553
|
+
function generateZodSchemaFileContent(header, schemas, includeZodImport = true) {
|
|
1554
|
+
const refImports = [...new Set(schemas.flatMap((s) => s.importStatements ?? []))].toSorted();
|
|
1555
|
+
const importBlock = [...includeZodImport ? [`import { z as zod } from 'zod';`] : [], ...refImports].join("\n");
|
|
1556
|
+
const schemaContent = schemas.map(({ schemaName, consts, zodExpression }) => {
|
|
1316
1557
|
return `${consts ? `${consts}\n` : ""}export const ${schemaName} = ${zodExpression}
|
|
1317
1558
|
|
|
1318
1559
|
export type ${schemaName} = zod.input<typeof ${schemaName}>;
|
|
1319
1560
|
export type ${schemaName}Output = zod.output<typeof ${schemaName}>;`;
|
|
1320
|
-
}).join("\n\n")
|
|
1321
|
-
`;
|
|
1561
|
+
}).join("\n\n");
|
|
1562
|
+
return `${header}${importBlock ? `${importBlock}\n\n` : ""}${schemaContent}\n`;
|
|
1563
|
+
}
|
|
1564
|
+
function renderReusableSchemaEntry(entry, context) {
|
|
1565
|
+
const consts = entry.consts ? `${entry.consts}\n\n` : "";
|
|
1566
|
+
if (entry.isRecursive) {
|
|
1567
|
+
const rawName = isComponentRef(entry.ref) ? getRefInfo(entry.ref, context).originalName : void 0;
|
|
1568
|
+
const schema = rawName ? context.spec.components?.schemas?.[rawName] : void 0;
|
|
1569
|
+
const resolved = schema ? resolveValue({
|
|
1570
|
+
schema,
|
|
1571
|
+
name: entry.name,
|
|
1572
|
+
context
|
|
1573
|
+
}) : void 0;
|
|
1574
|
+
const typeBody = resolved ? resolved.value : "unknown";
|
|
1575
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1576
|
+
const extraImports = [];
|
|
1577
|
+
for (const imp of resolved?.imports ?? []) {
|
|
1578
|
+
if (!imp.name || imp.name === entry.name) continue;
|
|
1579
|
+
const bindingKey = imp.alias ?? imp.name;
|
|
1580
|
+
if (seen.has(bindingKey)) continue;
|
|
1581
|
+
seen.add(bindingKey);
|
|
1582
|
+
extraImports.push({
|
|
1583
|
+
name: imp.name,
|
|
1584
|
+
...imp.alias ? { alias: imp.alias } : {}
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
return {
|
|
1588
|
+
content: `${consts}export type ${entry.name} = ${typeBody};\n\nexport const ${entry.name}: zod.ZodType<${entry.name}> = ${entry.zod};\n\nexport type ${entry.name}Output = zod.output<typeof ${entry.name}>;`,
|
|
1589
|
+
extraImports
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
return {
|
|
1593
|
+
content: `${consts}export const ${entry.name} = ${entry.zod};\n\nexport type ${entry.name} = zod.input<typeof ${entry.name}>;\nexport type ${entry.name}Output = zod.output<typeof ${entry.name}>;`,
|
|
1594
|
+
extraImports: []
|
|
1595
|
+
};
|
|
1322
1596
|
}
|
|
1323
1597
|
const isValidSchemaIdentifier = (name) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
1598
|
+
/**
|
|
1599
|
+
* Build the sibling-file `import { … } from './…'` block for one reusable
|
|
1600
|
+
* schema file. Two sources feed in:
|
|
1601
|
+
* - `usedRefs` — names from the zod runtime expression. Sourced from the
|
|
1602
|
+
* sentinel parser, so always unaliased.
|
|
1603
|
+
* - `extraImports` — names the recursive TS body needs that the zod runtime
|
|
1604
|
+
* collapsed (`propertyNames` $refs, etc.). May carry `alias`.
|
|
1605
|
+
* Keyed by export name (`name`) so an aliased `extraImports` entry overrides
|
|
1606
|
+
* a bare `usedRefs` entry — the recursive TS body uses the local binding, so
|
|
1607
|
+
* the aliased form has to win for the file to compile. Self-refs and
|
|
1608
|
+
* non-component identifiers are filtered out.
|
|
1609
|
+
*
|
|
1610
|
+
* Exported for unit-test coverage of the alias-propagation path; no
|
|
1611
|
+
* `resolveValue` producer surfaces aliases here today, so the integration
|
|
1612
|
+
* tests can't exercise it.
|
|
1613
|
+
*/
|
|
1614
|
+
function buildSiblingImports({ usedRefs, extraImports, entryName, componentNames, namingConvention, importExt }) {
|
|
1615
|
+
const importsByName = /* @__PURE__ */ new Map();
|
|
1616
|
+
for (const name of usedRefs) {
|
|
1617
|
+
if (name === entryName) continue;
|
|
1618
|
+
importsByName.set(name, { name });
|
|
1619
|
+
}
|
|
1620
|
+
for (const imp of extraImports) {
|
|
1621
|
+
if (imp.name === entryName || !componentNames.has(imp.name)) continue;
|
|
1622
|
+
importsByName.set(imp.name, imp);
|
|
1623
|
+
}
|
|
1624
|
+
return [...importsByName.values()].toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, alias }) => {
|
|
1625
|
+
const importedFile = conventionName(name, namingConvention);
|
|
1626
|
+
return `import { ${alias ? `${name} as ${alias}` : name} } from './${importedFile}${importExt}';`;
|
|
1627
|
+
}).join("\n");
|
|
1628
|
+
}
|
|
1324
1629
|
const isPrimitiveSchemaName = (name) => [
|
|
1325
1630
|
"string",
|
|
1326
1631
|
"number",
|
|
@@ -1361,7 +1666,8 @@ async function writeZodSchemaIndex(schemasPath, fileExtension, header, schemaNam
|
|
|
1361
1666
|
const uniqueExports = [...new Set(allExports.split("\n"))].filter((line) => line.trim()).toSorted().join("\n");
|
|
1362
1667
|
await fs.outputFile(indexPath, `${header}\n${uniqueExports}\n`);
|
|
1363
1668
|
}
|
|
1364
|
-
function generateZodSchemasInline(builder, output) {
|
|
1669
|
+
function generateZodSchemasInline(builder, output, includeZodImport = true, paramsMutator, includeParamsImport = false) {
|
|
1670
|
+
if (output.override.zod.generateReusableSchemas === true) return generateZodSchemasInlineReusable(builder, output, includeZodImport, paramsMutator, includeParamsImport);
|
|
1365
1671
|
const schemasWithOpenApiDef = builder.schemas.filter((s) => s.schema);
|
|
1366
1672
|
if (schemasWithOpenApiDef.length === 0) return "";
|
|
1367
1673
|
const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
|
|
@@ -1376,7 +1682,10 @@ function generateZodSchemasInline(builder, output) {
|
|
|
1376
1682
|
workspace: "",
|
|
1377
1683
|
output
|
|
1378
1684
|
};
|
|
1379
|
-
const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name, strict, isZodV4, {
|
|
1685
|
+
const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name, strict, isZodV4, {
|
|
1686
|
+
required: true,
|
|
1687
|
+
emitMeta: output.override.zod.generateMeta
|
|
1688
|
+
}), context, coerce, strict, isZodV4);
|
|
1380
1689
|
schemas.push({
|
|
1381
1690
|
schemaName: name,
|
|
1382
1691
|
consts: parsedZodDefinition.consts,
|
|
@@ -1384,9 +1693,38 @@ function generateZodSchemasInline(builder, output) {
|
|
|
1384
1693
|
});
|
|
1385
1694
|
}
|
|
1386
1695
|
if (schemas.length === 0) return "";
|
|
1387
|
-
return generateZodSchemaFileContent("", schemas);
|
|
1696
|
+
return generateZodSchemaFileContent("", schemas, includeZodImport);
|
|
1697
|
+
}
|
|
1698
|
+
function generateZodSchemasInlineReusable(builder, output, includeZodImport = true, paramsMutator, includeParamsImport = false) {
|
|
1699
|
+
const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
|
|
1700
|
+
const strict = output.override.zod.strict.body;
|
|
1701
|
+
const coerce = output.override.zod.coerce.body;
|
|
1702
|
+
const context = {
|
|
1703
|
+
spec: builder.spec,
|
|
1704
|
+
target: builder.target,
|
|
1705
|
+
workspace: "",
|
|
1706
|
+
output
|
|
1707
|
+
};
|
|
1708
|
+
const componentSchemas = builder.spec.components?.schemas ?? {};
|
|
1709
|
+
const refs = Object.keys(componentSchemas).map((schemaName) => `#/components/schemas/${schemaName}`);
|
|
1710
|
+
if (refs.length === 0) return "";
|
|
1711
|
+
resolveSchemaNames(refs, context);
|
|
1712
|
+
const body = rewriteReusableSchemas(generateReusableSchemaSet(refs, context, {
|
|
1713
|
+
strict,
|
|
1714
|
+
isZodV4,
|
|
1715
|
+
coerce,
|
|
1716
|
+
generateMeta: output.override.zod.generateMeta,
|
|
1717
|
+
paramsMutator
|
|
1718
|
+
})).map((entry) => renderReusableSchemaEntry(entry, context).content).join("\n\n");
|
|
1719
|
+
const zodImport = includeZodImport ? `import { z as zod } from 'zod';\n` : "";
|
|
1720
|
+
const paramsImport = paramsMutator && includeParamsImport && bodyReferencesMutator(body, paramsMutator) ? `${buildMutatorImportStatement(paramsMutator)}\n` : "";
|
|
1721
|
+
return `${zodImport || paramsImport ? `${zodImport}${paramsImport}\n` : ""}${body}\n`;
|
|
1388
1722
|
}
|
|
1389
|
-
async function writeZodSchemas(builder, schemasPath, fileExtension, header, output) {
|
|
1723
|
+
async function writeZodSchemas(builder, schemasPath, fileExtension, header, output, paramsMutator) {
|
|
1724
|
+
if (output.override.zod.generateReusableSchemas) {
|
|
1725
|
+
await writeZodSchemasReusable(builder, schemasPath, fileExtension, header, output, paramsMutator);
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1390
1728
|
const schemasWithOpenApiDef = builder.schemas.filter((s) => s.schema);
|
|
1391
1729
|
const schemasToWrite = [];
|
|
1392
1730
|
const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
|
|
@@ -1403,7 +1741,10 @@ async function writeZodSchemas(builder, schemasPath, fileExtension, header, outp
|
|
|
1403
1741
|
workspace: "",
|
|
1404
1742
|
output
|
|
1405
1743
|
};
|
|
1406
|
-
const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name, strict, isZodV4, {
|
|
1744
|
+
const parsedZodDefinition = parseZodValidationSchemaDefinition(generateZodValidationSchemaDefinition(dereference(schemaObject, context), context, name, strict, isZodV4, {
|
|
1745
|
+
required: true,
|
|
1746
|
+
emitMeta: output.override.zod.generateMeta
|
|
1747
|
+
}), context, coerce, strict, isZodV4);
|
|
1407
1748
|
schemasToWrite.push({
|
|
1408
1749
|
schemaName: name,
|
|
1409
1750
|
filePath,
|
|
@@ -1418,6 +1759,47 @@ async function writeZodSchemas(builder, schemasPath, fileExtension, header, outp
|
|
|
1418
1759
|
}
|
|
1419
1760
|
if (output.indexFiles) await writeZodSchemaIndex(schemasPath, fileExtension, header, groupedSchemasToWrite.map((schemaGroup) => schemaGroup[0].schemaName), output.namingConvention, false);
|
|
1420
1761
|
}
|
|
1762
|
+
async function writeZodSchemasReusable(builder, schemasPath, fileExtension, header, output, paramsMutator) {
|
|
1763
|
+
const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
|
|
1764
|
+
const strict = output.override.zod.strict.body;
|
|
1765
|
+
const coerce = output.override.zod.coerce.body;
|
|
1766
|
+
const context = {
|
|
1767
|
+
spec: builder.spec,
|
|
1768
|
+
target: builder.target,
|
|
1769
|
+
workspace: "",
|
|
1770
|
+
output
|
|
1771
|
+
};
|
|
1772
|
+
const componentSchemas = builder.spec.components?.schemas ?? {};
|
|
1773
|
+
const refs = Object.keys(componentSchemas).map((schemaName) => `#/components/schemas/${schemaName}`);
|
|
1774
|
+
resolveSchemaNames(refs, context);
|
|
1775
|
+
const rewritten = rewriteReusableSchemas(generateReusableSchemaSet(refs, context, {
|
|
1776
|
+
strict,
|
|
1777
|
+
isZodV4,
|
|
1778
|
+
coerce,
|
|
1779
|
+
generateMeta: output.override.zod.generateMeta,
|
|
1780
|
+
paramsMutator
|
|
1781
|
+
}));
|
|
1782
|
+
const componentNames = new Set(Object.keys(builder.spec.components?.schemas ?? {}).map((schemaName) => resolveSchemaName(`#/components/schemas/${schemaName}`, context)));
|
|
1783
|
+
const paramsMutatorImport = paramsMutator ? buildMutatorImportStatement(paramsMutator) : void 0;
|
|
1784
|
+
for (const entry of rewritten) {
|
|
1785
|
+
const fileName = conventionName(entry.name, output.namingConvention);
|
|
1786
|
+
const filePath = path.join(schemasPath, `${fileName}${fileExtension}`);
|
|
1787
|
+
const importExt = fileExtension.replace(/\.ts$/, "");
|
|
1788
|
+
const rendered = renderReusableSchemaEntry(entry, context);
|
|
1789
|
+
const refImports = buildSiblingImports({
|
|
1790
|
+
usedRefs: entry.usedRefs,
|
|
1791
|
+
extraImports: rendered.extraImports,
|
|
1792
|
+
entryName: entry.name,
|
|
1793
|
+
componentNames,
|
|
1794
|
+
namingConvention: output.namingConvention,
|
|
1795
|
+
importExt
|
|
1796
|
+
});
|
|
1797
|
+
const imports = [...!!paramsMutator && bodyReferencesMutator(entry.zod, paramsMutator) && paramsMutatorImport ? [paramsMutatorImport] : [], ...refImports ? [refImports] : []].join("\n");
|
|
1798
|
+
const fileContent = `${header}import { z as zod } from 'zod';\n` + (imports ? `${imports}\n\n` : "\n") + `${rendered.content}\n`;
|
|
1799
|
+
await fs.outputFile(filePath, fileContent);
|
|
1800
|
+
}
|
|
1801
|
+
if (output.indexFiles && rewritten.length > 0) await writeZodSchemaIndex(schemasPath, fileExtension, header, rewritten.map((e) => e.name), output.namingConvention, true);
|
|
1802
|
+
}
|
|
1421
1803
|
async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension, header, output, context) {
|
|
1422
1804
|
const zodContext = context;
|
|
1423
1805
|
const verbOptionsArray = Object.values(verbOptions);
|
|
@@ -1425,6 +1807,7 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
1425
1807
|
const isZodV4 = !!output.packageJson && isZodVersionV4(output.packageJson);
|
|
1426
1808
|
const strict = output.override.zod.strict.body;
|
|
1427
1809
|
const coerce = output.override.zod.coerce.body;
|
|
1810
|
+
const useReusableSchemas = output.override.zod.generateReusableSchemas === true;
|
|
1428
1811
|
const uniqueVerbsSchemas = dedupeSchemasByName(verbOptionsArray.flatMap((verbOption) => {
|
|
1429
1812
|
const operation = verbOption.originalOperation;
|
|
1430
1813
|
const shouldGenerate = {
|
|
@@ -1440,7 +1823,7 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
1440
1823
|
const bodySchema = bodyMedia?.schema;
|
|
1441
1824
|
const bodySchemas = shouldGenerate.body && bodySchema ? [{
|
|
1442
1825
|
name: `${pascal(verbOption.operationName)}Body`,
|
|
1443
|
-
schema: dereference(bodySchema, zodContext),
|
|
1826
|
+
schema: useReusableSchemas ? bodySchema : dereference(bodySchema, zodContext),
|
|
1444
1827
|
bodyContentType,
|
|
1445
1828
|
encoding: bodyMedia?.encoding
|
|
1446
1829
|
}] : [];
|
|
@@ -1450,7 +1833,7 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
1450
1833
|
name: `${pascal(verbOption.operationName)}Params`,
|
|
1451
1834
|
schema: {
|
|
1452
1835
|
type: "object",
|
|
1453
|
-
properties: Object.fromEntries(queryParams.filter((p) => "schema" in p && p.schema).map((p) => [p.name, dereference(p.schema, zodContext)])),
|
|
1836
|
+
properties: Object.fromEntries(queryParams.filter((p) => "schema" in p && p.schema).map((p) => [p.name, useReusableSchemas ? p.schema : dereference(p.schema, zodContext)])),
|
|
1454
1837
|
required: queryParams.filter((p) => p.required).map((p) => p.name).filter((name) => name !== void 0)
|
|
1455
1838
|
}
|
|
1456
1839
|
}] : [];
|
|
@@ -1459,13 +1842,13 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
1459
1842
|
name: `${pascal(verbOption.operationName)}Headers`,
|
|
1460
1843
|
schema: {
|
|
1461
1844
|
type: "object",
|
|
1462
|
-
properties: Object.fromEntries(headerParams.filter((p) => "schema" in p && p.schema).map((p) => [p.name, dereference(p.schema, zodContext)])),
|
|
1845
|
+
properties: Object.fromEntries(headerParams.filter((p) => "schema" in p && p.schema).map((p) => [p.name, useReusableSchemas ? p.schema : dereference(p.schema, zodContext)])),
|
|
1463
1846
|
required: headerParams.filter((p) => p.required).map((p) => p.name).filter((name) => name !== void 0)
|
|
1464
1847
|
}
|
|
1465
1848
|
}] : [];
|
|
1466
1849
|
const responseSchemas = shouldGenerate.response ? [...verbOption.response.types.success, ...verbOption.response.types.errors].filter((responseType) => !!responseType.originalSchema && !responseType.isRef && isValidSchemaIdentifier(responseType.value) && !isPrimitiveSchemaName(responseType.value)).map((responseType) => ({
|
|
1467
1850
|
name: responseType.value,
|
|
1468
|
-
schema: dereference(responseType.originalSchema, zodContext)
|
|
1851
|
+
schema: useReusableSchemas ? responseType.originalSchema : dereference(responseType.originalSchema, zodContext)
|
|
1469
1852
|
})) : [];
|
|
1470
1853
|
return dedupeSchemasByName([
|
|
1471
1854
|
...bodySchemas,
|
|
@@ -1476,15 +1859,29 @@ async function writeZodSchemasFromVerbs(verbOptions, schemasPath, fileExtension,
|
|
|
1476
1859
|
}));
|
|
1477
1860
|
const schemasToWrite = [];
|
|
1478
1861
|
for (const entry of uniqueVerbsSchemas) {
|
|
1862
|
+
if (useReusableSchemas && entry.schema && typeof entry.schema.$ref === "string" && Object.keys(entry.schema).length === 1) continue;
|
|
1479
1863
|
const { name, schema } = entry;
|
|
1480
1864
|
const fileName = conventionName(name, output.namingConvention);
|
|
1481
1865
|
const filePath = path.join(schemasPath, `${fileName}${fileExtension}`);
|
|
1482
|
-
const parsedZodDefinition = parseZodValidationSchemaDefinition("bodyContentType" in entry && entry.bodyContentType === "multipart/form-data" ? generateFormDataZodSchema(schema, zodContext, name, strict, isZodV4, "encoding" in entry ? entry.encoding : void 0) : generateZodValidationSchemaDefinition(schema, zodContext, name, strict, isZodV4, {
|
|
1866
|
+
const parsedZodDefinition = parseZodValidationSchemaDefinition("bodyContentType" in entry && entry.bodyContentType === "multipart/form-data" ? generateFormDataZodSchema(schema, zodContext, name, strict, isZodV4, "encoding" in entry ? entry.encoding : void 0, useReusableSchemas) : generateZodValidationSchemaDefinition(schema, zodContext, name, strict, isZodV4, {
|
|
1867
|
+
required: true,
|
|
1868
|
+
useReusableSchemas
|
|
1869
|
+
}), zodContext, coerce, strict, isZodV4);
|
|
1870
|
+
let zodExpression = parsedZodDefinition.zod;
|
|
1871
|
+
let importStatements;
|
|
1872
|
+
if (useReusableSchemas && parsedZodDefinition.usedRefs.size > 0) {
|
|
1873
|
+
zodExpression = rewriteSentinelsToDirect(zodExpression);
|
|
1874
|
+
const importExt = fileExtension.replace(/\.ts$/, "");
|
|
1875
|
+
importStatements = [...parsedZodDefinition.usedRefs].filter((refName) => refName !== name).toSorted().map((refName) => {
|
|
1876
|
+
return `import { ${refName} } from './${conventionName(refName, output.namingConvention)}${importExt}';`;
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1483
1879
|
schemasToWrite.push({
|
|
1484
1880
|
schemaName: name,
|
|
1485
1881
|
filePath,
|
|
1486
1882
|
consts: parsedZodDefinition.consts,
|
|
1487
|
-
zodExpression
|
|
1883
|
+
zodExpression,
|
|
1884
|
+
importStatements
|
|
1488
1885
|
});
|
|
1489
1886
|
}
|
|
1490
1887
|
const groupedSchemasToWrite = groupSchemasByFilePath(schemasToWrite);
|
|
@@ -1545,118 +1942,168 @@ async function addOperationSchemasReExport(schemaPath, operationSchemasPath, hea
|
|
|
1545
1942
|
await fs.outputFile(schemaIndexPath, content);
|
|
1546
1943
|
}
|
|
1547
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Emit `<schemas-dir>/index.faker.ts` (or `<output-root>/schemas.faker.ts`
|
|
1947
|
+
* when `output.schemas` is not configured) when a faker generator entry has
|
|
1948
|
+
* `schemas: true`. Each `components/schemas` entry becomes a
|
|
1949
|
+
* `get<SchemaName>Mock(overrides)` factory in the file. Returns the written
|
|
1950
|
+
* file path so callers can include it in formatter / hook runs, or
|
|
1951
|
+
* `undefined` if no file was written.
|
|
1952
|
+
*/
|
|
1953
|
+
async function writeFakerSchemaMocks(builder, options, header) {
|
|
1954
|
+
const { output } = options;
|
|
1955
|
+
const fakerEntry = output.mock.generators.find((g) => !isFunction(g) && g.type === OutputMockType.FAKER && g.schemas === true);
|
|
1956
|
+
if (!fakerEntry) return;
|
|
1957
|
+
const schemasWithDef = builder.schemas.filter((s) => !!s.schema);
|
|
1958
|
+
if (schemasWithDef.length === 0) return;
|
|
1959
|
+
const { implementation, imports } = generateFakerForSchemas(schemasWithDef, {
|
|
1960
|
+
spec: builder.spec,
|
|
1961
|
+
target: builder.target,
|
|
1962
|
+
workspace: "",
|
|
1963
|
+
output
|
|
1964
|
+
}, fakerEntry);
|
|
1965
|
+
if (!implementation.trim()) return;
|
|
1966
|
+
let filePath;
|
|
1967
|
+
let schemaImportPath;
|
|
1968
|
+
const fileExtension = output.fileExtension || ".ts";
|
|
1969
|
+
if (output.schemas) {
|
|
1970
|
+
const schemasDir = isString(output.schemas) ? output.schemas : output.schemas.path;
|
|
1971
|
+
filePath = path.join(schemasDir, `index.faker${fileExtension}`);
|
|
1972
|
+
schemaImportPath = ".";
|
|
1973
|
+
} else {
|
|
1974
|
+
const targetInfo = output.target ? getFileInfo(output.target, { extension: fileExtension }) : void 0;
|
|
1975
|
+
const dir = targetInfo?.dirname ?? process.cwd();
|
|
1976
|
+
filePath = path.join(dir, `schemas.faker${fileExtension}`);
|
|
1977
|
+
schemaImportPath = targetInfo ? `./${targetInfo.filename}${getImportExtension(fileExtension, output.tsconfig)}` : void 0;
|
|
1978
|
+
}
|
|
1979
|
+
const reroutedImports = imports.map((imp) => imp.importPath ? imp : {
|
|
1980
|
+
...imp,
|
|
1981
|
+
importPath: schemaImportPath
|
|
1982
|
+
});
|
|
1983
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1984
|
+
for (const imp of reroutedImports) {
|
|
1985
|
+
const key = imp.importPath ?? "";
|
|
1986
|
+
if (!key) continue;
|
|
1987
|
+
const bucket = grouped.get(key) ?? [];
|
|
1988
|
+
bucket.push(imp);
|
|
1989
|
+
grouped.set(key, bucket);
|
|
1990
|
+
}
|
|
1991
|
+
const content = `${header}${generateDependencyImports(implementation, [{
|
|
1992
|
+
exports: [{
|
|
1993
|
+
name: "faker",
|
|
1994
|
+
values: true
|
|
1995
|
+
}],
|
|
1996
|
+
dependency: fakerEntry.locale ? `@faker-js/faker/locale/${fakerEntry.locale}` : "@faker-js/faker"
|
|
1997
|
+
}, ...[...grouped.entries()].map(([dependency, exports]) => ({
|
|
1998
|
+
exports,
|
|
1999
|
+
dependency
|
|
2000
|
+
}))], void 0, !!output.schemas, false)}\n\n${implementation}`;
|
|
2001
|
+
await writeGeneratedFile(filePath, content);
|
|
2002
|
+
return filePath;
|
|
2003
|
+
}
|
|
2004
|
+
function isSchemaValidatorClient(client) {
|
|
2005
|
+
return client === "zod" || client === "effect";
|
|
2006
|
+
}
|
|
2007
|
+
function shouldGenerateZodSchemasInline(output, hasOperations) {
|
|
2008
|
+
if (output.client !== "zod" || output.schemas) return false;
|
|
2009
|
+
if (output.override.zod.generateReusableSchemas) return true;
|
|
2010
|
+
return !hasOperations;
|
|
2011
|
+
}
|
|
2012
|
+
function shouldGenerateSchemas(output, hasOperations) {
|
|
2013
|
+
return !output.schemas && !isSchemaValidatorClient(output.client) || shouldGenerateZodSchemasInline(output, hasOperations);
|
|
2014
|
+
}
|
|
1548
2015
|
async function writeSpecs(builder, workspace, options, projectName) {
|
|
1549
2016
|
const { info, schemas, target } = builder;
|
|
1550
2017
|
const { output } = options;
|
|
1551
2018
|
const projectTitle = projectName ?? info.title;
|
|
1552
2019
|
const header = getHeader(output.override.header, info);
|
|
1553
|
-
if (output.schemas)
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
if (regularSchemas.length > 0) await writeSchemas({
|
|
1563
|
-
schemaPath,
|
|
1564
|
-
schemas: regularSchemas,
|
|
1565
|
-
target,
|
|
1566
|
-
namingConvention: output.namingConvention,
|
|
1567
|
-
fileExtension,
|
|
1568
|
-
header,
|
|
1569
|
-
indexFiles: output.indexFiles,
|
|
2020
|
+
if (output.schemas) {
|
|
2021
|
+
const schemasPath = isString(output.schemas) ? output.schemas : output.schemas.path;
|
|
2022
|
+
if (!isString(output.schemas) && output.schemas.type === "zod" || isString(output.schemas) && output.client === "zod" && output.override.zod.generateReusableSchemas) {
|
|
2023
|
+
const fileExtension = output.schemaFileExtension;
|
|
2024
|
+
await writeZodSchemas(builder, schemasPath, fileExtension, header, output, output.override.zod.params ? await generateMutator({
|
|
2025
|
+
output: path.join(schemasPath, `__params__${fileExtension}`),
|
|
2026
|
+
mutator: output.override.zod.params,
|
|
2027
|
+
name: "zodParams",
|
|
2028
|
+
workspace,
|
|
1570
2029
|
tsconfig: output.tsconfig
|
|
2030
|
+
}) : void 0);
|
|
2031
|
+
await writeZodSchemasFromVerbs(builder.verbOptions, schemasPath, fileExtension, header, output, {
|
|
2032
|
+
spec: builder.spec,
|
|
2033
|
+
target: builder.target,
|
|
2034
|
+
workspace,
|
|
2035
|
+
output
|
|
1571
2036
|
});
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
2037
|
+
} else {
|
|
2038
|
+
const fileExtension = output.fileExtension || ".ts";
|
|
2039
|
+
if (output.operationSchemas) {
|
|
2040
|
+
const { regularSchemas, operationSchemas: opSchemas } = splitSchemasByType(schemas);
|
|
2041
|
+
const regularSchemaNames = new Set(regularSchemas.map((s) => s.name));
|
|
2042
|
+
const operationSchemaNames = new Set(opSchemas.map((s) => s.name));
|
|
2043
|
+
fixCrossDirectoryImports(opSchemas, regularSchemaNames, schemasPath, output.operationSchemas, output.namingConvention, fileExtension, output.tsconfig);
|
|
2044
|
+
fixRegularSchemaImports(regularSchemas, operationSchemaNames, schemasPath, output.operationSchemas, output.namingConvention, fileExtension, output.tsconfig);
|
|
2045
|
+
if (regularSchemas.length > 0) await writeSchemas({
|
|
2046
|
+
schemaPath: schemasPath,
|
|
2047
|
+
schemas: regularSchemas,
|
|
1576
2048
|
target,
|
|
1577
2049
|
namingConvention: output.namingConvention,
|
|
1578
2050
|
fileExtension,
|
|
1579
2051
|
header,
|
|
1580
2052
|
indexFiles: output.indexFiles,
|
|
1581
|
-
tsconfig: output.tsconfig
|
|
2053
|
+
tsconfig: output.tsconfig,
|
|
2054
|
+
factoryOutputDirectory: output.factoryMethods?.outputDirectory
|
|
1582
2055
|
});
|
|
1583
|
-
if (
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
const operationSchemaNames = new Set(opSchemas.map((s) => s.name));
|
|
1601
|
-
fixCrossDirectoryImports(opSchemas, regularSchemaNames, output.schemas.path, output.operationSchemas, output.namingConvention, fileExtension, output.tsconfig);
|
|
1602
|
-
fixRegularSchemaImports(regularSchemas, operationSchemaNames, output.schemas.path, output.operationSchemas, output.namingConvention, fileExtension, output.tsconfig);
|
|
1603
|
-
if (regularSchemas.length > 0) await writeSchemas({
|
|
1604
|
-
schemaPath: output.schemas.path,
|
|
1605
|
-
schemas: regularSchemas,
|
|
2056
|
+
if (opSchemas.length > 0) {
|
|
2057
|
+
await writeSchemas({
|
|
2058
|
+
schemaPath: output.operationSchemas,
|
|
2059
|
+
schemas: opSchemas,
|
|
2060
|
+
target,
|
|
2061
|
+
namingConvention: output.namingConvention,
|
|
2062
|
+
fileExtension,
|
|
2063
|
+
header,
|
|
2064
|
+
indexFiles: output.indexFiles,
|
|
2065
|
+
tsconfig: output.tsconfig,
|
|
2066
|
+
factoryOutputDirectory: output.factoryMethods?.outputDirectory
|
|
2067
|
+
});
|
|
2068
|
+
if (output.indexFiles) await addOperationSchemasReExport(schemasPath, output.operationSchemas, header);
|
|
2069
|
+
}
|
|
2070
|
+
} else await writeSchemas({
|
|
2071
|
+
schemaPath: schemasPath,
|
|
2072
|
+
schemas,
|
|
1606
2073
|
target,
|
|
1607
2074
|
namingConvention: output.namingConvention,
|
|
1608
2075
|
fileExtension,
|
|
1609
2076
|
header,
|
|
1610
2077
|
indexFiles: output.indexFiles,
|
|
1611
|
-
tsconfig: output.tsconfig
|
|
2078
|
+
tsconfig: output.tsconfig,
|
|
2079
|
+
factoryOutputDirectory: output.factoryMethods?.outputDirectory
|
|
1612
2080
|
});
|
|
1613
|
-
|
|
1614
|
-
await writeSchemas({
|
|
1615
|
-
schemaPath: output.operationSchemas,
|
|
1616
|
-
schemas: opSchemas,
|
|
1617
|
-
target,
|
|
1618
|
-
namingConvention: output.namingConvention,
|
|
1619
|
-
fileExtension,
|
|
1620
|
-
header,
|
|
1621
|
-
indexFiles: output.indexFiles,
|
|
1622
|
-
tsconfig: output.tsconfig
|
|
1623
|
-
});
|
|
1624
|
-
if (output.indexFiles) await addOperationSchemasReExport(output.schemas.path, output.operationSchemas, header);
|
|
1625
|
-
}
|
|
1626
|
-
} else await writeSchemas({
|
|
1627
|
-
schemaPath: output.schemas.path,
|
|
1628
|
-
schemas,
|
|
1629
|
-
target,
|
|
1630
|
-
namingConvention: output.namingConvention,
|
|
1631
|
-
fileExtension,
|
|
1632
|
-
header,
|
|
1633
|
-
indexFiles: output.indexFiles,
|
|
1634
|
-
tsconfig: output.tsconfig
|
|
1635
|
-
});
|
|
1636
|
-
} else {
|
|
1637
|
-
const fileExtension = ".zod.ts";
|
|
1638
|
-
await writeZodSchemas(builder, output.schemas.path, fileExtension, header, output);
|
|
1639
|
-
await writeZodSchemasFromVerbs(builder.verbOptions, output.schemas.path, fileExtension, header, output, {
|
|
1640
|
-
spec: builder.spec,
|
|
1641
|
-
target: builder.target,
|
|
1642
|
-
workspace,
|
|
1643
|
-
output
|
|
1644
|
-
});
|
|
2081
|
+
}
|
|
1645
2082
|
}
|
|
2083
|
+
const fakerSchemaPath = await writeFakerSchemaMocks(builder, options, header);
|
|
1646
2084
|
let implementationPaths = [];
|
|
1647
2085
|
if (output.target) {
|
|
1648
2086
|
const writeMode = getWriteMode(output.mode);
|
|
1649
|
-
const isZodClient = output.client === "zod";
|
|
1650
2087
|
const hasOperations = Object.keys(builder.operations).length > 0;
|
|
1651
|
-
const needZodSchemasInline =
|
|
2088
|
+
const needZodSchemasInline = shouldGenerateZodSchemasInline(output, hasOperations);
|
|
2089
|
+
const includeZodImport = !Object.values(builder.operations).some((operation) => /\bzod\b/.test(operation.implementation));
|
|
2090
|
+
const inlineSchemasParamsMutator = needZodSchemasInline && output.override.zod.params ? await generateMutator({
|
|
2091
|
+
output: output.target,
|
|
2092
|
+
mutator: output.override.zod.params,
|
|
2093
|
+
name: "zodParams",
|
|
2094
|
+
workspace,
|
|
2095
|
+
tsconfig: output.tsconfig
|
|
2096
|
+
}) : void 0;
|
|
2097
|
+
const isSchemasInSeparateFile = output.mode !== OutputMode.SINGLE;
|
|
2098
|
+
const includeParamsImport = !hasOperations || isSchemasInSeparateFile;
|
|
1652
2099
|
implementationPaths = await writeMode({
|
|
1653
2100
|
builder,
|
|
1654
2101
|
workspace,
|
|
1655
2102
|
output,
|
|
1656
2103
|
projectName,
|
|
1657
2104
|
header,
|
|
1658
|
-
needSchema:
|
|
1659
|
-
generateSchemasInline: needZodSchemasInline ? () => generateZodSchemasInline(builder, output) : void 0
|
|
2105
|
+
needSchema: shouldGenerateSchemas(output, hasOperations),
|
|
2106
|
+
generateSchemasInline: needZodSchemasInline ? () => generateZodSchemasInline(builder, output, includeZodImport, inlineSchemasParamsMutator, includeParamsImport) : void 0
|
|
1660
2107
|
});
|
|
1661
2108
|
}
|
|
1662
2109
|
if (output.workspace) {
|
|
@@ -1684,6 +2131,7 @@ async function writeSpecs(builder, workspace, options, projectName) {
|
|
|
1684
2131
|
}
|
|
1685
2132
|
const paths = [
|
|
1686
2133
|
...output.schemas ? [getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path).dirname] : [],
|
|
2134
|
+
...fakerSchemaPath ? [fakerSchemaPath] : [],
|
|
1687
2135
|
...output.operationSchemas ? [getFileInfo(output.operationSchemas).dirname] : [],
|
|
1688
2136
|
...implementationPaths
|
|
1689
2137
|
];
|
|
@@ -1814,4 +2262,4 @@ async function loadConfigFile(configFilePath) {
|
|
|
1814
2262
|
//#endregion
|
|
1815
2263
|
export { defineConfig as a, description as c, startWatcher as i, name as l, loadConfigFile as n, defineTransformer as o, generateSpec as r, normalizeOptions as s, findConfigFile as t, version as u };
|
|
1816
2264
|
|
|
1817
|
-
//# sourceMappingURL=config-
|
|
2265
|
+
//# sourceMappingURL=config-yIkhToq8.mjs.map
|