zenko 0.1.2 → 0.1.4
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/dist/cli.cjs +153 -79
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +153 -79
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +153 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.mjs +153 -79
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -261,8 +261,11 @@ function generate(spec, options = {}) {
|
|
|
261
261
|
const pathParamNames = op.pathParams.map((p) => p.name);
|
|
262
262
|
const hasPathParams = pathParamNames.length > 0;
|
|
263
263
|
const hasQueryParams = op.queryParams.length > 0;
|
|
264
|
+
const camelCaseOperationId = toCamelCase(op.operationId);
|
|
264
265
|
if (!hasPathParams && !hasQueryParams) {
|
|
265
|
-
output.push(
|
|
266
|
+
output.push(
|
|
267
|
+
` ${formatPropertyName(camelCaseOperationId)}: () => "${op.path}",`
|
|
268
|
+
);
|
|
266
269
|
continue;
|
|
267
270
|
}
|
|
268
271
|
const allParamNames = [
|
|
@@ -285,11 +288,13 @@ function generate(spec, options = {}) {
|
|
|
285
288
|
const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
|
|
286
289
|
if (!hasQueryParams) {
|
|
287
290
|
output.push(
|
|
288
|
-
` ${
|
|
291
|
+
` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
|
|
289
292
|
);
|
|
290
293
|
continue;
|
|
291
294
|
}
|
|
292
|
-
output.push(
|
|
295
|
+
output.push(
|
|
296
|
+
` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => {`
|
|
297
|
+
);
|
|
293
298
|
output.push(" const params = new URLSearchParams()");
|
|
294
299
|
for (const param of op.queryParams) {
|
|
295
300
|
const propertyKey = formatPropertyName(param.name);
|
|
@@ -334,82 +339,57 @@ function generate(spec, options = {}) {
|
|
|
334
339
|
}
|
|
335
340
|
output.push("} as const;");
|
|
336
341
|
output.push("");
|
|
337
|
-
output.push("// Header
|
|
338
|
-
output.push("export const
|
|
342
|
+
output.push("// Header Schemas");
|
|
343
|
+
output.push("export const headerSchemas = {");
|
|
339
344
|
for (const op of operations) {
|
|
345
|
+
const camelCaseOperationId = toCamelCase(op.operationId);
|
|
340
346
|
if (!op.requestHeaders || op.requestHeaders.length === 0) {
|
|
341
|
-
output.push(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const typeEntries = op.requestHeaders.map(
|
|
345
|
-
(header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
|
|
346
|
-
header
|
|
347
|
-
)}`
|
|
348
|
-
).join(", ");
|
|
349
|
-
const requiredHeaders = op.requestHeaders.filter(
|
|
350
|
-
(header) => header.required
|
|
351
|
-
);
|
|
352
|
-
const optionalHeaders = op.requestHeaders.filter(
|
|
353
|
-
(header) => !header.required
|
|
354
|
-
);
|
|
355
|
-
const hasRequired = requiredHeaders.length > 0;
|
|
356
|
-
const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
|
|
357
|
-
if (optionalHeaders.length === 0) {
|
|
358
|
-
output.push(` ${op.operationId}: ${signature} => ({`);
|
|
359
|
-
for (const header of requiredHeaders) {
|
|
360
|
-
const propertyKey = formatPropertyName(header.name);
|
|
361
|
-
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
|
362
|
-
output.push(` ${propertyKey}: ${accessor},`);
|
|
363
|
-
}
|
|
364
|
-
output.push(" }),");
|
|
347
|
+
output.push(
|
|
348
|
+
` ${formatPropertyName(camelCaseOperationId)}: z.object({}),`
|
|
349
|
+
);
|
|
365
350
|
continue;
|
|
366
351
|
}
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
352
|
+
const schemaFields = op.requestHeaders.map((header) => {
|
|
353
|
+
const zodType = mapHeaderToZodType(header);
|
|
354
|
+
const optional = header.required ? "" : ".optional()";
|
|
355
|
+
return ` ${formatPropertyName(header.name)}: ${zodType}${optional},`;
|
|
356
|
+
}).join("\n");
|
|
357
|
+
output.push(` ${formatPropertyName(camelCaseOperationId)}: z.object({`);
|
|
358
|
+
output.push(schemaFields);
|
|
359
|
+
output.push(" }),");
|
|
360
|
+
}
|
|
361
|
+
output.push("} as const;");
|
|
362
|
+
output.push("");
|
|
363
|
+
output.push("// Header Functions");
|
|
364
|
+
output.push("export const headers = {");
|
|
365
|
+
for (const op of operations) {
|
|
366
|
+
const camelCaseOperationId = toCamelCase(op.operationId);
|
|
367
|
+
if (!op.requestHeaders || op.requestHeaders.length === 0) {
|
|
372
368
|
output.push(
|
|
373
|
-
`
|
|
369
|
+
` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
|
|
374
370
|
);
|
|
375
371
|
continue;
|
|
376
372
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
)
|
|
380
|
-
output.push(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
for (const header of requiredHeaders) {
|
|
384
|
-
const propertyKey = formatPropertyName(header.name);
|
|
385
|
-
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
|
386
|
-
output.push(` ${propertyKey}: ${accessor},`);
|
|
387
|
-
}
|
|
388
|
-
output.push(" }");
|
|
389
|
-
} else {
|
|
390
|
-
output.push(` const headers: Record<string, ${valueTypes}> = {}`);
|
|
391
|
-
}
|
|
392
|
-
for (const header of optionalHeaders) {
|
|
393
|
-
const propertyKey = formatPropertyName(header.name);
|
|
394
|
-
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
|
395
|
-
const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
|
|
396
|
-
output.push(` if (${accessor} !== undefined) {`);
|
|
397
|
-
output.push(` ${assignment} = ${accessor}`);
|
|
398
|
-
output.push(" }");
|
|
399
|
-
}
|
|
400
|
-
output.push(" return headers");
|
|
373
|
+
output.push(
|
|
374
|
+
` ${formatPropertyName(camelCaseOperationId)}: (params: z.input<${isValidJSIdentifier(camelCaseOperationId) ? `typeof headerSchemas.${camelCaseOperationId}` : `(typeof headerSchemas)[${formatPropertyName(camelCaseOperationId)}]`}>) => {`
|
|
375
|
+
);
|
|
376
|
+
output.push(
|
|
377
|
+
` return ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse(params)`
|
|
378
|
+
);
|
|
401
379
|
output.push(" },");
|
|
402
380
|
}
|
|
403
381
|
output.push("} as const;");
|
|
404
382
|
output.push("");
|
|
405
383
|
output.push("// Operation Objects");
|
|
406
384
|
for (const op of operations) {
|
|
407
|
-
|
|
408
|
-
output.push(`
|
|
385
|
+
const camelCaseOperationId = toCamelCase(op.operationId);
|
|
386
|
+
output.push(`export const ${camelCaseOperationId} = {`);
|
|
387
|
+
output.push(` method: "${op.method}",`);
|
|
388
|
+
output.push(` path: paths.${camelCaseOperationId},`);
|
|
409
389
|
appendOperationField(output, "request", op.requestType);
|
|
410
390
|
appendOperationField(output, "response", op.responseType);
|
|
411
391
|
if (op.requestHeaders && op.requestHeaders.length > 0) {
|
|
412
|
-
output.push(` headers: headers.${
|
|
392
|
+
output.push(` headers: headers.${camelCaseOperationId},`);
|
|
413
393
|
}
|
|
414
394
|
if (op.errors && hasAnyErrors(op.errors)) {
|
|
415
395
|
output.push(" errors: {");
|
|
@@ -445,10 +425,57 @@ function hasAnyErrors(group) {
|
|
|
445
425
|
group.otherErrors
|
|
446
426
|
].some((bucket) => bucket && Object.keys(bucket).length > 0);
|
|
447
427
|
}
|
|
428
|
+
function isRequestMethod(method) {
|
|
429
|
+
switch (method) {
|
|
430
|
+
case "get":
|
|
431
|
+
case "put":
|
|
432
|
+
case "post":
|
|
433
|
+
case "delete":
|
|
434
|
+
case "options":
|
|
435
|
+
case "head":
|
|
436
|
+
case "patch":
|
|
437
|
+
case "trace":
|
|
438
|
+
return true;
|
|
439
|
+
default:
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
var CONTENT_TYPE_MAP = {
|
|
444
|
+
"application/json": "unknown",
|
|
445
|
+
// Will use schema when available
|
|
446
|
+
"text/csv": "string",
|
|
447
|
+
"text/plain": "string",
|
|
448
|
+
// Binary/ambiguous types default to unknown for cross-platform compatibility
|
|
449
|
+
"application/octet-stream": "unknown",
|
|
450
|
+
"application/pdf": "unknown"
|
|
451
|
+
};
|
|
452
|
+
function findContentType(content) {
|
|
453
|
+
const contentTypes = Object.keys(content);
|
|
454
|
+
if (contentTypes.includes("application/json")) {
|
|
455
|
+
return "application/json";
|
|
456
|
+
}
|
|
457
|
+
for (const contentType of contentTypes) {
|
|
458
|
+
if (contentType in CONTENT_TYPE_MAP) {
|
|
459
|
+
return contentType;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return contentTypes[0] || "";
|
|
463
|
+
}
|
|
464
|
+
function inferResponseType(contentType, statusCode) {
|
|
465
|
+
if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
|
|
466
|
+
return "undefined";
|
|
467
|
+
}
|
|
468
|
+
if (contentType in CONTENT_TYPE_MAP) {
|
|
469
|
+
return CONTENT_TYPE_MAP[contentType];
|
|
470
|
+
}
|
|
471
|
+
return "unknown";
|
|
472
|
+
}
|
|
448
473
|
function parseOperations(spec) {
|
|
449
474
|
const operations = [];
|
|
450
475
|
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
451
476
|
for (const [method, operation] of Object.entries(pathItem)) {
|
|
477
|
+
const normalizedMethod = method.toLowerCase();
|
|
478
|
+
if (!isRequestMethod(normalizedMethod)) continue;
|
|
452
479
|
if (!operation.operationId) continue;
|
|
453
480
|
const pathParams = extractPathParams(path);
|
|
454
481
|
const requestType = getRequestType(operation);
|
|
@@ -462,7 +489,7 @@ function parseOperations(spec) {
|
|
|
462
489
|
operations.push({
|
|
463
490
|
operationId: operation.operationId,
|
|
464
491
|
path,
|
|
465
|
-
method:
|
|
492
|
+
method: normalizedMethod,
|
|
466
493
|
pathParams,
|
|
467
494
|
queryParams,
|
|
468
495
|
requestType,
|
|
@@ -498,20 +525,24 @@ function appendHelperTypesImport(buffer, config) {
|
|
|
498
525
|
buffer.push(
|
|
499
526
|
"type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
|
|
500
527
|
);
|
|
528
|
+
buffer.push(
|
|
529
|
+
'type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";'
|
|
530
|
+
);
|
|
501
531
|
buffer.push(
|
|
502
532
|
"type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
|
|
503
533
|
);
|
|
504
534
|
buffer.push(
|
|
505
535
|
"type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
|
|
506
536
|
);
|
|
507
|
-
buffer.push(" clientErrors?:
|
|
508
|
-
buffer.push(" serverErrors?:
|
|
509
|
-
buffer.push(" defaultErrors?:
|
|
510
|
-
buffer.push(" otherErrors?:
|
|
537
|
+
buffer.push(" clientErrors?: TClient;");
|
|
538
|
+
buffer.push(" serverErrors?: TServer;");
|
|
539
|
+
buffer.push(" defaultErrors?: TDefault;");
|
|
540
|
+
buffer.push(" otherErrors?: TOther;");
|
|
511
541
|
buffer.push("};");
|
|
512
542
|
buffer.push(
|
|
513
|
-
"type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
|
543
|
+
"type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
|
514
544
|
);
|
|
545
|
+
buffer.push(" method: TMethod;");
|
|
515
546
|
buffer.push(" path: TPath;");
|
|
516
547
|
buffer.push(" request?: TRequest;");
|
|
517
548
|
buffer.push(" response?: TResponse;");
|
|
@@ -525,14 +556,18 @@ function generateOperationTypes(buffer, operations, config) {
|
|
|
525
556
|
if (!config.emit) return;
|
|
526
557
|
buffer.push("// Operation Types");
|
|
527
558
|
for (const op of operations) {
|
|
528
|
-
const
|
|
559
|
+
const camelCaseOperationId = toCamelCase(op.operationId);
|
|
560
|
+
const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
|
|
529
561
|
const requestType = wrapTypeReference(op.requestType);
|
|
530
562
|
const responseType = wrapTypeReference(op.responseType);
|
|
531
563
|
const errorsType = buildOperationErrorsType(op.errors);
|
|
532
564
|
buffer.push(
|
|
533
|
-
`export type ${capitalize(
|
|
565
|
+
`export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
|
|
566
|
+
);
|
|
567
|
+
buffer.push(` "${op.method}",`);
|
|
568
|
+
buffer.push(
|
|
569
|
+
` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
|
|
534
570
|
);
|
|
535
|
-
buffer.push(` typeof paths.${op.operationId},`);
|
|
536
571
|
buffer.push(` ${requestType},`);
|
|
537
572
|
buffer.push(` ${responseType},`);
|
|
538
573
|
buffer.push(` ${headerType},`);
|
|
@@ -655,13 +690,34 @@ function getResponseTypes(operation, operationId) {
|
|
|
655
690
|
const successCodes = /* @__PURE__ */ new Map();
|
|
656
691
|
const errorEntries = [];
|
|
657
692
|
for (const [statusCode, response] of Object.entries(responses)) {
|
|
658
|
-
const
|
|
659
|
-
if (!
|
|
693
|
+
const content = response?.content;
|
|
694
|
+
if (!content || Object.keys(content).length === 0) {
|
|
695
|
+
if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
|
|
696
|
+
successCodes.set(statusCode, "undefined");
|
|
697
|
+
}
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const contentType = findContentType(content);
|
|
701
|
+
const resolvedSchema = content[contentType]?.schema;
|
|
702
|
+
if (!resolvedSchema) {
|
|
703
|
+
const inferredType = inferResponseType(contentType, statusCode);
|
|
704
|
+
if (inferredType) {
|
|
705
|
+
if (isErrorStatus(statusCode)) {
|
|
706
|
+
errorEntries.push({
|
|
707
|
+
code: statusCode,
|
|
708
|
+
schema: inferredType
|
|
709
|
+
});
|
|
710
|
+
} else if (/^2\d\d$/.test(statusCode)) {
|
|
711
|
+
successCodes.set(statusCode, inferredType);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
660
716
|
if (isErrorStatus(statusCode)) {
|
|
661
717
|
errorEntries.push({ code: statusCode, schema: resolvedSchema });
|
|
662
718
|
continue;
|
|
663
719
|
}
|
|
664
|
-
if (/^2\d\d$/.test(statusCode)
|
|
720
|
+
if (/^2\d\d$/.test(statusCode)) {
|
|
665
721
|
successCodes.set(statusCode, resolvedSchema);
|
|
666
722
|
}
|
|
667
723
|
}
|
|
@@ -675,6 +731,9 @@ function selectSuccessResponse(responses, operationId) {
|
|
|
675
731
|
for (const code of preferredOrder) {
|
|
676
732
|
const schema = responses.get(code);
|
|
677
733
|
if (schema) {
|
|
734
|
+
if (typeof schema === "string") {
|
|
735
|
+
return schema;
|
|
736
|
+
}
|
|
678
737
|
return resolveResponseType(
|
|
679
738
|
schema,
|
|
680
739
|
`${capitalize(operationId)}Response${code}`
|
|
@@ -683,6 +742,9 @@ function selectSuccessResponse(responses, operationId) {
|
|
|
683
742
|
}
|
|
684
743
|
const [firstCode, firstSchema] = responses.entries().next().value ?? [];
|
|
685
744
|
if (!firstSchema) return void 0;
|
|
745
|
+
if (typeof firstSchema === "string") {
|
|
746
|
+
return firstSchema;
|
|
747
|
+
}
|
|
686
748
|
return resolveResponseType(
|
|
687
749
|
firstSchema,
|
|
688
750
|
`${capitalize(operationId)}Response${firstCode ?? "Default"}`
|
|
@@ -720,6 +782,9 @@ function buildErrorGroups(errors = [], operationId) {
|
|
|
720
782
|
return group;
|
|
721
783
|
}
|
|
722
784
|
function resolveResponseType(schema, fallbackName) {
|
|
785
|
+
if (typeof schema === "string") {
|
|
786
|
+
return schema;
|
|
787
|
+
}
|
|
723
788
|
if (schema.$ref) {
|
|
724
789
|
return extractRefName(schema.$ref);
|
|
725
790
|
}
|
|
@@ -753,16 +818,22 @@ function getQueryParams(parameters) {
|
|
|
753
818
|
}
|
|
754
819
|
return queryParams;
|
|
755
820
|
}
|
|
756
|
-
function
|
|
757
|
-
const
|
|
821
|
+
function mapHeaderToZodType(header) {
|
|
822
|
+
const schema = header.schema ?? {};
|
|
823
|
+
const schemaType = schema.type;
|
|
758
824
|
switch (schemaType) {
|
|
759
825
|
case "integer":
|
|
760
826
|
case "number":
|
|
761
|
-
return "number";
|
|
827
|
+
return "z.coerce.number()";
|
|
762
828
|
case "boolean":
|
|
763
|
-
return "boolean";
|
|
829
|
+
return "z.coerce.boolean()";
|
|
830
|
+
case "array": {
|
|
831
|
+
const items = schema.items ?? { type: "string" };
|
|
832
|
+
const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
|
|
833
|
+
return `z.array(${itemType})`;
|
|
834
|
+
}
|
|
764
835
|
default:
|
|
765
|
-
return "string";
|
|
836
|
+
return "z.string()";
|
|
766
837
|
}
|
|
767
838
|
}
|
|
768
839
|
function mapQueryType(param) {
|
|
@@ -966,6 +1037,9 @@ function applyNumericBounds(schema, builder) {
|
|
|
966
1037
|
}
|
|
967
1038
|
return builder;
|
|
968
1039
|
}
|
|
1040
|
+
function toCamelCase(str) {
|
|
1041
|
+
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1042
|
+
}
|
|
969
1043
|
function capitalize(str) {
|
|
970
1044
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
971
1045
|
}
|