zenko 0.1.3 → 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,83 +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} = {`);
390
+ const camelCaseOperationId = toCamelCase(op.operationId);
391
+ output.push(`export const ${camelCaseOperationId} = {`);
413
392
  output.push(` method: "${op.method}",`);
414
- output.push(` path: paths.${op.operationId},`);
393
+ output.push(` path: paths.${camelCaseOperationId},`);
415
394
  appendOperationField(output, "request", op.requestType);
416
395
  appendOperationField(output, "response", op.responseType);
417
396
  if (op.requestHeaders && op.requestHeaders.length > 0) {
418
- output.push(` headers: headers.${op.operationId},`);
397
+ output.push(` headers: headers.${camelCaseOperationId},`);
419
398
  }
420
399
  if (op.errors && hasAnyErrors(op.errors)) {
421
400
  output.push(" errors: {");
@@ -466,6 +445,36 @@ function isRequestMethod(method) {
466
445
  return false;
467
446
  }
468
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
+ }
469
478
  function parseOperations(spec) {
470
479
  const operations = [];
471
480
  for (const [path2, pathItem] of Object.entries(spec.paths)) {
@@ -552,15 +561,18 @@ function generateOperationTypes(buffer, operations, config) {
552
561
  if (!config.emit) return;
553
562
  buffer.push("// Operation Types");
554
563
  for (const op of operations) {
555
- 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";
556
566
  const requestType = wrapTypeReference(op.requestType);
557
567
  const responseType = wrapTypeReference(op.responseType);
558
568
  const errorsType = buildOperationErrorsType(op.errors);
559
569
  buffer.push(
560
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
570
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
561
571
  );
562
572
  buffer.push(` "${op.method}",`);
563
- buffer.push(` typeof paths.${op.operationId},`);
573
+ buffer.push(
574
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
575
+ );
564
576
  buffer.push(` ${requestType},`);
565
577
  buffer.push(` ${responseType},`);
566
578
  buffer.push(` ${headerType},`);
@@ -683,13 +695,34 @@ function getResponseTypes(operation, operationId) {
683
695
  const successCodes = /* @__PURE__ */ new Map();
684
696
  const errorEntries = [];
685
697
  for (const [statusCode, response] of Object.entries(responses)) {
686
- const resolvedSchema = response?.content?.["application/json"]?.schema;
687
- 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
+ }
688
721
  if (isErrorStatus(statusCode)) {
689
722
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
690
723
  continue;
691
724
  }
692
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
725
+ if (/^2\d\d$/.test(statusCode)) {
693
726
  successCodes.set(statusCode, resolvedSchema);
694
727
  }
695
728
  }
@@ -703,6 +736,9 @@ function selectSuccessResponse(responses, operationId) {
703
736
  for (const code of preferredOrder) {
704
737
  const schema = responses.get(code);
705
738
  if (schema) {
739
+ if (typeof schema === "string") {
740
+ return schema;
741
+ }
706
742
  return resolveResponseType(
707
743
  schema,
708
744
  `${capitalize(operationId)}Response${code}`
@@ -711,6 +747,9 @@ function selectSuccessResponse(responses, operationId) {
711
747
  }
712
748
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
713
749
  if (!firstSchema) return void 0;
750
+ if (typeof firstSchema === "string") {
751
+ return firstSchema;
752
+ }
714
753
  return resolveResponseType(
715
754
  firstSchema,
716
755
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -748,6 +787,9 @@ function buildErrorGroups(errors = [], operationId) {
748
787
  return group;
749
788
  }
750
789
  function resolveResponseType(schema, fallbackName) {
790
+ if (typeof schema === "string") {
791
+ return schema;
792
+ }
751
793
  if (schema.$ref) {
752
794
  return extractRefName(schema.$ref);
753
795
  }
@@ -781,16 +823,22 @@ function getQueryParams(parameters) {
781
823
  }
782
824
  return queryParams;
783
825
  }
784
- function mapHeaderType(header) {
785
- const schemaType = header.schema?.type;
826
+ function mapHeaderToZodType(header) {
827
+ const schema = header.schema ?? {};
828
+ const schemaType = schema.type;
786
829
  switch (schemaType) {
787
830
  case "integer":
788
831
  case "number":
789
- return "number";
832
+ return "z.coerce.number()";
790
833
  case "boolean":
791
- 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
+ }
792
840
  default:
793
- return "string";
841
+ return "z.string()";
794
842
  }
795
843
  }
796
844
  function mapQueryType(param) {
@@ -994,6 +1042,9 @@ function applyNumericBounds(schema, builder) {
994
1042
  }
995
1043
  return builder;
996
1044
  }
1045
+ function toCamelCase(str) {
1046
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1047
+ }
997
1048
  function capitalize(str) {
998
1049
  return str.charAt(0).toUpperCase() + str.slice(1);
999
1050
  }