zenko 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -12
- package/dist/cli.cjs +283 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +283 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +265 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.mjs +265 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -8
package/dist/cli.mjs
CHANGED
@@ -214,11 +214,13 @@ function generate(spec, options = {}) {
|
|
214
214
|
const output = [];
|
215
215
|
const generatedTypes = /* @__PURE__ */ new Set();
|
216
216
|
const { strictDates = false, strictNumeric = false } = options;
|
217
|
+
const typesConfig = normalizeTypesConfig(options.types);
|
217
218
|
const schemaOptions = {
|
218
219
|
strictDates,
|
219
220
|
strictNumeric
|
220
221
|
};
|
221
222
|
output.push('import { z } from "zod";');
|
223
|
+
appendHelperTypesImport(output, typesConfig);
|
222
224
|
output.push("");
|
223
225
|
if (spec.components?.schemas) {
|
224
226
|
output.push("// Generated Zod Schemas");
|
@@ -238,16 +240,79 @@ function generate(spec, options = {}) {
|
|
238
240
|
output.push("// Path Functions");
|
239
241
|
output.push("export const paths = {");
|
240
242
|
for (const op of operations) {
|
241
|
-
|
243
|
+
const pathParamNames = op.pathParams.map((p) => p.name);
|
244
|
+
const hasPathParams = pathParamNames.length > 0;
|
245
|
+
const hasQueryParams = op.queryParams.length > 0;
|
246
|
+
if (!hasPathParams && !hasQueryParams) {
|
242
247
|
output.push(` ${op.operationId}: () => "${op.path}",`);
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
248
|
+
continue;
|
249
|
+
}
|
250
|
+
const allParamNames = [
|
251
|
+
...pathParamNames,
|
252
|
+
...op.queryParams.map((p) => p.name)
|
253
|
+
];
|
254
|
+
const signaturePieces = [];
|
255
|
+
for (const param of op.pathParams) {
|
256
|
+
signaturePieces.push(`${param.name}: string`);
|
257
|
+
}
|
258
|
+
for (const param of op.queryParams) {
|
259
|
+
signaturePieces.push(
|
260
|
+
`${param.name}${param.required ? "" : "?"}: ${mapQueryType(param)}`
|
261
|
+
);
|
262
|
+
}
|
263
|
+
const signatureParams = signaturePieces.join(", ");
|
264
|
+
const needsDefaultObject = !hasPathParams && hasQueryParams && op.queryParams.every((param) => !param.required);
|
265
|
+
const signatureArgs = allParamNames.length ? `{ ${allParamNames.join(", ")} }` : "{}";
|
266
|
+
const signature = `${signatureArgs}: { ${signatureParams} }${needsDefaultObject ? " = {}" : ""}`;
|
267
|
+
const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
|
268
|
+
if (!hasQueryParams) {
|
247
269
|
output.push(
|
248
|
-
` ${op.operationId}: (
|
270
|
+
` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
|
249
271
|
);
|
272
|
+
continue;
|
273
|
+
}
|
274
|
+
output.push(` ${op.operationId}: (${signature}) => {`);
|
275
|
+
output.push(" const params = new URLSearchParams()");
|
276
|
+
for (const param of op.queryParams) {
|
277
|
+
const propertyKey = formatPropertyName(param.name);
|
278
|
+
const accessor = isValidJSIdentifier(param.name) ? param.name : propertyKey;
|
279
|
+
const schema = param.schema ?? {};
|
280
|
+
if (schema?.type === "array") {
|
281
|
+
const itemValueExpression = convertQueryParamValue(
|
282
|
+
schema.items ?? {},
|
283
|
+
"value"
|
284
|
+
);
|
285
|
+
if (param.required) {
|
286
|
+
output.push(` for (const value of ${accessor}) {`);
|
287
|
+
output.push(
|
288
|
+
` params.append("${param.name}", ${itemValueExpression})`
|
289
|
+
);
|
290
|
+
output.push(" }");
|
291
|
+
} else {
|
292
|
+
output.push(` if (${accessor} !== undefined) {`);
|
293
|
+
output.push(` for (const value of ${accessor}) {`);
|
294
|
+
output.push(
|
295
|
+
` params.append("${param.name}", ${itemValueExpression})`
|
296
|
+
);
|
297
|
+
output.push(" }");
|
298
|
+
output.push(" }");
|
299
|
+
}
|
300
|
+
continue;
|
301
|
+
}
|
302
|
+
const valueExpression = convertQueryParamValue(schema, accessor);
|
303
|
+
if (param.required) {
|
304
|
+
output.push(` params.set("${param.name}", ${valueExpression})`);
|
305
|
+
} else {
|
306
|
+
output.push(` if (${accessor} !== undefined) {`);
|
307
|
+
output.push(` params.set("${param.name}", ${valueExpression})`);
|
308
|
+
output.push(" }");
|
309
|
+
}
|
250
310
|
}
|
311
|
+
output.push(" const _searchParams = params.toString()");
|
312
|
+
output.push(
|
313
|
+
` return \`${pathWithParams}\${_searchParams ? \`?\${_searchParams}\` : ""}\``
|
314
|
+
);
|
315
|
+
output.push(" },");
|
251
316
|
}
|
252
317
|
output.push("} as const;");
|
253
318
|
output.push("");
|
@@ -339,6 +404,7 @@ function generate(spec, options = {}) {
|
|
339
404
|
output.push("} as const;");
|
340
405
|
output.push("");
|
341
406
|
}
|
407
|
+
generateOperationTypes(output, operations, typesConfig);
|
342
408
|
return output.join("\n");
|
343
409
|
}
|
344
410
|
function appendOperationField(buffer, key, value) {
|
@@ -374,11 +440,13 @@ function parseOperations(spec) {
|
|
374
440
|
);
|
375
441
|
const resolvedParameters = collectParameters(pathItem, operation, spec);
|
376
442
|
const requestHeaders = getRequestHeaders(resolvedParameters);
|
443
|
+
const queryParams = getQueryParams(resolvedParameters);
|
377
444
|
operations.push({
|
378
445
|
operationId: operation.operationId,
|
379
446
|
path: path2,
|
380
447
|
method: method.toLowerCase(),
|
381
448
|
pathParams,
|
449
|
+
queryParams,
|
382
450
|
requestType,
|
383
451
|
responseType: successResponse,
|
384
452
|
requestHeaders,
|
@@ -388,6 +456,129 @@ function parseOperations(spec) {
|
|
388
456
|
}
|
389
457
|
return operations;
|
390
458
|
}
|
459
|
+
function normalizeTypesConfig(config) {
|
460
|
+
return {
|
461
|
+
emit: config?.emit ?? true,
|
462
|
+
helpers: config?.helpers ?? "package",
|
463
|
+
helpersOutput: config?.helpersOutput ?? "./zenko-types"
|
464
|
+
};
|
465
|
+
}
|
466
|
+
function appendHelperTypesImport(buffer, config) {
|
467
|
+
if (!config.emit) return;
|
468
|
+
switch (config.helpers) {
|
469
|
+
case "package":
|
470
|
+
buffer.push(
|
471
|
+
'import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "zenko";'
|
472
|
+
);
|
473
|
+
return;
|
474
|
+
case "file":
|
475
|
+
buffer.push(
|
476
|
+
`import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "${config.helpersOutput}";`
|
477
|
+
);
|
478
|
+
return;
|
479
|
+
case "inline":
|
480
|
+
buffer.push(
|
481
|
+
"type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
|
482
|
+
);
|
483
|
+
buffer.push(
|
484
|
+
"type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
|
485
|
+
);
|
486
|
+
buffer.push(
|
487
|
+
"type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
|
488
|
+
);
|
489
|
+
buffer.push(" clientErrors?: Record<string, TClient>;");
|
490
|
+
buffer.push(" serverErrors?: Record<string, TServer>;");
|
491
|
+
buffer.push(" defaultErrors?: Record<string, TDefault>;");
|
492
|
+
buffer.push(" otherErrors?: Record<string, TOther>;");
|
493
|
+
buffer.push("};");
|
494
|
+
buffer.push(
|
495
|
+
"type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
496
|
+
);
|
497
|
+
buffer.push(" path: TPath;");
|
498
|
+
buffer.push(" request?: TRequest;");
|
499
|
+
buffer.push(" response?: TResponse;");
|
500
|
+
buffer.push(" headers?: THeaders;");
|
501
|
+
buffer.push(" errors?: TErrors;");
|
502
|
+
buffer.push("};");
|
503
|
+
return;
|
504
|
+
}
|
505
|
+
}
|
506
|
+
function generateOperationTypes(buffer, operations, config) {
|
507
|
+
if (!config.emit) return;
|
508
|
+
buffer.push("// Operation Types");
|
509
|
+
for (const op of operations) {
|
510
|
+
const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
|
511
|
+
const requestType = wrapTypeReference(op.requestType);
|
512
|
+
const responseType = wrapTypeReference(op.responseType);
|
513
|
+
const errorsType = buildOperationErrorsType(op.errors);
|
514
|
+
buffer.push(
|
515
|
+
`export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
|
516
|
+
);
|
517
|
+
buffer.push(` typeof paths.${op.operationId},`);
|
518
|
+
buffer.push(` ${requestType},`);
|
519
|
+
buffer.push(` ${responseType},`);
|
520
|
+
buffer.push(` ${headerType},`);
|
521
|
+
buffer.push(` ${errorsType}`);
|
522
|
+
buffer.push(`>;`);
|
523
|
+
buffer.push("");
|
524
|
+
}
|
525
|
+
}
|
526
|
+
function buildOperationErrorsType(errors) {
|
527
|
+
if (!errors || !hasAnyErrors(errors)) {
|
528
|
+
return "OperationErrors";
|
529
|
+
}
|
530
|
+
const client = buildErrorBucket(errors.clientErrors);
|
531
|
+
const server = buildErrorBucket(errors.serverErrors);
|
532
|
+
const fallback = buildErrorBucket(errors.defaultErrors);
|
533
|
+
const other = buildErrorBucket(errors.otherErrors);
|
534
|
+
return `OperationErrors<${client}, ${server}, ${fallback}, ${other}>`;
|
535
|
+
}
|
536
|
+
function buildErrorBucket(bucket) {
|
537
|
+
if (!bucket || Object.keys(bucket).length === 0) {
|
538
|
+
return "unknown";
|
539
|
+
}
|
540
|
+
const entries = Object.entries(bucket);
|
541
|
+
const accessibleEntries = entries.map(([name, type]) => {
|
542
|
+
const propertyKey = formatPropertyName(name);
|
543
|
+
const valueType = wrapErrorValueType(type);
|
544
|
+
return `${propertyKey}: ${valueType}`;
|
545
|
+
});
|
546
|
+
return `{ ${accessibleEntries.join("; ")} }`;
|
547
|
+
}
|
548
|
+
var TYPE_KEYWORDS = /* @__PURE__ */ new Set([
|
549
|
+
"any",
|
550
|
+
"unknown",
|
551
|
+
"never",
|
552
|
+
"void",
|
553
|
+
"null",
|
554
|
+
"undefined",
|
555
|
+
"string",
|
556
|
+
"number",
|
557
|
+
"boolean",
|
558
|
+
"bigint",
|
559
|
+
"symbol"
|
560
|
+
]);
|
561
|
+
function wrapTypeReference(typeName) {
|
562
|
+
if (!typeName) return "undefined";
|
563
|
+
if (typeName === "undefined") return "undefined";
|
564
|
+
if (TYPE_KEYWORDS.has(typeName)) return typeName;
|
565
|
+
if (typeName.startsWith("typeof ")) return typeName;
|
566
|
+
const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
567
|
+
if (identifierPattern.test(typeName)) {
|
568
|
+
return `typeof ${typeName}`;
|
569
|
+
}
|
570
|
+
return typeName;
|
571
|
+
}
|
572
|
+
function wrapErrorValueType(typeName) {
|
573
|
+
if (!typeName) return "unknown";
|
574
|
+
if (TYPE_KEYWORDS.has(typeName)) return typeName;
|
575
|
+
if (typeName.startsWith("typeof ")) return typeName;
|
576
|
+
const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
577
|
+
if (identifierPattern.test(typeName)) {
|
578
|
+
return `typeof ${typeName}`;
|
579
|
+
}
|
580
|
+
return typeName;
|
581
|
+
}
|
391
582
|
function collectParameters(pathItem, operation, spec) {
|
392
583
|
const parametersMap = /* @__PURE__ */ new Map();
|
393
584
|
const addParameters = (params) => {
|
@@ -530,6 +721,20 @@ function getRequestHeaders(parameters) {
|
|
530
721
|
}
|
531
722
|
return headers;
|
532
723
|
}
|
724
|
+
function getQueryParams(parameters) {
|
725
|
+
const queryParams = [];
|
726
|
+
for (const param of parameters ?? []) {
|
727
|
+
if (param.in === "query") {
|
728
|
+
queryParams.push({
|
729
|
+
name: param.name,
|
730
|
+
description: param.description,
|
731
|
+
schema: param.schema,
|
732
|
+
required: param.required
|
733
|
+
});
|
734
|
+
}
|
735
|
+
}
|
736
|
+
return queryParams;
|
737
|
+
}
|
533
738
|
function mapHeaderType(header) {
|
534
739
|
const schemaType = header.schema?.type;
|
535
740
|
switch (schemaType) {
|
@@ -542,6 +747,39 @@ function mapHeaderType(header) {
|
|
542
747
|
return "string";
|
543
748
|
}
|
544
749
|
}
|
750
|
+
function mapQueryType(param) {
|
751
|
+
return mapQuerySchemaType(param.schema);
|
752
|
+
}
|
753
|
+
function mapQuerySchemaType(schema) {
|
754
|
+
if (!schema) return "string";
|
755
|
+
if (schema.type === "array") {
|
756
|
+
const itemType = mapQuerySchemaType(schema.items);
|
757
|
+
return `Array<${itemType}>`;
|
758
|
+
}
|
759
|
+
switch (schema.type) {
|
760
|
+
case "integer":
|
761
|
+
case "number":
|
762
|
+
return "number";
|
763
|
+
case "boolean":
|
764
|
+
return "boolean";
|
765
|
+
default:
|
766
|
+
return "string";
|
767
|
+
}
|
768
|
+
}
|
769
|
+
function convertQueryParamValue(schema, accessor) {
|
770
|
+
if (!schema) {
|
771
|
+
return `String(${accessor})`;
|
772
|
+
}
|
773
|
+
switch (schema.type) {
|
774
|
+
case "integer":
|
775
|
+
case "number":
|
776
|
+
return `String(${accessor})`;
|
777
|
+
case "boolean":
|
778
|
+
return `${accessor} ? "true" : "false"`;
|
779
|
+
default:
|
780
|
+
return `String(${accessor})`;
|
781
|
+
}
|
782
|
+
}
|
545
783
|
function generateZodSchema(name, schema, generatedTypes, options) {
|
546
784
|
if (generatedTypes.has(name)) return "";
|
547
785
|
generatedTypes.add(name);
|
@@ -550,18 +788,7 @@ function generateZodSchema(name, schema, generatedTypes, options) {
|
|
550
788
|
return `export const ${name} = z.enum([${enumValues}]);`;
|
551
789
|
}
|
552
790
|
if (schema.type === "object" || schema.properties) {
|
553
|
-
const
|
554
|
-
for (const [propName, propSchema] of Object.entries(
|
555
|
-
schema.properties || {}
|
556
|
-
)) {
|
557
|
-
const isRequired = schema.required?.includes(propName) ?? false;
|
558
|
-
const zodType = getZodTypeFromSchema(propSchema, options);
|
559
|
-
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
560
|
-
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
561
|
-
}
|
562
|
-
return `export const ${name} = z.object({
|
563
|
-
${properties.join("\n")}
|
564
|
-
});`;
|
791
|
+
return `export const ${name} = ${buildZodObject(schema, options)};`;
|
565
792
|
}
|
566
793
|
if (schema.type === "array") {
|
567
794
|
const itemSchema = schema.items ?? { type: "unknown" };
|
@@ -584,6 +811,9 @@ function getZodTypeFromSchema(schema, options) {
|
|
584
811
|
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
585
812
|
return `z.enum([${enumValues}])`;
|
586
813
|
}
|
814
|
+
if (schema.type === "object" || schema.properties) {
|
815
|
+
return buildZodObject(schema, options);
|
816
|
+
}
|
587
817
|
switch (schema.type) {
|
588
818
|
case "string":
|
589
819
|
return buildString(schema, options);
|
@@ -604,6 +834,23 @@ function getZodTypeFromSchema(schema, options) {
|
|
604
834
|
return "z.unknown()";
|
605
835
|
}
|
606
836
|
}
|
837
|
+
function buildZodObject(schema, options) {
|
838
|
+
const properties = [];
|
839
|
+
for (const [propName, propSchema] of Object.entries(
|
840
|
+
schema.properties || {}
|
841
|
+
)) {
|
842
|
+
const isRequired = schema.required?.includes(propName) ?? false;
|
843
|
+
const zodType = getZodTypeFromSchema(propSchema, options);
|
844
|
+
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
845
|
+
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
846
|
+
}
|
847
|
+
if (properties.length === 0) {
|
848
|
+
return "z.object({})";
|
849
|
+
}
|
850
|
+
return `z.object({
|
851
|
+
${properties.join("\n")}
|
852
|
+
})`;
|
853
|
+
}
|
607
854
|
function buildString(schema, options) {
|
608
855
|
if (options.strictDates) {
|
609
856
|
switch (schema.format) {
|
@@ -794,7 +1041,7 @@ function printHelp() {
|
|
794
1041
|
console.log("");
|
795
1042
|
console.log("Config file format:");
|
796
1043
|
console.log(
|
797
|
-
' {"schemas": [{ input, output, strictDates?, strictNumeric? }] }'
|
1044
|
+
' {"types"?: { emit?, helpers?, helpersOutput? }, "schemas": [{ input, output, strictDates?, strictNumeric?, types? }] }'
|
798
1045
|
);
|
799
1046
|
}
|
800
1047
|
async function runFromConfig(parsed) {
|
@@ -803,14 +1050,17 @@ async function runFromConfig(parsed) {
|
|
803
1050
|
const config = await loadConfig(resolvedConfigPath);
|
804
1051
|
validateConfig(config);
|
805
1052
|
const baseDir = path.dirname(resolvedConfigPath);
|
1053
|
+
const baseTypesConfig = config.types;
|
806
1054
|
for (const entry of config.schemas) {
|
807
1055
|
const inputFile = resolvePath(entry.input, baseDir);
|
808
1056
|
const outputFile = resolvePath(entry.output, baseDir);
|
1057
|
+
const typesConfig = resolveTypesConfig(baseTypesConfig, entry.types);
|
809
1058
|
await generateSingle({
|
810
1059
|
inputFile,
|
811
1060
|
outputFile,
|
812
1061
|
strictDates: entry.strictDates ?? parsed.strictDates,
|
813
|
-
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric
|
1062
|
+
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric,
|
1063
|
+
typesConfig
|
814
1064
|
});
|
815
1065
|
}
|
816
1066
|
}
|
@@ -847,12 +1097,23 @@ function validateConfig(config) {
|
|
847
1097
|
function resolvePath(filePath, baseDir) {
|
848
1098
|
return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
|
849
1099
|
}
|
1100
|
+
function resolveTypesConfig(baseConfig, entryConfig) {
|
1101
|
+
if (!baseConfig && !entryConfig) return void 0;
|
1102
|
+
return {
|
1103
|
+
...baseConfig,
|
1104
|
+
...entryConfig
|
1105
|
+
};
|
1106
|
+
}
|
850
1107
|
async function generateSingle(options) {
|
851
|
-
const { inputFile, outputFile, strictDates, strictNumeric } = options;
|
1108
|
+
const { inputFile, outputFile, strictDates, strictNumeric, typesConfig } = options;
|
852
1109
|
const resolvedInput = path.resolve(inputFile);
|
853
1110
|
const resolvedOutput = path.resolve(outputFile);
|
854
1111
|
const spec = readSpec(resolvedInput);
|
855
|
-
const output = generate(spec, {
|
1112
|
+
const output = generate(spec, {
|
1113
|
+
strictDates,
|
1114
|
+
strictNumeric,
|
1115
|
+
types: typesConfig
|
1116
|
+
});
|
856
1117
|
fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
|
857
1118
|
fs.writeFileSync(resolvedOutput, output);
|
858
1119
|
console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
|