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.mjs CHANGED
@@ -243,8 +243,11 @@ function generate(spec, options = {}) {
243
243
  const pathParamNames = op.pathParams.map((p) => p.name);
244
244
  const hasPathParams = pathParamNames.length > 0;
245
245
  const hasQueryParams = op.queryParams.length > 0;
246
+ const camelCaseOperationId = toCamelCase(op.operationId);
246
247
  if (!hasPathParams && !hasQueryParams) {
247
- output.push(` ${op.operationId}: () => "${op.path}",`);
248
+ output.push(
249
+ ` ${formatPropertyName(camelCaseOperationId)}: () => "${op.path}",`
250
+ );
248
251
  continue;
249
252
  }
250
253
  const allParamNames = [
@@ -267,11 +270,13 @@ function generate(spec, options = {}) {
267
270
  const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
268
271
  if (!hasQueryParams) {
269
272
  output.push(
270
- ` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
273
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
271
274
  );
272
275
  continue;
273
276
  }
274
- output.push(` ${op.operationId}: (${signature}) => {`);
277
+ output.push(
278
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => {`
279
+ );
275
280
  output.push(" const params = new URLSearchParams()");
276
281
  for (const param of op.queryParams) {
277
282
  const propertyKey = formatPropertyName(param.name);
@@ -316,82 +321,57 @@ function generate(spec, options = {}) {
316
321
  }
317
322
  output.push("} as const;");
318
323
  output.push("");
319
- output.push("// Header Functions");
320
- output.push("export const headers = {");
324
+ output.push("// Header Schemas");
325
+ output.push("export const headerSchemas = {");
321
326
  for (const op of operations) {
327
+ const camelCaseOperationId = toCamelCase(op.operationId);
322
328
  if (!op.requestHeaders || op.requestHeaders.length === 0) {
323
- output.push(` ${op.operationId}: () => ({}),`);
324
- continue;
325
- }
326
- const typeEntries = op.requestHeaders.map(
327
- (header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
328
- header
329
- )}`
330
- ).join(", ");
331
- const requiredHeaders = op.requestHeaders.filter(
332
- (header) => header.required
333
- );
334
- const optionalHeaders = op.requestHeaders.filter(
335
- (header) => !header.required
336
- );
337
- const hasRequired = requiredHeaders.length > 0;
338
- const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
339
- if (optionalHeaders.length === 0) {
340
- output.push(` ${op.operationId}: ${signature} => ({`);
341
- for (const header of requiredHeaders) {
342
- const propertyKey = formatPropertyName(header.name);
343
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
344
- output.push(` ${propertyKey}: ${accessor},`);
345
- }
346
- output.push(" }),");
329
+ output.push(
330
+ ` ${formatPropertyName(camelCaseOperationId)}: z.object({}),`
331
+ );
347
332
  continue;
348
333
  }
349
- if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
350
- const header = optionalHeaders[0];
351
- const propertyKey = formatPropertyName(header.name);
352
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
353
- output.push(` ${op.operationId}: ${signature} =>`);
334
+ const schemaFields = op.requestHeaders.map((header) => {
335
+ const zodType = mapHeaderToZodType(header);
336
+ const optional = header.required ? "" : ".optional()";
337
+ return ` ${formatPropertyName(header.name)}: ${zodType}${optional},`;
338
+ }).join("\n");
339
+ output.push(` ${formatPropertyName(camelCaseOperationId)}: z.object({`);
340
+ output.push(schemaFields);
341
+ output.push(" }),");
342
+ }
343
+ output.push("} as const;");
344
+ output.push("");
345
+ output.push("// Header Functions");
346
+ output.push("export const headers = {");
347
+ for (const op of operations) {
348
+ const camelCaseOperationId = toCamelCase(op.operationId);
349
+ if (!op.requestHeaders || op.requestHeaders.length === 0) {
354
350
  output.push(
355
- ` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
351
+ ` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
356
352
  );
357
353
  continue;
358
354
  }
359
- const valueTypes = Array.from(
360
- new Set(optionalHeaders.map((header) => mapHeaderType(header)))
361
- ).join(" | ");
362
- output.push(` ${op.operationId}: ${signature} => {`);
363
- if (hasRequired) {
364
- output.push(" const headers = {");
365
- for (const header of requiredHeaders) {
366
- const propertyKey = formatPropertyName(header.name);
367
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
368
- output.push(` ${propertyKey}: ${accessor},`);
369
- }
370
- output.push(" }");
371
- } else {
372
- output.push(` const headers: Record<string, ${valueTypes}> = {}`);
373
- }
374
- for (const header of optionalHeaders) {
375
- const propertyKey = formatPropertyName(header.name);
376
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
377
- const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
378
- output.push(` if (${accessor} !== undefined) {`);
379
- output.push(` ${assignment} = ${accessor}`);
380
- output.push(" }");
381
- }
382
- output.push(" return headers");
355
+ output.push(
356
+ ` ${formatPropertyName(camelCaseOperationId)}: (params: z.input<${isValidJSIdentifier(camelCaseOperationId) ? `typeof headerSchemas.${camelCaseOperationId}` : `(typeof headerSchemas)[${formatPropertyName(camelCaseOperationId)}]`}>) => {`
357
+ );
358
+ output.push(
359
+ ` return ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse(params)`
360
+ );
383
361
  output.push(" },");
384
362
  }
385
363
  output.push("} as const;");
386
364
  output.push("");
387
365
  output.push("// Operation Objects");
388
366
  for (const op of operations) {
389
- output.push(`export const ${op.operationId} = {`);
390
- output.push(` path: paths.${op.operationId},`);
367
+ const camelCaseOperationId = toCamelCase(op.operationId);
368
+ output.push(`export const ${camelCaseOperationId} = {`);
369
+ output.push(` method: "${op.method}",`);
370
+ output.push(` path: paths.${camelCaseOperationId},`);
391
371
  appendOperationField(output, "request", op.requestType);
392
372
  appendOperationField(output, "response", op.responseType);
393
373
  if (op.requestHeaders && op.requestHeaders.length > 0) {
394
- output.push(` headers: headers.${op.operationId},`);
374
+ output.push(` headers: headers.${camelCaseOperationId},`);
395
375
  }
396
376
  if (op.errors && hasAnyErrors(op.errors)) {
397
377
  output.push(" errors: {");
@@ -427,10 +407,57 @@ function hasAnyErrors(group) {
427
407
  group.otherErrors
428
408
  ].some((bucket) => bucket && Object.keys(bucket).length > 0);
429
409
  }
410
+ function isRequestMethod(method) {
411
+ switch (method) {
412
+ case "get":
413
+ case "put":
414
+ case "post":
415
+ case "delete":
416
+ case "options":
417
+ case "head":
418
+ case "patch":
419
+ case "trace":
420
+ return true;
421
+ default:
422
+ return false;
423
+ }
424
+ }
425
+ var CONTENT_TYPE_MAP = {
426
+ "application/json": "unknown",
427
+ // Will use schema when available
428
+ "text/csv": "string",
429
+ "text/plain": "string",
430
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
431
+ "application/octet-stream": "unknown",
432
+ "application/pdf": "unknown"
433
+ };
434
+ function findContentType(content) {
435
+ const contentTypes = Object.keys(content);
436
+ if (contentTypes.includes("application/json")) {
437
+ return "application/json";
438
+ }
439
+ for (const contentType of contentTypes) {
440
+ if (contentType in CONTENT_TYPE_MAP) {
441
+ return contentType;
442
+ }
443
+ }
444
+ return contentTypes[0] || "";
445
+ }
446
+ function inferResponseType(contentType, statusCode) {
447
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
448
+ return "undefined";
449
+ }
450
+ if (contentType in CONTENT_TYPE_MAP) {
451
+ return CONTENT_TYPE_MAP[contentType];
452
+ }
453
+ return "unknown";
454
+ }
430
455
  function parseOperations(spec) {
431
456
  const operations = [];
432
457
  for (const [path2, pathItem] of Object.entries(spec.paths)) {
433
458
  for (const [method, operation] of Object.entries(pathItem)) {
459
+ const normalizedMethod = method.toLowerCase();
460
+ if (!isRequestMethod(normalizedMethod)) continue;
434
461
  if (!operation.operationId) continue;
435
462
  const pathParams = extractPathParams(path2);
436
463
  const requestType = getRequestType(operation);
@@ -444,7 +471,7 @@ function parseOperations(spec) {
444
471
  operations.push({
445
472
  operationId: operation.operationId,
446
473
  path: path2,
447
- method: method.toLowerCase(),
474
+ method: normalizedMethod,
448
475
  pathParams,
449
476
  queryParams,
450
477
  requestType,
@@ -480,20 +507,24 @@ function appendHelperTypesImport(buffer, config) {
480
507
  buffer.push(
481
508
  "type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
482
509
  );
510
+ buffer.push(
511
+ 'type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";'
512
+ );
483
513
  buffer.push(
484
514
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
485
515
  );
486
516
  buffer.push(
487
517
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
488
518
  );
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>;");
519
+ buffer.push(" clientErrors?: TClient;");
520
+ buffer.push(" serverErrors?: TServer;");
521
+ buffer.push(" defaultErrors?: TDefault;");
522
+ buffer.push(" otherErrors?: TOther;");
493
523
  buffer.push("};");
494
524
  buffer.push(
495
- "type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
525
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
496
526
  );
527
+ buffer.push(" method: TMethod;");
497
528
  buffer.push(" path: TPath;");
498
529
  buffer.push(" request?: TRequest;");
499
530
  buffer.push(" response?: TResponse;");
@@ -507,14 +538,18 @@ function generateOperationTypes(buffer, operations, config) {
507
538
  if (!config.emit) return;
508
539
  buffer.push("// Operation Types");
509
540
  for (const op of operations) {
510
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
541
+ const camelCaseOperationId = toCamelCase(op.operationId);
542
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
511
543
  const requestType = wrapTypeReference(op.requestType);
512
544
  const responseType = wrapTypeReference(op.responseType);
513
545
  const errorsType = buildOperationErrorsType(op.errors);
514
546
  buffer.push(
515
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
547
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
548
+ );
549
+ buffer.push(` "${op.method}",`);
550
+ buffer.push(
551
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
516
552
  );
517
- buffer.push(` typeof paths.${op.operationId},`);
518
553
  buffer.push(` ${requestType},`);
519
554
  buffer.push(` ${responseType},`);
520
555
  buffer.push(` ${headerType},`);
@@ -637,13 +672,34 @@ function getResponseTypes(operation, operationId) {
637
672
  const successCodes = /* @__PURE__ */ new Map();
638
673
  const errorEntries = [];
639
674
  for (const [statusCode, response] of Object.entries(responses)) {
640
- const resolvedSchema = response?.content?.["application/json"]?.schema;
641
- if (!resolvedSchema) continue;
675
+ const content = response?.content;
676
+ if (!content || Object.keys(content).length === 0) {
677
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
678
+ successCodes.set(statusCode, "undefined");
679
+ }
680
+ continue;
681
+ }
682
+ const contentType = findContentType(content);
683
+ const resolvedSchema = content[contentType]?.schema;
684
+ if (!resolvedSchema) {
685
+ const inferredType = inferResponseType(contentType, statusCode);
686
+ if (inferredType) {
687
+ if (isErrorStatus(statusCode)) {
688
+ errorEntries.push({
689
+ code: statusCode,
690
+ schema: inferredType
691
+ });
692
+ } else if (/^2\d\d$/.test(statusCode)) {
693
+ successCodes.set(statusCode, inferredType);
694
+ }
695
+ }
696
+ continue;
697
+ }
642
698
  if (isErrorStatus(statusCode)) {
643
699
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
644
700
  continue;
645
701
  }
646
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
702
+ if (/^2\d\d$/.test(statusCode)) {
647
703
  successCodes.set(statusCode, resolvedSchema);
648
704
  }
649
705
  }
@@ -657,6 +713,9 @@ function selectSuccessResponse(responses, operationId) {
657
713
  for (const code of preferredOrder) {
658
714
  const schema = responses.get(code);
659
715
  if (schema) {
716
+ if (typeof schema === "string") {
717
+ return schema;
718
+ }
660
719
  return resolveResponseType(
661
720
  schema,
662
721
  `${capitalize(operationId)}Response${code}`
@@ -665,6 +724,9 @@ function selectSuccessResponse(responses, operationId) {
665
724
  }
666
725
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
667
726
  if (!firstSchema) return void 0;
727
+ if (typeof firstSchema === "string") {
728
+ return firstSchema;
729
+ }
668
730
  return resolveResponseType(
669
731
  firstSchema,
670
732
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -702,6 +764,9 @@ function buildErrorGroups(errors = [], operationId) {
702
764
  return group;
703
765
  }
704
766
  function resolveResponseType(schema, fallbackName) {
767
+ if (typeof schema === "string") {
768
+ return schema;
769
+ }
705
770
  if (schema.$ref) {
706
771
  return extractRefName(schema.$ref);
707
772
  }
@@ -735,16 +800,22 @@ function getQueryParams(parameters) {
735
800
  }
736
801
  return queryParams;
737
802
  }
738
- function mapHeaderType(header) {
739
- const schemaType = header.schema?.type;
803
+ function mapHeaderToZodType(header) {
804
+ const schema = header.schema ?? {};
805
+ const schemaType = schema.type;
740
806
  switch (schemaType) {
741
807
  case "integer":
742
808
  case "number":
743
- return "number";
809
+ return "z.coerce.number()";
744
810
  case "boolean":
745
- return "boolean";
811
+ return "z.coerce.boolean()";
812
+ case "array": {
813
+ const items = schema.items ?? { type: "string" };
814
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
815
+ return `z.array(${itemType})`;
816
+ }
746
817
  default:
747
- return "string";
818
+ return "z.string()";
748
819
  }
749
820
  }
750
821
  function mapQueryType(param) {
@@ -948,6 +1019,9 @@ function applyNumericBounds(schema, builder) {
948
1019
  }
949
1020
  return builder;
950
1021
  }
1022
+ function toCamelCase(str) {
1023
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1024
+ }
951
1025
  function capitalize(str) {
952
1026
  return str.charAt(0).toUpperCase() + str.slice(1);
953
1027
  }