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/index.cjs CHANGED
@@ -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,82 +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} = {`);
408
- output.push(` path: paths.${op.operationId},`);
385
+ const camelCaseOperationId = toCamelCase(op.operationId);
386
+ output.push(`export const ${camelCaseOperationId} = {`);
387
+ output.push(` method: "${op.method}",`);
388
+ output.push(` path: paths.${camelCaseOperationId},`);
409
389
  appendOperationField(output, "request", op.requestType);
410
390
  appendOperationField(output, "response", op.responseType);
411
391
  if (op.requestHeaders && op.requestHeaders.length > 0) {
412
- output.push(` headers: headers.${op.operationId},`);
392
+ output.push(` headers: headers.${camelCaseOperationId},`);
413
393
  }
414
394
  if (op.errors && hasAnyErrors(op.errors)) {
415
395
  output.push(" errors: {");
@@ -445,10 +425,57 @@ function hasAnyErrors(group) {
445
425
  group.otherErrors
446
426
  ].some((bucket) => bucket && Object.keys(bucket).length > 0);
447
427
  }
428
+ function isRequestMethod(method) {
429
+ switch (method) {
430
+ case "get":
431
+ case "put":
432
+ case "post":
433
+ case "delete":
434
+ case "options":
435
+ case "head":
436
+ case "patch":
437
+ case "trace":
438
+ return true;
439
+ default:
440
+ return false;
441
+ }
442
+ }
443
+ var CONTENT_TYPE_MAP = {
444
+ "application/json": "unknown",
445
+ // Will use schema when available
446
+ "text/csv": "string",
447
+ "text/plain": "string",
448
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
449
+ "application/octet-stream": "unknown",
450
+ "application/pdf": "unknown"
451
+ };
452
+ function findContentType(content) {
453
+ const contentTypes = Object.keys(content);
454
+ if (contentTypes.includes("application/json")) {
455
+ return "application/json";
456
+ }
457
+ for (const contentType of contentTypes) {
458
+ if (contentType in CONTENT_TYPE_MAP) {
459
+ return contentType;
460
+ }
461
+ }
462
+ return contentTypes[0] || "";
463
+ }
464
+ function inferResponseType(contentType, statusCode) {
465
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
466
+ return "undefined";
467
+ }
468
+ if (contentType in CONTENT_TYPE_MAP) {
469
+ return CONTENT_TYPE_MAP[contentType];
470
+ }
471
+ return "unknown";
472
+ }
448
473
  function parseOperations(spec) {
449
474
  const operations = [];
450
475
  for (const [path, pathItem] of Object.entries(spec.paths)) {
451
476
  for (const [method, operation] of Object.entries(pathItem)) {
477
+ const normalizedMethod = method.toLowerCase();
478
+ if (!isRequestMethod(normalizedMethod)) continue;
452
479
  if (!operation.operationId) continue;
453
480
  const pathParams = extractPathParams(path);
454
481
  const requestType = getRequestType(operation);
@@ -462,7 +489,7 @@ function parseOperations(spec) {
462
489
  operations.push({
463
490
  operationId: operation.operationId,
464
491
  path,
465
- method: method.toLowerCase(),
492
+ method: normalizedMethod,
466
493
  pathParams,
467
494
  queryParams,
468
495
  requestType,
@@ -498,20 +525,24 @@ function appendHelperTypesImport(buffer, config) {
498
525
  buffer.push(
499
526
  "type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
500
527
  );
528
+ buffer.push(
529
+ 'type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";'
530
+ );
501
531
  buffer.push(
502
532
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
503
533
  );
504
534
  buffer.push(
505
535
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
506
536
  );
507
- buffer.push(" clientErrors?: Record<string, TClient>;");
508
- buffer.push(" serverErrors?: Record<string, TServer>;");
509
- buffer.push(" defaultErrors?: Record<string, TDefault>;");
510
- buffer.push(" otherErrors?: Record<string, TOther>;");
537
+ buffer.push(" clientErrors?: TClient;");
538
+ buffer.push(" serverErrors?: TServer;");
539
+ buffer.push(" defaultErrors?: TDefault;");
540
+ buffer.push(" otherErrors?: TOther;");
511
541
  buffer.push("};");
512
542
  buffer.push(
513
- "type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
543
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
514
544
  );
545
+ buffer.push(" method: TMethod;");
515
546
  buffer.push(" path: TPath;");
516
547
  buffer.push(" request?: TRequest;");
517
548
  buffer.push(" response?: TResponse;");
@@ -525,14 +556,18 @@ function generateOperationTypes(buffer, operations, config) {
525
556
  if (!config.emit) return;
526
557
  buffer.push("// Operation Types");
527
558
  for (const op of operations) {
528
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
559
+ const camelCaseOperationId = toCamelCase(op.operationId);
560
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
529
561
  const requestType = wrapTypeReference(op.requestType);
530
562
  const responseType = wrapTypeReference(op.responseType);
531
563
  const errorsType = buildOperationErrorsType(op.errors);
532
564
  buffer.push(
533
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
565
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
566
+ );
567
+ buffer.push(` "${op.method}",`);
568
+ buffer.push(
569
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
534
570
  );
535
- buffer.push(` typeof paths.${op.operationId},`);
536
571
  buffer.push(` ${requestType},`);
537
572
  buffer.push(` ${responseType},`);
538
573
  buffer.push(` ${headerType},`);
@@ -655,13 +690,34 @@ function getResponseTypes(operation, operationId) {
655
690
  const successCodes = /* @__PURE__ */ new Map();
656
691
  const errorEntries = [];
657
692
  for (const [statusCode, response] of Object.entries(responses)) {
658
- const resolvedSchema = response?.content?.["application/json"]?.schema;
659
- if (!resolvedSchema) continue;
693
+ const content = response?.content;
694
+ if (!content || Object.keys(content).length === 0) {
695
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
696
+ successCodes.set(statusCode, "undefined");
697
+ }
698
+ continue;
699
+ }
700
+ const contentType = findContentType(content);
701
+ const resolvedSchema = content[contentType]?.schema;
702
+ if (!resolvedSchema) {
703
+ const inferredType = inferResponseType(contentType, statusCode);
704
+ if (inferredType) {
705
+ if (isErrorStatus(statusCode)) {
706
+ errorEntries.push({
707
+ code: statusCode,
708
+ schema: inferredType
709
+ });
710
+ } else if (/^2\d\d$/.test(statusCode)) {
711
+ successCodes.set(statusCode, inferredType);
712
+ }
713
+ }
714
+ continue;
715
+ }
660
716
  if (isErrorStatus(statusCode)) {
661
717
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
662
718
  continue;
663
719
  }
664
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
720
+ if (/^2\d\d$/.test(statusCode)) {
665
721
  successCodes.set(statusCode, resolvedSchema);
666
722
  }
667
723
  }
@@ -675,6 +731,9 @@ function selectSuccessResponse(responses, operationId) {
675
731
  for (const code of preferredOrder) {
676
732
  const schema = responses.get(code);
677
733
  if (schema) {
734
+ if (typeof schema === "string") {
735
+ return schema;
736
+ }
678
737
  return resolveResponseType(
679
738
  schema,
680
739
  `${capitalize(operationId)}Response${code}`
@@ -683,6 +742,9 @@ function selectSuccessResponse(responses, operationId) {
683
742
  }
684
743
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
685
744
  if (!firstSchema) return void 0;
745
+ if (typeof firstSchema === "string") {
746
+ return firstSchema;
747
+ }
686
748
  return resolveResponseType(
687
749
  firstSchema,
688
750
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -720,6 +782,9 @@ function buildErrorGroups(errors = [], operationId) {
720
782
  return group;
721
783
  }
722
784
  function resolveResponseType(schema, fallbackName) {
785
+ if (typeof schema === "string") {
786
+ return schema;
787
+ }
723
788
  if (schema.$ref) {
724
789
  return extractRefName(schema.$ref);
725
790
  }
@@ -753,16 +818,22 @@ function getQueryParams(parameters) {
753
818
  }
754
819
  return queryParams;
755
820
  }
756
- function mapHeaderType(header) {
757
- const schemaType = header.schema?.type;
821
+ function mapHeaderToZodType(header) {
822
+ const schema = header.schema ?? {};
823
+ const schemaType = schema.type;
758
824
  switch (schemaType) {
759
825
  case "integer":
760
826
  case "number":
761
- return "number";
827
+ return "z.coerce.number()";
762
828
  case "boolean":
763
- return "boolean";
829
+ return "z.coerce.boolean()";
830
+ case "array": {
831
+ const items = schema.items ?? { type: "string" };
832
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
833
+ return `z.array(${itemType})`;
834
+ }
764
835
  default:
765
- return "string";
836
+ return "z.string()";
766
837
  }
767
838
  }
768
839
  function mapQueryType(param) {
@@ -966,6 +1037,9 @@ function applyNumericBounds(schema, builder) {
966
1037
  }
967
1038
  return builder;
968
1039
  }
1040
+ function toCamelCase(str) {
1041
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1042
+ }
969
1043
  function capitalize(str) {
970
1044
  return str.charAt(0).toUpperCase() + str.slice(1);
971
1045
  }