zenko 0.1.3 → 0.1.5

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/index.cjs CHANGED
@@ -228,7 +228,7 @@ function isErrorStatus(status) {
228
228
  }
229
229
 
230
230
  // src/zenko.ts
231
- function generate(spec, options = {}) {
231
+ function generateWithMetadata(spec, options = {}) {
232
232
  const output = [];
233
233
  const generatedTypes = /* @__PURE__ */ new Set();
234
234
  const { strictDates = false, strictNumeric = false } = options;
@@ -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(` ${op.operationId}: () => "${op.path}",`);
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
- ` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
291
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
289
292
  );
290
293
  continue;
291
294
  }
292
- output.push(` ${op.operationId}: (${signature}) => {`);
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,83 +339,57 @@ function generate(spec, options = {}) {
334
339
  }
335
340
  output.push("} as const;");
336
341
  output.push("");
337
- output.push("// Header Functions");
338
- output.push("export const headers = {");
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(` ${op.operationId}: () => ({}),`);
342
- continue;
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
- if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
368
- const header = optionalHeaders[0];
369
- const propertyKey = formatPropertyName(header.name);
370
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
371
- output.push(` ${op.operationId}: ${signature} =>`);
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
- ` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
369
+ ` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
374
370
  );
375
371
  continue;
376
372
  }
377
- const valueTypes = Array.from(
378
- new Set(optionalHeaders.map((header) => mapHeaderType(header)))
379
- ).join(" | ");
380
- output.push(` ${op.operationId}: ${signature} => {`);
381
- if (hasRequired) {
382
- output.push(" const headers = {");
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
- output.push(`export const ${op.operationId} = {`);
385
+ const camelCaseOperationId = toCamelCase(op.operationId);
386
+ output.push(`export const ${camelCaseOperationId} = {`);
408
387
  output.push(` method: "${op.method}",`);
409
- output.push(` path: paths.${op.operationId},`);
388
+ output.push(` path: paths.${camelCaseOperationId},`);
410
389
  appendOperationField(output, "request", op.requestType);
411
390
  appendOperationField(output, "response", op.responseType);
412
391
  if (op.requestHeaders && op.requestHeaders.length > 0) {
413
- output.push(` headers: headers.${op.operationId},`);
392
+ output.push(` headers: headers.${camelCaseOperationId},`);
414
393
  }
415
394
  if (op.errors && hasAnyErrors(op.errors)) {
416
395
  output.push(" errors: {");
@@ -424,7 +403,19 @@ function generate(spec, options = {}) {
424
403
  output.push("");
425
404
  }
426
405
  generateOperationTypes(output, operations, typesConfig);
427
- return output.join("\n");
406
+ const result = {
407
+ output: output.join("\n")
408
+ };
409
+ if (typesConfig.emit && typesConfig.helpers === "file" && typesConfig.helpersOutput) {
410
+ result.helperFile = {
411
+ path: typesConfig.helpersOutput,
412
+ content: generateHelperFile()
413
+ };
414
+ }
415
+ return result;
416
+ }
417
+ function generate(spec, options = {}) {
418
+ return generateWithMetadata(spec, options).output;
428
419
  }
429
420
  function appendOperationField(buffer, key, value) {
430
421
  if (!value) return;
@@ -461,6 +452,36 @@ function isRequestMethod(method) {
461
452
  return false;
462
453
  }
463
454
  }
455
+ var CONTENT_TYPE_MAP = {
456
+ "application/json": "unknown",
457
+ // Will use schema when available
458
+ "text/csv": "string",
459
+ "text/plain": "string",
460
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
461
+ "application/octet-stream": "unknown",
462
+ "application/pdf": "unknown"
463
+ };
464
+ function findContentType(content) {
465
+ const contentTypes = Object.keys(content);
466
+ if (contentTypes.includes("application/json")) {
467
+ return "application/json";
468
+ }
469
+ for (const contentType of contentTypes) {
470
+ if (contentType in CONTENT_TYPE_MAP) {
471
+ return contentType;
472
+ }
473
+ }
474
+ return contentTypes[0] || "";
475
+ }
476
+ function inferResponseType(contentType, statusCode) {
477
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
478
+ return "undefined";
479
+ }
480
+ if (contentType in CONTENT_TYPE_MAP) {
481
+ return CONTENT_TYPE_MAP[contentType];
482
+ }
483
+ return "unknown";
484
+ }
464
485
  function parseOperations(spec) {
465
486
  const operations = [];
466
487
  for (const [path, pathItem] of Object.entries(spec.paths)) {
@@ -522,6 +543,9 @@ function appendHelperTypesImport(buffer, config) {
522
543
  buffer.push(
523
544
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
524
545
  );
546
+ buffer.push(
547
+ "type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown);"
548
+ );
525
549
  buffer.push(
526
550
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
527
551
  );
@@ -531,7 +555,7 @@ function appendHelperTypesImport(buffer, config) {
531
555
  buffer.push(" otherErrors?: TOther;");
532
556
  buffer.push("};");
533
557
  buffer.push(
534
- "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
558
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
535
559
  );
536
560
  buffer.push(" method: TMethod;");
537
561
  buffer.push(" path: TPath;");
@@ -547,15 +571,18 @@ function generateOperationTypes(buffer, operations, config) {
547
571
  if (!config.emit) return;
548
572
  buffer.push("// Operation Types");
549
573
  for (const op of operations) {
550
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
574
+ const camelCaseOperationId = toCamelCase(op.operationId);
575
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
551
576
  const requestType = wrapTypeReference(op.requestType);
552
577
  const responseType = wrapTypeReference(op.responseType);
553
578
  const errorsType = buildOperationErrorsType(op.errors);
554
579
  buffer.push(
555
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
580
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
556
581
  );
557
582
  buffer.push(` "${op.method}",`);
558
- buffer.push(` typeof paths.${op.operationId},`);
583
+ buffer.push(
584
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
585
+ );
559
586
  buffer.push(` ${requestType},`);
560
587
  buffer.push(` ${responseType},`);
561
588
  buffer.push(` ${headerType},`);
@@ -599,26 +626,35 @@ var TYPE_KEYWORDS = /* @__PURE__ */ new Set([
599
626
  "bigint",
600
627
  "symbol"
601
628
  ]);
629
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
602
630
  function wrapTypeReference(typeName) {
603
631
  if (!typeName) return "undefined";
604
- if (typeName === "undefined") return "undefined";
605
- if (TYPE_KEYWORDS.has(typeName)) return typeName;
606
- if (typeName.startsWith("typeof ")) return typeName;
607
- const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
608
- if (identifierPattern.test(typeName)) {
609
- return `typeof ${typeName}`;
632
+ const normalized = typeName.trim();
633
+ if (normalized === "undefined") return "undefined";
634
+ if (TYPE_KEYWORDS.has(normalized)) return normalized;
635
+ if (normalized.startsWith("typeof ")) return normalized;
636
+ const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
637
+ if (arrayMatch) {
638
+ return `z.ZodArray<${wrapTypeReference(arrayMatch[1])}>`;
610
639
  }
611
- return typeName;
640
+ if (IDENTIFIER_PATTERN.test(normalized)) {
641
+ return `typeof ${normalized}`;
642
+ }
643
+ return normalized;
612
644
  }
613
645
  function wrapErrorValueType(typeName) {
614
646
  if (!typeName) return "unknown";
615
- if (TYPE_KEYWORDS.has(typeName)) return typeName;
616
- if (typeName.startsWith("typeof ")) return typeName;
617
- const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
618
- if (identifierPattern.test(typeName)) {
619
- return `typeof ${typeName}`;
647
+ const normalized = typeName.trim();
648
+ if (TYPE_KEYWORDS.has(normalized)) return normalized;
649
+ if (normalized.startsWith("typeof ")) return normalized;
650
+ const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
651
+ if (arrayMatch) {
652
+ return `z.ZodArray<${wrapErrorValueType(arrayMatch[1])}>`;
620
653
  }
621
- return typeName;
654
+ if (IDENTIFIER_PATTERN.test(normalized)) {
655
+ return `typeof ${normalized}`;
656
+ }
657
+ return normalized;
622
658
  }
623
659
  function collectParameters(pathItem, operation, spec) {
624
660
  const parametersMap = /* @__PURE__ */ new Map();
@@ -678,13 +714,39 @@ function getResponseTypes(operation, operationId) {
678
714
  const successCodes = /* @__PURE__ */ new Map();
679
715
  const errorEntries = [];
680
716
  for (const [statusCode, response] of Object.entries(responses)) {
681
- const resolvedSchema = response?.content?.["application/json"]?.schema;
682
- if (!resolvedSchema) continue;
717
+ const content = response?.content;
718
+ if (!content || Object.keys(content).length === 0) {
719
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
720
+ successCodes.set(statusCode, "undefined");
721
+ } else if (isErrorStatus(statusCode)) {
722
+ errorEntries.push({
723
+ code: statusCode,
724
+ schema: "undefined"
725
+ });
726
+ }
727
+ continue;
728
+ }
729
+ const contentType = findContentType(content);
730
+ const resolvedSchema = content[contentType]?.schema;
731
+ if (!resolvedSchema) {
732
+ const inferredType = inferResponseType(contentType, statusCode);
733
+ if (inferredType) {
734
+ if (isErrorStatus(statusCode)) {
735
+ errorEntries.push({
736
+ code: statusCode,
737
+ schema: inferredType
738
+ });
739
+ } else if (/^2\d\d$/.test(statusCode)) {
740
+ successCodes.set(statusCode, inferredType);
741
+ }
742
+ }
743
+ continue;
744
+ }
683
745
  if (isErrorStatus(statusCode)) {
684
746
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
685
747
  continue;
686
748
  }
687
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
749
+ if (/^2\d\d$/.test(statusCode)) {
688
750
  successCodes.set(statusCode, resolvedSchema);
689
751
  }
690
752
  }
@@ -698,6 +760,9 @@ function selectSuccessResponse(responses, operationId) {
698
760
  for (const code of preferredOrder) {
699
761
  const schema = responses.get(code);
700
762
  if (schema) {
763
+ if (typeof schema === "string") {
764
+ return schema;
765
+ }
701
766
  return resolveResponseType(
702
767
  schema,
703
768
  `${capitalize(operationId)}Response${code}`
@@ -706,6 +771,9 @@ function selectSuccessResponse(responses, operationId) {
706
771
  }
707
772
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
708
773
  if (!firstSchema) return void 0;
774
+ if (typeof firstSchema === "string") {
775
+ return firstSchema;
776
+ }
709
777
  return resolveResponseType(
710
778
  firstSchema,
711
779
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -743,9 +811,16 @@ function buildErrorGroups(errors = [], operationId) {
743
811
  return group;
744
812
  }
745
813
  function resolveResponseType(schema, fallbackName) {
814
+ if (typeof schema === "string") {
815
+ return schema;
816
+ }
746
817
  if (schema.$ref) {
747
818
  return extractRefName(schema.$ref);
748
819
  }
820
+ if (schema.type === "array" && schema.items?.$ref) {
821
+ const itemRef = extractRefName(schema.items.$ref);
822
+ return `z.array(${itemRef})`;
823
+ }
749
824
  return fallbackName;
750
825
  }
751
826
  function getRequestHeaders(parameters) {
@@ -776,16 +851,22 @@ function getQueryParams(parameters) {
776
851
  }
777
852
  return queryParams;
778
853
  }
779
- function mapHeaderType(header) {
780
- const schemaType = header.schema?.type;
854
+ function mapHeaderToZodType(header) {
855
+ const schema = header.schema ?? {};
856
+ const schemaType = schema.type;
781
857
  switch (schemaType) {
782
858
  case "integer":
783
859
  case "number":
784
- return "number";
860
+ return "z.coerce.number()";
785
861
  case "boolean":
786
- return "boolean";
862
+ return "z.coerce.boolean()";
863
+ case "array": {
864
+ const items = schema.items ?? { type: "string" };
865
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
866
+ return `z.array(${itemType})`;
867
+ }
787
868
  default:
788
- return "string";
869
+ return "z.string()";
789
870
  }
790
871
  }
791
872
  function mapQueryType(param) {
@@ -989,6 +1070,54 @@ function applyNumericBounds(schema, builder) {
989
1070
  }
990
1071
  return builder;
991
1072
  }
1073
+ function generateHelperFile() {
1074
+ const output = [];
1075
+ output.push("// Generated helper types for Zenko");
1076
+ output.push(
1077
+ "// This file provides type definitions for operation objects and path functions"
1078
+ );
1079
+ output.push("");
1080
+ output.push(
1081
+ "export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
1082
+ );
1083
+ output.push("");
1084
+ output.push(
1085
+ 'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
1086
+ );
1087
+ output.push("");
1088
+ output.push(
1089
+ "export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
1090
+ );
1091
+ output.push("");
1092
+ output.push(
1093
+ "export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
1094
+ );
1095
+ output.push("");
1096
+ output.push(
1097
+ "export type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
1098
+ );
1099
+ output.push(" clientErrors?: TClient");
1100
+ output.push(" serverErrors?: TServer");
1101
+ output.push(" defaultErrors?: TDefault");
1102
+ output.push(" otherErrors?: TOther");
1103
+ output.push("}");
1104
+ output.push("");
1105
+ output.push(
1106
+ "export type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
1107
+ );
1108
+ output.push(" method: TMethod");
1109
+ output.push(" path: TPath");
1110
+ output.push(" request?: TRequest");
1111
+ output.push(" response?: TResponse");
1112
+ output.push(" headers?: THeaders");
1113
+ output.push(" errors?: TErrors");
1114
+ output.push("}");
1115
+ output.push("");
1116
+ return output.join("\n");
1117
+ }
1118
+ function toCamelCase(str) {
1119
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1120
+ }
992
1121
  function capitalize(str) {
993
1122
  return str.charAt(0).toUpperCase() + str.slice(1);
994
1123
  }