zenko 0.1.0 → 0.1.1
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 +245 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +245 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +227 -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 +227 -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,91 @@ 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 = op.requestType ?? "undefined";
|
512
|
+
const responseType = op.responseType ?? "undefined";
|
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 typeEntries = entries.map(([name, type]) => `${formatPropertyName(name)}: ${type}`).join("; ");
|
542
|
+
return `{ ${typeEntries} }`;
|
543
|
+
}
|
391
544
|
function collectParameters(pathItem, operation, spec) {
|
392
545
|
const parametersMap = /* @__PURE__ */ new Map();
|
393
546
|
const addParameters = (params) => {
|
@@ -530,6 +683,20 @@ function getRequestHeaders(parameters) {
|
|
530
683
|
}
|
531
684
|
return headers;
|
532
685
|
}
|
686
|
+
function getQueryParams(parameters) {
|
687
|
+
const queryParams = [];
|
688
|
+
for (const param of parameters ?? []) {
|
689
|
+
if (param.in === "query") {
|
690
|
+
queryParams.push({
|
691
|
+
name: param.name,
|
692
|
+
description: param.description,
|
693
|
+
schema: param.schema,
|
694
|
+
required: param.required
|
695
|
+
});
|
696
|
+
}
|
697
|
+
}
|
698
|
+
return queryParams;
|
699
|
+
}
|
533
700
|
function mapHeaderType(header) {
|
534
701
|
const schemaType = header.schema?.type;
|
535
702
|
switch (schemaType) {
|
@@ -542,6 +709,39 @@ function mapHeaderType(header) {
|
|
542
709
|
return "string";
|
543
710
|
}
|
544
711
|
}
|
712
|
+
function mapQueryType(param) {
|
713
|
+
return mapQuerySchemaType(param.schema);
|
714
|
+
}
|
715
|
+
function mapQuerySchemaType(schema) {
|
716
|
+
if (!schema) return "string";
|
717
|
+
if (schema.type === "array") {
|
718
|
+
const itemType = mapQuerySchemaType(schema.items);
|
719
|
+
return `Array<${itemType}>`;
|
720
|
+
}
|
721
|
+
switch (schema.type) {
|
722
|
+
case "integer":
|
723
|
+
case "number":
|
724
|
+
return "number";
|
725
|
+
case "boolean":
|
726
|
+
return "boolean";
|
727
|
+
default:
|
728
|
+
return "string";
|
729
|
+
}
|
730
|
+
}
|
731
|
+
function convertQueryParamValue(schema, accessor) {
|
732
|
+
if (!schema) {
|
733
|
+
return `String(${accessor})`;
|
734
|
+
}
|
735
|
+
switch (schema.type) {
|
736
|
+
case "integer":
|
737
|
+
case "number":
|
738
|
+
return `String(${accessor})`;
|
739
|
+
case "boolean":
|
740
|
+
return `${accessor} ? "true" : "false"`;
|
741
|
+
default:
|
742
|
+
return `String(${accessor})`;
|
743
|
+
}
|
744
|
+
}
|
545
745
|
function generateZodSchema(name, schema, generatedTypes, options) {
|
546
746
|
if (generatedTypes.has(name)) return "";
|
547
747
|
generatedTypes.add(name);
|
@@ -550,18 +750,7 @@ function generateZodSchema(name, schema, generatedTypes, options) {
|
|
550
750
|
return `export const ${name} = z.enum([${enumValues}]);`;
|
551
751
|
}
|
552
752
|
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
|
-
});`;
|
753
|
+
return `export const ${name} = ${buildZodObject(schema, options)};`;
|
565
754
|
}
|
566
755
|
if (schema.type === "array") {
|
567
756
|
const itemSchema = schema.items ?? { type: "unknown" };
|
@@ -584,6 +773,9 @@ function getZodTypeFromSchema(schema, options) {
|
|
584
773
|
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
585
774
|
return `z.enum([${enumValues}])`;
|
586
775
|
}
|
776
|
+
if (schema.type === "object" || schema.properties) {
|
777
|
+
return buildZodObject(schema, options);
|
778
|
+
}
|
587
779
|
switch (schema.type) {
|
588
780
|
case "string":
|
589
781
|
return buildString(schema, options);
|
@@ -604,6 +796,23 @@ function getZodTypeFromSchema(schema, options) {
|
|
604
796
|
return "z.unknown()";
|
605
797
|
}
|
606
798
|
}
|
799
|
+
function buildZodObject(schema, options) {
|
800
|
+
const properties = [];
|
801
|
+
for (const [propName, propSchema] of Object.entries(
|
802
|
+
schema.properties || {}
|
803
|
+
)) {
|
804
|
+
const isRequired = schema.required?.includes(propName) ?? false;
|
805
|
+
const zodType = getZodTypeFromSchema(propSchema, options);
|
806
|
+
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
807
|
+
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
808
|
+
}
|
809
|
+
if (properties.length === 0) {
|
810
|
+
return "z.object({})";
|
811
|
+
}
|
812
|
+
return `z.object({
|
813
|
+
${properties.join("\n")}
|
814
|
+
})`;
|
815
|
+
}
|
607
816
|
function buildString(schema, options) {
|
608
817
|
if (options.strictDates) {
|
609
818
|
switch (schema.format) {
|
@@ -794,7 +1003,7 @@ function printHelp() {
|
|
794
1003
|
console.log("");
|
795
1004
|
console.log("Config file format:");
|
796
1005
|
console.log(
|
797
|
-
' {"schemas": [{ input, output, strictDates?, strictNumeric? }] }'
|
1006
|
+
' {"types"?: { emit?, helpers?, helpersOutput? }, "schemas": [{ input, output, strictDates?, strictNumeric?, types? }] }'
|
798
1007
|
);
|
799
1008
|
}
|
800
1009
|
async function runFromConfig(parsed) {
|
@@ -803,14 +1012,17 @@ async function runFromConfig(parsed) {
|
|
803
1012
|
const config = await loadConfig(resolvedConfigPath);
|
804
1013
|
validateConfig(config);
|
805
1014
|
const baseDir = path.dirname(resolvedConfigPath);
|
1015
|
+
const baseTypesConfig = config.types;
|
806
1016
|
for (const entry of config.schemas) {
|
807
1017
|
const inputFile = resolvePath(entry.input, baseDir);
|
808
1018
|
const outputFile = resolvePath(entry.output, baseDir);
|
1019
|
+
const typesConfig = resolveTypesConfig(baseTypesConfig, entry.types);
|
809
1020
|
await generateSingle({
|
810
1021
|
inputFile,
|
811
1022
|
outputFile,
|
812
1023
|
strictDates: entry.strictDates ?? parsed.strictDates,
|
813
|
-
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric
|
1024
|
+
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric,
|
1025
|
+
typesConfig
|
814
1026
|
});
|
815
1027
|
}
|
816
1028
|
}
|
@@ -847,12 +1059,23 @@ function validateConfig(config) {
|
|
847
1059
|
function resolvePath(filePath, baseDir) {
|
848
1060
|
return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
|
849
1061
|
}
|
1062
|
+
function resolveTypesConfig(baseConfig, entryConfig) {
|
1063
|
+
if (!baseConfig && !entryConfig) return void 0;
|
1064
|
+
return {
|
1065
|
+
...baseConfig,
|
1066
|
+
...entryConfig
|
1067
|
+
};
|
1068
|
+
}
|
850
1069
|
async function generateSingle(options) {
|
851
|
-
const { inputFile, outputFile, strictDates, strictNumeric } = options;
|
1070
|
+
const { inputFile, outputFile, strictDates, strictNumeric, typesConfig } = options;
|
852
1071
|
const resolvedInput = path.resolve(inputFile);
|
853
1072
|
const resolvedOutput = path.resolve(outputFile);
|
854
1073
|
const spec = readSpec(resolvedInput);
|
855
|
-
const output = generate(spec, {
|
1074
|
+
const output = generate(spec, {
|
1075
|
+
strictDates,
|
1076
|
+
strictNumeric,
|
1077
|
+
types: typesConfig
|
1078
|
+
});
|
856
1079
|
fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
|
857
1080
|
fs.writeFileSync(resolvedOutput, output);
|
858
1081
|
console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
|