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 CHANGED
@@ -266,8 +266,11 @@ function generate(spec, options = {}) {
266
266
  const pathParamNames = op.pathParams.map((p) => p.name);
267
267
  const hasPathParams = pathParamNames.length > 0;
268
268
  const hasQueryParams = op.queryParams.length > 0;
269
+ const camelCaseOperationId = toCamelCase(op.operationId);
269
270
  if (!hasPathParams && !hasQueryParams) {
270
- output.push(` ${op.operationId}: () => "${op.path}",`);
271
+ output.push(
272
+ ` ${formatPropertyName(camelCaseOperationId)}: () => "${op.path}",`
273
+ );
271
274
  continue;
272
275
  }
273
276
  const allParamNames = [
@@ -290,11 +293,13 @@ function generate(spec, options = {}) {
290
293
  const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
291
294
  if (!hasQueryParams) {
292
295
  output.push(
293
- ` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
296
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
294
297
  );
295
298
  continue;
296
299
  }
297
- output.push(` ${op.operationId}: (${signature}) => {`);
300
+ output.push(
301
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => {`
302
+ );
298
303
  output.push(" const params = new URLSearchParams()");
299
304
  for (const param of op.queryParams) {
300
305
  const propertyKey = formatPropertyName(param.name);
@@ -339,82 +344,57 @@ function generate(spec, options = {}) {
339
344
  }
340
345
  output.push("} as const;");
341
346
  output.push("");
342
- output.push("// Header Functions");
343
- output.push("export const headers = {");
347
+ output.push("// Header Schemas");
348
+ output.push("export const headerSchemas = {");
344
349
  for (const op of operations) {
350
+ const camelCaseOperationId = toCamelCase(op.operationId);
345
351
  if (!op.requestHeaders || op.requestHeaders.length === 0) {
346
- output.push(` ${op.operationId}: () => ({}),`);
347
- continue;
348
- }
349
- const typeEntries = op.requestHeaders.map(
350
- (header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
351
- header
352
- )}`
353
- ).join(", ");
354
- const requiredHeaders = op.requestHeaders.filter(
355
- (header) => header.required
356
- );
357
- const optionalHeaders = op.requestHeaders.filter(
358
- (header) => !header.required
359
- );
360
- const hasRequired = requiredHeaders.length > 0;
361
- const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
362
- if (optionalHeaders.length === 0) {
363
- output.push(` ${op.operationId}: ${signature} => ({`);
364
- for (const header of requiredHeaders) {
365
- const propertyKey = formatPropertyName(header.name);
366
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
367
- output.push(` ${propertyKey}: ${accessor},`);
368
- }
369
- output.push(" }),");
352
+ output.push(
353
+ ` ${formatPropertyName(camelCaseOperationId)}: z.object({}),`
354
+ );
370
355
  continue;
371
356
  }
372
- if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
373
- const header = optionalHeaders[0];
374
- const propertyKey = formatPropertyName(header.name);
375
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
376
- output.push(` ${op.operationId}: ${signature} =>`);
357
+ const schemaFields = op.requestHeaders.map((header) => {
358
+ const zodType = mapHeaderToZodType(header);
359
+ const optional = header.required ? "" : ".optional()";
360
+ return ` ${formatPropertyName(header.name)}: ${zodType}${optional},`;
361
+ }).join("\n");
362
+ output.push(` ${formatPropertyName(camelCaseOperationId)}: z.object({`);
363
+ output.push(schemaFields);
364
+ output.push(" }),");
365
+ }
366
+ output.push("} as const;");
367
+ output.push("");
368
+ output.push("// Header Functions");
369
+ output.push("export const headers = {");
370
+ for (const op of operations) {
371
+ const camelCaseOperationId = toCamelCase(op.operationId);
372
+ if (!op.requestHeaders || op.requestHeaders.length === 0) {
377
373
  output.push(
378
- ` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
374
+ ` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
379
375
  );
380
376
  continue;
381
377
  }
382
- const valueTypes = Array.from(
383
- new Set(optionalHeaders.map((header) => mapHeaderType(header)))
384
- ).join(" | ");
385
- output.push(` ${op.operationId}: ${signature} => {`);
386
- if (hasRequired) {
387
- output.push(" const headers = {");
388
- for (const header of requiredHeaders) {
389
- const propertyKey = formatPropertyName(header.name);
390
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
391
- output.push(` ${propertyKey}: ${accessor},`);
392
- }
393
- output.push(" }");
394
- } else {
395
- output.push(` const headers: Record<string, ${valueTypes}> = {}`);
396
- }
397
- for (const header of optionalHeaders) {
398
- const propertyKey = formatPropertyName(header.name);
399
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
400
- const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
401
- output.push(` if (${accessor} !== undefined) {`);
402
- output.push(` ${assignment} = ${accessor}`);
403
- output.push(" }");
404
- }
405
- output.push(" return headers");
378
+ output.push(
379
+ ` ${formatPropertyName(camelCaseOperationId)}: (params: z.input<${isValidJSIdentifier(camelCaseOperationId) ? `typeof headerSchemas.${camelCaseOperationId}` : `(typeof headerSchemas)[${formatPropertyName(camelCaseOperationId)}]`}>) => {`
380
+ );
381
+ output.push(
382
+ ` return ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse(params)`
383
+ );
406
384
  output.push(" },");
407
385
  }
408
386
  output.push("} as const;");
409
387
  output.push("");
410
388
  output.push("// Operation Objects");
411
389
  for (const op of operations) {
412
- output.push(`export const ${op.operationId} = {`);
413
- output.push(` path: paths.${op.operationId},`);
390
+ const camelCaseOperationId = toCamelCase(op.operationId);
391
+ output.push(`export const ${camelCaseOperationId} = {`);
392
+ output.push(` method: "${op.method}",`);
393
+ output.push(` path: paths.${camelCaseOperationId},`);
414
394
  appendOperationField(output, "request", op.requestType);
415
395
  appendOperationField(output, "response", op.responseType);
416
396
  if (op.requestHeaders && op.requestHeaders.length > 0) {
417
- output.push(` headers: headers.${op.operationId},`);
397
+ output.push(` headers: headers.${camelCaseOperationId},`);
418
398
  }
419
399
  if (op.errors && hasAnyErrors(op.errors)) {
420
400
  output.push(" errors: {");
@@ -450,10 +430,57 @@ function hasAnyErrors(group) {
450
430
  group.otherErrors
451
431
  ].some((bucket) => bucket && Object.keys(bucket).length > 0);
452
432
  }
433
+ function isRequestMethod(method) {
434
+ switch (method) {
435
+ case "get":
436
+ case "put":
437
+ case "post":
438
+ case "delete":
439
+ case "options":
440
+ case "head":
441
+ case "patch":
442
+ case "trace":
443
+ return true;
444
+ default:
445
+ return false;
446
+ }
447
+ }
448
+ var CONTENT_TYPE_MAP = {
449
+ "application/json": "unknown",
450
+ // Will use schema when available
451
+ "text/csv": "string",
452
+ "text/plain": "string",
453
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
454
+ "application/octet-stream": "unknown",
455
+ "application/pdf": "unknown"
456
+ };
457
+ function findContentType(content) {
458
+ const contentTypes = Object.keys(content);
459
+ if (contentTypes.includes("application/json")) {
460
+ return "application/json";
461
+ }
462
+ for (const contentType of contentTypes) {
463
+ if (contentType in CONTENT_TYPE_MAP) {
464
+ return contentType;
465
+ }
466
+ }
467
+ return contentTypes[0] || "";
468
+ }
469
+ function inferResponseType(contentType, statusCode) {
470
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
471
+ return "undefined";
472
+ }
473
+ if (contentType in CONTENT_TYPE_MAP) {
474
+ return CONTENT_TYPE_MAP[contentType];
475
+ }
476
+ return "unknown";
477
+ }
453
478
  function parseOperations(spec) {
454
479
  const operations = [];
455
480
  for (const [path2, pathItem] of Object.entries(spec.paths)) {
456
481
  for (const [method, operation] of Object.entries(pathItem)) {
482
+ const normalizedMethod = method.toLowerCase();
483
+ if (!isRequestMethod(normalizedMethod)) continue;
457
484
  if (!operation.operationId) continue;
458
485
  const pathParams = extractPathParams(path2);
459
486
  const requestType = getRequestType(operation);
@@ -467,7 +494,7 @@ function parseOperations(spec) {
467
494
  operations.push({
468
495
  operationId: operation.operationId,
469
496
  path: path2,
470
- method: method.toLowerCase(),
497
+ method: normalizedMethod,
471
498
  pathParams,
472
499
  queryParams,
473
500
  requestType,
@@ -503,20 +530,24 @@ function appendHelperTypesImport(buffer, config) {
503
530
  buffer.push(
504
531
  "type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
505
532
  );
533
+ buffer.push(
534
+ 'type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";'
535
+ );
506
536
  buffer.push(
507
537
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
508
538
  );
509
539
  buffer.push(
510
540
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
511
541
  );
512
- buffer.push(" clientErrors?: Record<string, TClient>;");
513
- buffer.push(" serverErrors?: Record<string, TServer>;");
514
- buffer.push(" defaultErrors?: Record<string, TDefault>;");
515
- buffer.push(" otherErrors?: Record<string, TOther>;");
542
+ buffer.push(" clientErrors?: TClient;");
543
+ buffer.push(" serverErrors?: TServer;");
544
+ buffer.push(" defaultErrors?: TDefault;");
545
+ buffer.push(" otherErrors?: TOther;");
516
546
  buffer.push("};");
517
547
  buffer.push(
518
- "type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
548
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
519
549
  );
550
+ buffer.push(" method: TMethod;");
520
551
  buffer.push(" path: TPath;");
521
552
  buffer.push(" request?: TRequest;");
522
553
  buffer.push(" response?: TResponse;");
@@ -530,14 +561,18 @@ function generateOperationTypes(buffer, operations, config) {
530
561
  if (!config.emit) return;
531
562
  buffer.push("// Operation Types");
532
563
  for (const op of operations) {
533
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
564
+ const camelCaseOperationId = toCamelCase(op.operationId);
565
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
534
566
  const requestType = wrapTypeReference(op.requestType);
535
567
  const responseType = wrapTypeReference(op.responseType);
536
568
  const errorsType = buildOperationErrorsType(op.errors);
537
569
  buffer.push(
538
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
570
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
571
+ );
572
+ buffer.push(` "${op.method}",`);
573
+ buffer.push(
574
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
539
575
  );
540
- buffer.push(` typeof paths.${op.operationId},`);
541
576
  buffer.push(` ${requestType},`);
542
577
  buffer.push(` ${responseType},`);
543
578
  buffer.push(` ${headerType},`);
@@ -660,13 +695,34 @@ function getResponseTypes(operation, operationId) {
660
695
  const successCodes = /* @__PURE__ */ new Map();
661
696
  const errorEntries = [];
662
697
  for (const [statusCode, response] of Object.entries(responses)) {
663
- const resolvedSchema = response?.content?.["application/json"]?.schema;
664
- if (!resolvedSchema) continue;
698
+ const content = response?.content;
699
+ if (!content || Object.keys(content).length === 0) {
700
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
701
+ successCodes.set(statusCode, "undefined");
702
+ }
703
+ continue;
704
+ }
705
+ const contentType = findContentType(content);
706
+ const resolvedSchema = content[contentType]?.schema;
707
+ if (!resolvedSchema) {
708
+ const inferredType = inferResponseType(contentType, statusCode);
709
+ if (inferredType) {
710
+ if (isErrorStatus(statusCode)) {
711
+ errorEntries.push({
712
+ code: statusCode,
713
+ schema: inferredType
714
+ });
715
+ } else if (/^2\d\d$/.test(statusCode)) {
716
+ successCodes.set(statusCode, inferredType);
717
+ }
718
+ }
719
+ continue;
720
+ }
665
721
  if (isErrorStatus(statusCode)) {
666
722
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
667
723
  continue;
668
724
  }
669
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
725
+ if (/^2\d\d$/.test(statusCode)) {
670
726
  successCodes.set(statusCode, resolvedSchema);
671
727
  }
672
728
  }
@@ -680,6 +736,9 @@ function selectSuccessResponse(responses, operationId) {
680
736
  for (const code of preferredOrder) {
681
737
  const schema = responses.get(code);
682
738
  if (schema) {
739
+ if (typeof schema === "string") {
740
+ return schema;
741
+ }
683
742
  return resolveResponseType(
684
743
  schema,
685
744
  `${capitalize(operationId)}Response${code}`
@@ -688,6 +747,9 @@ function selectSuccessResponse(responses, operationId) {
688
747
  }
689
748
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
690
749
  if (!firstSchema) return void 0;
750
+ if (typeof firstSchema === "string") {
751
+ return firstSchema;
752
+ }
691
753
  return resolveResponseType(
692
754
  firstSchema,
693
755
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -725,6 +787,9 @@ function buildErrorGroups(errors = [], operationId) {
725
787
  return group;
726
788
  }
727
789
  function resolveResponseType(schema, fallbackName) {
790
+ if (typeof schema === "string") {
791
+ return schema;
792
+ }
728
793
  if (schema.$ref) {
729
794
  return extractRefName(schema.$ref);
730
795
  }
@@ -758,16 +823,22 @@ function getQueryParams(parameters) {
758
823
  }
759
824
  return queryParams;
760
825
  }
761
- function mapHeaderType(header) {
762
- const schemaType = header.schema?.type;
826
+ function mapHeaderToZodType(header) {
827
+ const schema = header.schema ?? {};
828
+ const schemaType = schema.type;
763
829
  switch (schemaType) {
764
830
  case "integer":
765
831
  case "number":
766
- return "number";
832
+ return "z.coerce.number()";
767
833
  case "boolean":
768
- return "boolean";
834
+ return "z.coerce.boolean()";
835
+ case "array": {
836
+ const items = schema.items ?? { type: "string" };
837
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
838
+ return `z.array(${itemType})`;
839
+ }
769
840
  default:
770
- return "string";
841
+ return "z.string()";
771
842
  }
772
843
  }
773
844
  function mapQueryType(param) {
@@ -971,6 +1042,9 @@ function applyNumericBounds(schema, builder) {
971
1042
  }
972
1043
  return builder;
973
1044
  }
1045
+ function toCamelCase(str) {
1046
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1047
+ }
974
1048
  function capitalize(str) {
975
1049
  return str.charAt(0).toUpperCase() + str.slice(1);
976
1050
  }