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.mjs CHANGED
@@ -202,7 +202,7 @@ function isErrorStatus(status) {
202
202
  }
203
203
 
204
204
  // src/zenko.ts
205
- function generate(spec, options = {}) {
205
+ function generateWithMetadata(spec, options = {}) {
206
206
  const output = [];
207
207
  const generatedTypes = /* @__PURE__ */ new Set();
208
208
  const { strictDates = false, strictNumeric = false } = options;
@@ -235,8 +235,11 @@ function generate(spec, options = {}) {
235
235
  const pathParamNames = op.pathParams.map((p) => p.name);
236
236
  const hasPathParams = pathParamNames.length > 0;
237
237
  const hasQueryParams = op.queryParams.length > 0;
238
+ const camelCaseOperationId = toCamelCase(op.operationId);
238
239
  if (!hasPathParams && !hasQueryParams) {
239
- output.push(` ${op.operationId}: () => "${op.path}",`);
240
+ output.push(
241
+ ` ${formatPropertyName(camelCaseOperationId)}: () => "${op.path}",`
242
+ );
240
243
  continue;
241
244
  }
242
245
  const allParamNames = [
@@ -259,11 +262,13 @@ function generate(spec, options = {}) {
259
262
  const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
260
263
  if (!hasQueryParams) {
261
264
  output.push(
262
- ` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
265
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
263
266
  );
264
267
  continue;
265
268
  }
266
- output.push(` ${op.operationId}: (${signature}) => {`);
269
+ output.push(
270
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => {`
271
+ );
267
272
  output.push(" const params = new URLSearchParams()");
268
273
  for (const param of op.queryParams) {
269
274
  const propertyKey = formatPropertyName(param.name);
@@ -308,83 +313,57 @@ function generate(spec, options = {}) {
308
313
  }
309
314
  output.push("} as const;");
310
315
  output.push("");
311
- output.push("// Header Functions");
312
- output.push("export const headers = {");
316
+ output.push("// Header Schemas");
317
+ output.push("export const headerSchemas = {");
313
318
  for (const op of operations) {
319
+ const camelCaseOperationId = toCamelCase(op.operationId);
314
320
  if (!op.requestHeaders || op.requestHeaders.length === 0) {
315
- output.push(` ${op.operationId}: () => ({}),`);
316
- continue;
317
- }
318
- const typeEntries = op.requestHeaders.map(
319
- (header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
320
- header
321
- )}`
322
- ).join(", ");
323
- const requiredHeaders = op.requestHeaders.filter(
324
- (header) => header.required
325
- );
326
- const optionalHeaders = op.requestHeaders.filter(
327
- (header) => !header.required
328
- );
329
- const hasRequired = requiredHeaders.length > 0;
330
- const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
331
- if (optionalHeaders.length === 0) {
332
- output.push(` ${op.operationId}: ${signature} => ({`);
333
- for (const header of requiredHeaders) {
334
- const propertyKey = formatPropertyName(header.name);
335
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
336
- output.push(` ${propertyKey}: ${accessor},`);
337
- }
338
- output.push(" }),");
321
+ output.push(
322
+ ` ${formatPropertyName(camelCaseOperationId)}: z.object({}),`
323
+ );
339
324
  continue;
340
325
  }
341
- if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
342
- const header = optionalHeaders[0];
343
- const propertyKey = formatPropertyName(header.name);
344
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
345
- output.push(` ${op.operationId}: ${signature} =>`);
326
+ const schemaFields = op.requestHeaders.map((header) => {
327
+ const zodType = mapHeaderToZodType(header);
328
+ const optional = header.required ? "" : ".optional()";
329
+ return ` ${formatPropertyName(header.name)}: ${zodType}${optional},`;
330
+ }).join("\n");
331
+ output.push(` ${formatPropertyName(camelCaseOperationId)}: z.object({`);
332
+ output.push(schemaFields);
333
+ output.push(" }),");
334
+ }
335
+ output.push("} as const;");
336
+ output.push("");
337
+ output.push("// Header Functions");
338
+ output.push("export const headers = {");
339
+ for (const op of operations) {
340
+ const camelCaseOperationId = toCamelCase(op.operationId);
341
+ if (!op.requestHeaders || op.requestHeaders.length === 0) {
346
342
  output.push(
347
- ` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
343
+ ` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
348
344
  );
349
345
  continue;
350
346
  }
351
- const valueTypes = Array.from(
352
- new Set(optionalHeaders.map((header) => mapHeaderType(header)))
353
- ).join(" | ");
354
- output.push(` ${op.operationId}: ${signature} => {`);
355
- if (hasRequired) {
356
- output.push(" const headers = {");
357
- for (const header of requiredHeaders) {
358
- const propertyKey = formatPropertyName(header.name);
359
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
360
- output.push(` ${propertyKey}: ${accessor},`);
361
- }
362
- output.push(" }");
363
- } else {
364
- output.push(` const headers: Record<string, ${valueTypes}> = {}`);
365
- }
366
- for (const header of optionalHeaders) {
367
- const propertyKey = formatPropertyName(header.name);
368
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
369
- const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
370
- output.push(` if (${accessor} !== undefined) {`);
371
- output.push(` ${assignment} = ${accessor}`);
372
- output.push(" }");
373
- }
374
- output.push(" return headers");
347
+ output.push(
348
+ ` ${formatPropertyName(camelCaseOperationId)}: (params: z.input<${isValidJSIdentifier(camelCaseOperationId) ? `typeof headerSchemas.${camelCaseOperationId}` : `(typeof headerSchemas)[${formatPropertyName(camelCaseOperationId)}]`}>) => {`
349
+ );
350
+ output.push(
351
+ ` return ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse(params)`
352
+ );
375
353
  output.push(" },");
376
354
  }
377
355
  output.push("} as const;");
378
356
  output.push("");
379
357
  output.push("// Operation Objects");
380
358
  for (const op of operations) {
381
- output.push(`export const ${op.operationId} = {`);
359
+ const camelCaseOperationId = toCamelCase(op.operationId);
360
+ output.push(`export const ${camelCaseOperationId} = {`);
382
361
  output.push(` method: "${op.method}",`);
383
- output.push(` path: paths.${op.operationId},`);
362
+ output.push(` path: paths.${camelCaseOperationId},`);
384
363
  appendOperationField(output, "request", op.requestType);
385
364
  appendOperationField(output, "response", op.responseType);
386
365
  if (op.requestHeaders && op.requestHeaders.length > 0) {
387
- output.push(` headers: headers.${op.operationId},`);
366
+ output.push(` headers: headers.${camelCaseOperationId},`);
388
367
  }
389
368
  if (op.errors && hasAnyErrors(op.errors)) {
390
369
  output.push(" errors: {");
@@ -398,7 +377,19 @@ function generate(spec, options = {}) {
398
377
  output.push("");
399
378
  }
400
379
  generateOperationTypes(output, operations, typesConfig);
401
- return output.join("\n");
380
+ const result = {
381
+ output: output.join("\n")
382
+ };
383
+ if (typesConfig.emit && typesConfig.helpers === "file" && typesConfig.helpersOutput) {
384
+ result.helperFile = {
385
+ path: typesConfig.helpersOutput,
386
+ content: generateHelperFile()
387
+ };
388
+ }
389
+ return result;
390
+ }
391
+ function generate(spec, options = {}) {
392
+ return generateWithMetadata(spec, options).output;
402
393
  }
403
394
  function appendOperationField(buffer, key, value) {
404
395
  if (!value) return;
@@ -435,6 +426,36 @@ function isRequestMethod(method) {
435
426
  return false;
436
427
  }
437
428
  }
429
+ var CONTENT_TYPE_MAP = {
430
+ "application/json": "unknown",
431
+ // Will use schema when available
432
+ "text/csv": "string",
433
+ "text/plain": "string",
434
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
435
+ "application/octet-stream": "unknown",
436
+ "application/pdf": "unknown"
437
+ };
438
+ function findContentType(content) {
439
+ const contentTypes = Object.keys(content);
440
+ if (contentTypes.includes("application/json")) {
441
+ return "application/json";
442
+ }
443
+ for (const contentType of contentTypes) {
444
+ if (contentType in CONTENT_TYPE_MAP) {
445
+ return contentType;
446
+ }
447
+ }
448
+ return contentTypes[0] || "";
449
+ }
450
+ function inferResponseType(contentType, statusCode) {
451
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
452
+ return "undefined";
453
+ }
454
+ if (contentType in CONTENT_TYPE_MAP) {
455
+ return CONTENT_TYPE_MAP[contentType];
456
+ }
457
+ return "unknown";
458
+ }
438
459
  function parseOperations(spec) {
439
460
  const operations = [];
440
461
  for (const [path, pathItem] of Object.entries(spec.paths)) {
@@ -496,6 +517,9 @@ function appendHelperTypesImport(buffer, config) {
496
517
  buffer.push(
497
518
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
498
519
  );
520
+ buffer.push(
521
+ "type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown);"
522
+ );
499
523
  buffer.push(
500
524
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
501
525
  );
@@ -505,7 +529,7 @@ function appendHelperTypesImport(buffer, config) {
505
529
  buffer.push(" otherErrors?: TOther;");
506
530
  buffer.push("};");
507
531
  buffer.push(
508
- "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
532
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
509
533
  );
510
534
  buffer.push(" method: TMethod;");
511
535
  buffer.push(" path: TPath;");
@@ -521,15 +545,18 @@ function generateOperationTypes(buffer, operations, config) {
521
545
  if (!config.emit) return;
522
546
  buffer.push("// Operation Types");
523
547
  for (const op of operations) {
524
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
548
+ const camelCaseOperationId = toCamelCase(op.operationId);
549
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
525
550
  const requestType = wrapTypeReference(op.requestType);
526
551
  const responseType = wrapTypeReference(op.responseType);
527
552
  const errorsType = buildOperationErrorsType(op.errors);
528
553
  buffer.push(
529
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
554
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
530
555
  );
531
556
  buffer.push(` "${op.method}",`);
532
- buffer.push(` typeof paths.${op.operationId},`);
557
+ buffer.push(
558
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
559
+ );
533
560
  buffer.push(` ${requestType},`);
534
561
  buffer.push(` ${responseType},`);
535
562
  buffer.push(` ${headerType},`);
@@ -573,26 +600,35 @@ var TYPE_KEYWORDS = /* @__PURE__ */ new Set([
573
600
  "bigint",
574
601
  "symbol"
575
602
  ]);
603
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
576
604
  function wrapTypeReference(typeName) {
577
605
  if (!typeName) return "undefined";
578
- if (typeName === "undefined") return "undefined";
579
- if (TYPE_KEYWORDS.has(typeName)) return typeName;
580
- if (typeName.startsWith("typeof ")) return typeName;
581
- const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
582
- if (identifierPattern.test(typeName)) {
583
- return `typeof ${typeName}`;
606
+ const normalized = typeName.trim();
607
+ if (normalized === "undefined") return "undefined";
608
+ if (TYPE_KEYWORDS.has(normalized)) return normalized;
609
+ if (normalized.startsWith("typeof ")) return normalized;
610
+ const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
611
+ if (arrayMatch) {
612
+ return `z.ZodArray<${wrapTypeReference(arrayMatch[1])}>`;
584
613
  }
585
- return typeName;
614
+ if (IDENTIFIER_PATTERN.test(normalized)) {
615
+ return `typeof ${normalized}`;
616
+ }
617
+ return normalized;
586
618
  }
587
619
  function wrapErrorValueType(typeName) {
588
620
  if (!typeName) return "unknown";
589
- if (TYPE_KEYWORDS.has(typeName)) return typeName;
590
- if (typeName.startsWith("typeof ")) return typeName;
591
- const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
592
- if (identifierPattern.test(typeName)) {
593
- return `typeof ${typeName}`;
621
+ const normalized = typeName.trim();
622
+ if (TYPE_KEYWORDS.has(normalized)) return normalized;
623
+ if (normalized.startsWith("typeof ")) return normalized;
624
+ const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
625
+ if (arrayMatch) {
626
+ return `z.ZodArray<${wrapErrorValueType(arrayMatch[1])}>`;
594
627
  }
595
- return typeName;
628
+ if (IDENTIFIER_PATTERN.test(normalized)) {
629
+ return `typeof ${normalized}`;
630
+ }
631
+ return normalized;
596
632
  }
597
633
  function collectParameters(pathItem, operation, spec) {
598
634
  const parametersMap = /* @__PURE__ */ new Map();
@@ -652,13 +688,39 @@ function getResponseTypes(operation, operationId) {
652
688
  const successCodes = /* @__PURE__ */ new Map();
653
689
  const errorEntries = [];
654
690
  for (const [statusCode, response] of Object.entries(responses)) {
655
- const resolvedSchema = response?.content?.["application/json"]?.schema;
656
- if (!resolvedSchema) continue;
691
+ const content = response?.content;
692
+ if (!content || Object.keys(content).length === 0) {
693
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
694
+ successCodes.set(statusCode, "undefined");
695
+ } else if (isErrorStatus(statusCode)) {
696
+ errorEntries.push({
697
+ code: statusCode,
698
+ schema: "undefined"
699
+ });
700
+ }
701
+ continue;
702
+ }
703
+ const contentType = findContentType(content);
704
+ const resolvedSchema = content[contentType]?.schema;
705
+ if (!resolvedSchema) {
706
+ const inferredType = inferResponseType(contentType, statusCode);
707
+ if (inferredType) {
708
+ if (isErrorStatus(statusCode)) {
709
+ errorEntries.push({
710
+ code: statusCode,
711
+ schema: inferredType
712
+ });
713
+ } else if (/^2\d\d$/.test(statusCode)) {
714
+ successCodes.set(statusCode, inferredType);
715
+ }
716
+ }
717
+ continue;
718
+ }
657
719
  if (isErrorStatus(statusCode)) {
658
720
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
659
721
  continue;
660
722
  }
661
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
723
+ if (/^2\d\d$/.test(statusCode)) {
662
724
  successCodes.set(statusCode, resolvedSchema);
663
725
  }
664
726
  }
@@ -672,6 +734,9 @@ function selectSuccessResponse(responses, operationId) {
672
734
  for (const code of preferredOrder) {
673
735
  const schema = responses.get(code);
674
736
  if (schema) {
737
+ if (typeof schema === "string") {
738
+ return schema;
739
+ }
675
740
  return resolveResponseType(
676
741
  schema,
677
742
  `${capitalize(operationId)}Response${code}`
@@ -680,6 +745,9 @@ function selectSuccessResponse(responses, operationId) {
680
745
  }
681
746
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
682
747
  if (!firstSchema) return void 0;
748
+ if (typeof firstSchema === "string") {
749
+ return firstSchema;
750
+ }
683
751
  return resolveResponseType(
684
752
  firstSchema,
685
753
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -717,9 +785,16 @@ function buildErrorGroups(errors = [], operationId) {
717
785
  return group;
718
786
  }
719
787
  function resolveResponseType(schema, fallbackName) {
788
+ if (typeof schema === "string") {
789
+ return schema;
790
+ }
720
791
  if (schema.$ref) {
721
792
  return extractRefName(schema.$ref);
722
793
  }
794
+ if (schema.type === "array" && schema.items?.$ref) {
795
+ const itemRef = extractRefName(schema.items.$ref);
796
+ return `z.array(${itemRef})`;
797
+ }
723
798
  return fallbackName;
724
799
  }
725
800
  function getRequestHeaders(parameters) {
@@ -750,16 +825,22 @@ function getQueryParams(parameters) {
750
825
  }
751
826
  return queryParams;
752
827
  }
753
- function mapHeaderType(header) {
754
- const schemaType = header.schema?.type;
828
+ function mapHeaderToZodType(header) {
829
+ const schema = header.schema ?? {};
830
+ const schemaType = schema.type;
755
831
  switch (schemaType) {
756
832
  case "integer":
757
833
  case "number":
758
- return "number";
834
+ return "z.coerce.number()";
759
835
  case "boolean":
760
- return "boolean";
836
+ return "z.coerce.boolean()";
837
+ case "array": {
838
+ const items = schema.items ?? { type: "string" };
839
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
840
+ return `z.array(${itemType})`;
841
+ }
761
842
  default:
762
- return "string";
843
+ return "z.string()";
763
844
  }
764
845
  }
765
846
  function mapQueryType(param) {
@@ -963,6 +1044,54 @@ function applyNumericBounds(schema, builder) {
963
1044
  }
964
1045
  return builder;
965
1046
  }
1047
+ function generateHelperFile() {
1048
+ const output = [];
1049
+ output.push("// Generated helper types for Zenko");
1050
+ output.push(
1051
+ "// This file provides type definitions for operation objects and path functions"
1052
+ );
1053
+ output.push("");
1054
+ output.push(
1055
+ "export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
1056
+ );
1057
+ output.push("");
1058
+ output.push(
1059
+ 'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
1060
+ );
1061
+ output.push("");
1062
+ output.push(
1063
+ "export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
1064
+ );
1065
+ output.push("");
1066
+ output.push(
1067
+ "export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
1068
+ );
1069
+ output.push("");
1070
+ output.push(
1071
+ "export type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
1072
+ );
1073
+ output.push(" clientErrors?: TClient");
1074
+ output.push(" serverErrors?: TServer");
1075
+ output.push(" defaultErrors?: TDefault");
1076
+ output.push(" otherErrors?: TOther");
1077
+ output.push("}");
1078
+ output.push("");
1079
+ output.push(
1080
+ "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> = {"
1081
+ );
1082
+ output.push(" method: TMethod");
1083
+ output.push(" path: TPath");
1084
+ output.push(" request?: TRequest");
1085
+ output.push(" response?: TResponse");
1086
+ output.push(" headers?: THeaders");
1087
+ output.push(" errors?: TErrors");
1088
+ output.push("}");
1089
+ output.push("");
1090
+ return output.join("\n");
1091
+ }
1092
+ function toCamelCase(str) {
1093
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1094
+ }
966
1095
  function capitalize(str) {
967
1096
  return str.charAt(0).toUpperCase() + str.slice(1);
968
1097
  }