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.mjs CHANGED
@@ -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,82 +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} = {`);
382
- output.push(` path: paths.${op.operationId},`);
359
+ const camelCaseOperationId = toCamelCase(op.operationId);
360
+ output.push(`export const ${camelCaseOperationId} = {`);
361
+ output.push(` method: "${op.method}",`);
362
+ output.push(` path: paths.${camelCaseOperationId},`);
383
363
  appendOperationField(output, "request", op.requestType);
384
364
  appendOperationField(output, "response", op.responseType);
385
365
  if (op.requestHeaders && op.requestHeaders.length > 0) {
386
- output.push(` headers: headers.${op.operationId},`);
366
+ output.push(` headers: headers.${camelCaseOperationId},`);
387
367
  }
388
368
  if (op.errors && hasAnyErrors(op.errors)) {
389
369
  output.push(" errors: {");
@@ -419,10 +399,57 @@ function hasAnyErrors(group) {
419
399
  group.otherErrors
420
400
  ].some((bucket) => bucket && Object.keys(bucket).length > 0);
421
401
  }
402
+ function isRequestMethod(method) {
403
+ switch (method) {
404
+ case "get":
405
+ case "put":
406
+ case "post":
407
+ case "delete":
408
+ case "options":
409
+ case "head":
410
+ case "patch":
411
+ case "trace":
412
+ return true;
413
+ default:
414
+ return false;
415
+ }
416
+ }
417
+ var CONTENT_TYPE_MAP = {
418
+ "application/json": "unknown",
419
+ // Will use schema when available
420
+ "text/csv": "string",
421
+ "text/plain": "string",
422
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
423
+ "application/octet-stream": "unknown",
424
+ "application/pdf": "unknown"
425
+ };
426
+ function findContentType(content) {
427
+ const contentTypes = Object.keys(content);
428
+ if (contentTypes.includes("application/json")) {
429
+ return "application/json";
430
+ }
431
+ for (const contentType of contentTypes) {
432
+ if (contentType in CONTENT_TYPE_MAP) {
433
+ return contentType;
434
+ }
435
+ }
436
+ return contentTypes[0] || "";
437
+ }
438
+ function inferResponseType(contentType, statusCode) {
439
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
440
+ return "undefined";
441
+ }
442
+ if (contentType in CONTENT_TYPE_MAP) {
443
+ return CONTENT_TYPE_MAP[contentType];
444
+ }
445
+ return "unknown";
446
+ }
422
447
  function parseOperations(spec) {
423
448
  const operations = [];
424
449
  for (const [path, pathItem] of Object.entries(spec.paths)) {
425
450
  for (const [method, operation] of Object.entries(pathItem)) {
451
+ const normalizedMethod = method.toLowerCase();
452
+ if (!isRequestMethod(normalizedMethod)) continue;
426
453
  if (!operation.operationId) continue;
427
454
  const pathParams = extractPathParams(path);
428
455
  const requestType = getRequestType(operation);
@@ -436,7 +463,7 @@ function parseOperations(spec) {
436
463
  operations.push({
437
464
  operationId: operation.operationId,
438
465
  path,
439
- method: method.toLowerCase(),
466
+ method: normalizedMethod,
440
467
  pathParams,
441
468
  queryParams,
442
469
  requestType,
@@ -472,20 +499,24 @@ function appendHelperTypesImport(buffer, config) {
472
499
  buffer.push(
473
500
  "type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
474
501
  );
502
+ buffer.push(
503
+ 'type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";'
504
+ );
475
505
  buffer.push(
476
506
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
477
507
  );
478
508
  buffer.push(
479
509
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
480
510
  );
481
- buffer.push(" clientErrors?: Record<string, TClient>;");
482
- buffer.push(" serverErrors?: Record<string, TServer>;");
483
- buffer.push(" defaultErrors?: Record<string, TDefault>;");
484
- buffer.push(" otherErrors?: Record<string, TOther>;");
511
+ buffer.push(" clientErrors?: TClient;");
512
+ buffer.push(" serverErrors?: TServer;");
513
+ buffer.push(" defaultErrors?: TDefault;");
514
+ buffer.push(" otherErrors?: TOther;");
485
515
  buffer.push("};");
486
516
  buffer.push(
487
- "type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
517
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
488
518
  );
519
+ buffer.push(" method: TMethod;");
489
520
  buffer.push(" path: TPath;");
490
521
  buffer.push(" request?: TRequest;");
491
522
  buffer.push(" response?: TResponse;");
@@ -499,14 +530,18 @@ function generateOperationTypes(buffer, operations, config) {
499
530
  if (!config.emit) return;
500
531
  buffer.push("// Operation Types");
501
532
  for (const op of operations) {
502
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
533
+ const camelCaseOperationId = toCamelCase(op.operationId);
534
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
503
535
  const requestType = wrapTypeReference(op.requestType);
504
536
  const responseType = wrapTypeReference(op.responseType);
505
537
  const errorsType = buildOperationErrorsType(op.errors);
506
538
  buffer.push(
507
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
539
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
540
+ );
541
+ buffer.push(` "${op.method}",`);
542
+ buffer.push(
543
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
508
544
  );
509
- buffer.push(` typeof paths.${op.operationId},`);
510
545
  buffer.push(` ${requestType},`);
511
546
  buffer.push(` ${responseType},`);
512
547
  buffer.push(` ${headerType},`);
@@ -629,13 +664,34 @@ function getResponseTypes(operation, operationId) {
629
664
  const successCodes = /* @__PURE__ */ new Map();
630
665
  const errorEntries = [];
631
666
  for (const [statusCode, response] of Object.entries(responses)) {
632
- const resolvedSchema = response?.content?.["application/json"]?.schema;
633
- if (!resolvedSchema) continue;
667
+ const content = response?.content;
668
+ if (!content || Object.keys(content).length === 0) {
669
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
670
+ successCodes.set(statusCode, "undefined");
671
+ }
672
+ continue;
673
+ }
674
+ const contentType = findContentType(content);
675
+ const resolvedSchema = content[contentType]?.schema;
676
+ if (!resolvedSchema) {
677
+ const inferredType = inferResponseType(contentType, statusCode);
678
+ if (inferredType) {
679
+ if (isErrorStatus(statusCode)) {
680
+ errorEntries.push({
681
+ code: statusCode,
682
+ schema: inferredType
683
+ });
684
+ } else if (/^2\d\d$/.test(statusCode)) {
685
+ successCodes.set(statusCode, inferredType);
686
+ }
687
+ }
688
+ continue;
689
+ }
634
690
  if (isErrorStatus(statusCode)) {
635
691
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
636
692
  continue;
637
693
  }
638
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
694
+ if (/^2\d\d$/.test(statusCode)) {
639
695
  successCodes.set(statusCode, resolvedSchema);
640
696
  }
641
697
  }
@@ -649,6 +705,9 @@ function selectSuccessResponse(responses, operationId) {
649
705
  for (const code of preferredOrder) {
650
706
  const schema = responses.get(code);
651
707
  if (schema) {
708
+ if (typeof schema === "string") {
709
+ return schema;
710
+ }
652
711
  return resolveResponseType(
653
712
  schema,
654
713
  `${capitalize(operationId)}Response${code}`
@@ -657,6 +716,9 @@ function selectSuccessResponse(responses, operationId) {
657
716
  }
658
717
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
659
718
  if (!firstSchema) return void 0;
719
+ if (typeof firstSchema === "string") {
720
+ return firstSchema;
721
+ }
660
722
  return resolveResponseType(
661
723
  firstSchema,
662
724
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -694,6 +756,9 @@ function buildErrorGroups(errors = [], operationId) {
694
756
  return group;
695
757
  }
696
758
  function resolveResponseType(schema, fallbackName) {
759
+ if (typeof schema === "string") {
760
+ return schema;
761
+ }
697
762
  if (schema.$ref) {
698
763
  return extractRefName(schema.$ref);
699
764
  }
@@ -727,16 +792,22 @@ function getQueryParams(parameters) {
727
792
  }
728
793
  return queryParams;
729
794
  }
730
- function mapHeaderType(header) {
731
- const schemaType = header.schema?.type;
795
+ function mapHeaderToZodType(header) {
796
+ const schema = header.schema ?? {};
797
+ const schemaType = schema.type;
732
798
  switch (schemaType) {
733
799
  case "integer":
734
800
  case "number":
735
- return "number";
801
+ return "z.coerce.number()";
736
802
  case "boolean":
737
- return "boolean";
803
+ return "z.coerce.boolean()";
804
+ case "array": {
805
+ const items = schema.items ?? { type: "string" };
806
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
807
+ return `z.array(${itemType})`;
808
+ }
738
809
  default:
739
- return "string";
810
+ return "z.string()";
740
811
  }
741
812
  }
742
813
  function mapQueryType(param) {
@@ -940,6 +1011,9 @@ function applyNumericBounds(schema, builder) {
940
1011
  }
941
1012
  return builder;
942
1013
  }
1014
+ function toCamelCase(str) {
1015
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1016
+ }
943
1017
  function capitalize(str) {
944
1018
  return str.charAt(0).toUpperCase() + str.slice(1);
945
1019
  }