zenko 0.1.6-beta.1 → 0.1.7-beta.1

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
@@ -29,7 +29,7 @@ var path = __toESM(require("path"), 1);
29
29
  var import_url = require("url");
30
30
  var import_js_yaml = require("js-yaml");
31
31
 
32
- // ../zenko-core/src/utils/topological-sort.ts
32
+ // src/utils/topological-sort.ts
33
33
  function topologicalSort(schemas) {
34
34
  const visited = /* @__PURE__ */ new Set();
35
35
  const visiting = /* @__PURE__ */ new Set();
@@ -78,7 +78,7 @@ function extractRefName(ref) {
78
78
  return ref.split("/").pop() || "Unknown";
79
79
  }
80
80
 
81
- // ../zenko-core/src/utils/property-name.ts
81
+ // src/utils/property-name.ts
82
82
  function isValidJSIdentifier(name) {
83
83
  if (!name) return false;
84
84
  const firstChar = name.at(0);
@@ -158,7 +158,15 @@ function formatPropertyName(name) {
158
158
  return isValidJSIdentifier(name) ? name : `"${name}"`;
159
159
  }
160
160
 
161
- // ../zenko-core/src/utils/http-status.ts
161
+ // src/utils/string-utils.ts
162
+ function toCamelCase(str) {
163
+ return str.replace(/-([a-zA-Z])/g, (_, letter) => letter.toUpperCase()).replace(/-+$/, "");
164
+ }
165
+ function capitalize(str) {
166
+ return str.charAt(0).toUpperCase() + str.slice(1);
167
+ }
168
+
169
+ // src/utils/http-status.ts
162
170
  var statusNameMap = {
163
171
  "400": "badRequest",
164
172
  "401": "unauthorized",
@@ -217,14 +225,6 @@ function mapStatusToIdentifier(status) {
217
225
  if (!candidate) return "unknownError";
218
226
  return /^[a-zA-Z_$]/.test(candidate) ? candidate : `status${candidate.charAt(0).toUpperCase()}${candidate.slice(1)}`;
219
227
  }
220
- function getStatusCategory(status) {
221
- if (status === "default") return "default";
222
- const code = Number(status);
223
- if (!Number.isInteger(code)) return "unknown";
224
- if (code >= 400 && code <= 499) return "client";
225
- if (code >= 500 && code <= 599) return "server";
226
- return "unknown";
227
- }
228
228
  function isErrorStatus(status) {
229
229
  if (status === "default") return true;
230
230
  const code = Number(status);
@@ -232,7 +232,170 @@ function isErrorStatus(status) {
232
232
  return code >= 400;
233
233
  }
234
234
 
235
- // ../zenko-core/src/zenko.ts
235
+ // src/utils/tree-shaking.ts
236
+ function analyzeZenkoUsage(operations) {
237
+ const usage = {
238
+ usesHeaderFn: false,
239
+ usesOperationDefinition: false,
240
+ usesOperationErrors: false
241
+ };
242
+ if (operations.length > 0) {
243
+ usage.usesOperationDefinition = true;
244
+ }
245
+ for (const op of operations) {
246
+ if (op.requestHeaders && op.requestHeaders.length > 0) {
247
+ usage.usesHeaderFn = true;
248
+ }
249
+ if (op.errors && hasAnyErrors(op.errors)) {
250
+ usage.usesOperationErrors = true;
251
+ }
252
+ }
253
+ if (operations.length > 0 && !usage.usesOperationErrors) {
254
+ const hasDefaultErrors = operations.some(
255
+ (op) => !op.errors || !hasAnyErrors(op.errors)
256
+ );
257
+ if (hasDefaultErrors) {
258
+ usage.usesOperationErrors = true;
259
+ }
260
+ }
261
+ return usage;
262
+ }
263
+ function generateZenkoImport(usage, mode, helpersOutput) {
264
+ const types = [];
265
+ if (usage.usesHeaderFn) types.push("HeaderFn");
266
+ if (usage.usesOperationDefinition) types.push("OperationDefinition");
267
+ if (usage.usesOperationErrors) types.push("OperationErrors");
268
+ if (types.length === 0) {
269
+ return "";
270
+ }
271
+ const importSource = mode === "package" ? '"zenko"' : `"${helpersOutput}"`;
272
+ return `import type { ${types.join(", ")} } from ${importSource};`;
273
+ }
274
+ function hasAnyErrors(errors) {
275
+ return Boolean(errors && Object.keys(errors).length > 0);
276
+ }
277
+
278
+ // src/utils/collect-inline-types.ts
279
+ function collectInlineRequestTypes(operations, spec) {
280
+ const requestTypesToGenerate = /* @__PURE__ */ new Map();
281
+ const operationLookup = /* @__PURE__ */ new Map();
282
+ for (const [, pathItem] of Object.entries(spec.paths || {})) {
283
+ for (const [, operation] of Object.entries(pathItem)) {
284
+ const op = operation;
285
+ if (op.operationId) {
286
+ operationLookup.set(op.operationId, op);
287
+ }
288
+ }
289
+ }
290
+ for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
291
+ for (const [, operation] of Object.entries(pathItem)) {
292
+ const op = operation;
293
+ if (op.operationId) {
294
+ operationLookup.set(op.operationId, op);
295
+ }
296
+ }
297
+ }
298
+ for (const op of operations) {
299
+ const operation = operationLookup.get(op.operationId);
300
+ if (!operation) continue;
301
+ const requestBody = operation.requestBody;
302
+ if (requestBody && requestBody.content) {
303
+ const content = requestBody.content;
304
+ const jsonContent = content["application/json"];
305
+ if (jsonContent && jsonContent.schema) {
306
+ const schema = jsonContent.schema;
307
+ const typeName = `${capitalize(toCamelCase(op.operationId))}Request`;
308
+ if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
309
+ requestTypesToGenerate.set(typeName, schema);
310
+ }
311
+ }
312
+ }
313
+ }
314
+ return requestTypesToGenerate;
315
+ }
316
+ function collectInlineResponseTypes(operations, spec) {
317
+ const responseTypesToGenerate = /* @__PURE__ */ new Map();
318
+ const operationLookup = /* @__PURE__ */ new Map();
319
+ for (const [, pathItem] of Object.entries(spec.paths || {})) {
320
+ for (const [, operation] of Object.entries(pathItem)) {
321
+ const op = operation;
322
+ if (op.operationId) {
323
+ operationLookup.set(op.operationId, op);
324
+ }
325
+ }
326
+ }
327
+ for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
328
+ for (const [, operation] of Object.entries(pathItem)) {
329
+ const op = operation;
330
+ if (op.operationId) {
331
+ operationLookup.set(op.operationId, op);
332
+ }
333
+ }
334
+ }
335
+ for (const op of operations) {
336
+ const operation = operationLookup.get(op.operationId);
337
+ if (!operation) continue;
338
+ const responses = operation.responses || {};
339
+ for (const [statusCode, response] of Object.entries(responses)) {
340
+ if (/^2\d\d$/.test(statusCode) && response.content) {
341
+ const content = response.content;
342
+ const jsonContent = content["application/json"];
343
+ if (jsonContent && jsonContent.schema) {
344
+ const schema = jsonContent.schema;
345
+ const typeName = `${capitalize(toCamelCase(op.operationId))}Response`;
346
+ if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
347
+ responseTypesToGenerate.set(typeName, schema);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ return responseTypesToGenerate;
354
+ }
355
+
356
+ // src/utils/generate-helper-file.ts
357
+ function generateHelperFile() {
358
+ const output = [];
359
+ output.push("// Generated helper types for Zenko");
360
+ output.push(
361
+ "// This file provides type definitions for operation objects and path functions"
362
+ );
363
+ output.push("");
364
+ output.push(
365
+ "export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
366
+ );
367
+ output.push("");
368
+ output.push(
369
+ 'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
370
+ );
371
+ output.push("");
372
+ output.push(
373
+ "export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
374
+ );
375
+ output.push("");
376
+ output.push(
377
+ "export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
378
+ );
379
+ output.push("");
380
+ output.push(
381
+ "export type OperationErrors<TError = unknown> = TError extends Record<string, unknown> ? TError : Record<string, TError>;"
382
+ );
383
+ output.push("");
384
+ output.push(
385
+ "export type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
386
+ );
387
+ output.push(" method: TMethod");
388
+ output.push(" path: TPath");
389
+ output.push(" request?: TRequest");
390
+ output.push(" response?: TResponse");
391
+ output.push(" headers?: THeaders");
392
+ output.push(" errors?: TErrors");
393
+ output.push("}");
394
+ output.push("");
395
+ return output.join("\n");
396
+ }
397
+
398
+ // src/zenko.ts
236
399
  function generateWithMetadata(spec, options = {}) {
237
400
  const output = [];
238
401
  const generatedTypes = /* @__PURE__ */ new Set();
@@ -243,7 +406,14 @@ function generateWithMetadata(spec, options = {}) {
243
406
  strictNumeric
244
407
  };
245
408
  output.push('import { z } from "zod";');
246
- appendHelperTypesImport(output, typesConfig);
409
+ const nameMap = /* @__PURE__ */ new Map();
410
+ if (spec.components?.schemas) {
411
+ for (const name of Object.keys(spec.components.schemas)) {
412
+ nameMap.set(name, toCamelCase(name));
413
+ }
414
+ }
415
+ const operations = parseOperations(spec, nameMap);
416
+ appendHelperTypesImport(output, typesConfig, operations);
247
417
  output.push("");
248
418
  if (spec.components?.schemas) {
249
419
  output.push("// Generated Zod Schemas");
@@ -251,15 +421,23 @@ function generateWithMetadata(spec, options = {}) {
251
421
  const sortedSchemas = topologicalSort(spec.components.schemas);
252
422
  for (const name of sortedSchemas) {
253
423
  const schema = spec.components.schemas[name];
424
+ const sanitizedName = nameMap.get(name);
254
425
  output.push(
255
- generateZodSchema(name, schema, generatedTypes, schemaOptions)
426
+ generateZodSchema(
427
+ sanitizedName,
428
+ schema,
429
+ generatedTypes,
430
+ schemaOptions,
431
+ nameMap
432
+ )
256
433
  );
257
434
  output.push("");
258
- output.push(`export type ${name} = z.infer<typeof ${name}>;`);
435
+ output.push(
436
+ `export type ${sanitizedName} = z.infer<typeof ${sanitizedName}>;`
437
+ );
259
438
  output.push("");
260
439
  }
261
440
  }
262
- const operations = parseOperations(spec);
263
441
  output.push("// Path Functions");
264
442
  output.push("export const paths = {");
265
443
  for (const op of operations) {
@@ -273,24 +451,37 @@ function generateWithMetadata(spec, options = {}) {
273
451
  );
274
452
  continue;
275
453
  }
276
- const allParamNames = [
277
- ...pathParamNames,
278
- ...op.queryParams.map((p) => p.name)
279
- ];
280
- const signaturePieces = [];
454
+ const alias = (n) => {
455
+ if (isValidJSIdentifier(n)) return n;
456
+ let aliased = toCamelCase(n);
457
+ if (!isValidJSIdentifier(aliased)) {
458
+ aliased = `_${aliased}`;
459
+ }
460
+ return aliased;
461
+ };
462
+ const destructPieces = [];
463
+ const typePieces = [];
281
464
  for (const param of op.pathParams) {
282
- signaturePieces.push(`${param.name}: string`);
465
+ destructPieces.push(
466
+ isValidJSIdentifier(param.name) ? param.name : `${formatPropertyName(param.name)}: ${alias(param.name)}`
467
+ );
468
+ typePieces.push(`${formatPropertyName(param.name)}: string`);
283
469
  }
284
470
  for (const param of op.queryParams) {
285
- signaturePieces.push(
286
- `${param.name}${param.required ? "" : "?"}: ${mapQueryType(param)}`
471
+ destructPieces.push(
472
+ isValidJSIdentifier(param.name) ? param.name : `${formatPropertyName(param.name)}: ${alias(param.name)}`
473
+ );
474
+ typePieces.push(
475
+ `${formatPropertyName(param.name)}${param.required ? "" : "?"}: ${mapQueryType(param)}`
287
476
  );
288
477
  }
289
- const signatureParams = signaturePieces.join(", ");
290
478
  const needsDefaultObject = !hasPathParams && hasQueryParams && op.queryParams.every((param) => !param.required);
291
- const signatureArgs = allParamNames.length ? `{ ${allParamNames.join(", ")} }` : "{}";
292
- const signature = `${signatureArgs}: { ${signatureParams} }${needsDefaultObject ? " = {}" : ""}`;
293
- const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
479
+ const signatureArgs = destructPieces.length ? `{ ${destructPieces.join(", ")} }` : "{}";
480
+ const signature = `${signatureArgs}: { ${typePieces.join(", ")} }${needsDefaultObject ? " = {}" : ""}`;
481
+ const pathWithParams = op.path.replace(
482
+ /{([^}]+)}/g,
483
+ (_m, n) => `\${${alias(n)}}`
484
+ );
294
485
  if (!hasQueryParams) {
295
486
  output.push(
296
487
  ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
@@ -302,8 +493,7 @@ function generateWithMetadata(spec, options = {}) {
302
493
  );
303
494
  output.push(" const params = new URLSearchParams()");
304
495
  for (const param of op.queryParams) {
305
- const propertyKey = formatPropertyName(param.name);
306
- const accessor = isValidJSIdentifier(param.name) ? param.name : propertyKey;
496
+ const accessor = isValidJSIdentifier(param.name) ? param.name : alias(toCamelCase(param.name));
307
497
  const schema = param.schema ?? {};
308
498
  if (schema?.type === "array") {
309
499
  const itemValueExpression = convertQueryParamValue(
@@ -385,10 +575,14 @@ function generateWithMetadata(spec, options = {}) {
385
575
  }
386
576
  output.push("} as const;");
387
577
  output.push("");
578
+ generateRequestTypes(output, operations, spec, nameMap, schemaOptions);
579
+ generateResponseTypes(output, operations, spec, nameMap, schemaOptions);
580
+ generateOperationTypes(output, operations, typesConfig);
388
581
  output.push("// Operation Objects");
389
582
  for (const op of operations) {
390
583
  const camelCaseOperationId = toCamelCase(op.operationId);
391
- output.push(`export const ${camelCaseOperationId} = {`);
584
+ const typeAnnotation = typesConfig.emit ? `: ${capitalize(camelCaseOperationId)}Operation` : "";
585
+ output.push(`export const ${camelCaseOperationId}${typeAnnotation} = {`);
392
586
  output.push(` method: "${op.method}",`);
393
587
  output.push(` path: paths.${camelCaseOperationId},`);
394
588
  appendOperationField(output, "request", op.requestType);
@@ -396,18 +590,12 @@ function generateWithMetadata(spec, options = {}) {
396
590
  if (op.requestHeaders && op.requestHeaders.length > 0) {
397
591
  output.push(` headers: headers.${camelCaseOperationId},`);
398
592
  }
399
- if (op.errors && hasAnyErrors(op.errors)) {
400
- output.push(" errors: {");
401
- appendErrorGroup(output, "clientErrors", op.errors.clientErrors);
402
- appendErrorGroup(output, "serverErrors", op.errors.serverErrors);
403
- appendErrorGroup(output, "defaultErrors", op.errors.defaultErrors);
404
- appendErrorGroup(output, "otherErrors", op.errors.otherErrors);
405
- output.push(" },");
593
+ if (op.errors && hasAnyErrors2(op.errors)) {
594
+ appendErrorGroup(output, "errors", op.errors);
406
595
  }
407
596
  output.push("} as const;");
408
597
  output.push("");
409
598
  }
410
- generateOperationTypes(output, operations, typesConfig);
411
599
  const result = {
412
600
  output: output.join("\n")
413
601
  };
@@ -419,6 +607,46 @@ function generateWithMetadata(spec, options = {}) {
419
607
  }
420
608
  return result;
421
609
  }
610
+ function generateRequestTypes(output, operations, spec, nameMap, schemaOptions) {
611
+ const requestTypesToGenerate = collectInlineRequestTypes(operations, spec);
612
+ if (requestTypesToGenerate.size > 0) {
613
+ output.push("// Generated Request Types");
614
+ output.push("");
615
+ for (const [typeName, schema] of requestTypesToGenerate) {
616
+ const generatedSchema = generateZodSchema(
617
+ typeName,
618
+ schema,
619
+ /* @__PURE__ */ new Set(),
620
+ schemaOptions,
621
+ nameMap
622
+ );
623
+ output.push(generatedSchema);
624
+ output.push("");
625
+ output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
626
+ output.push("");
627
+ }
628
+ }
629
+ }
630
+ function generateResponseTypes(output, operations, spec, nameMap, schemaOptions) {
631
+ const responseTypesToGenerate = collectInlineResponseTypes(operations, spec);
632
+ if (responseTypesToGenerate.size > 0) {
633
+ output.push("// Generated Response Types");
634
+ output.push("");
635
+ for (const [typeName, schema] of responseTypesToGenerate) {
636
+ const generatedSchema = generateZodSchema(
637
+ typeName,
638
+ schema,
639
+ /* @__PURE__ */ new Set(),
640
+ schemaOptions,
641
+ nameMap
642
+ );
643
+ output.push(generatedSchema);
644
+ output.push("");
645
+ output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
646
+ output.push("");
647
+ }
648
+ }
649
+ }
422
650
  function appendOperationField(buffer, key, value) {
423
651
  if (!value) return;
424
652
  buffer.push(` ${key}: ${value},`);
@@ -431,13 +659,8 @@ function appendErrorGroup(buffer, label, errors) {
431
659
  }
432
660
  buffer.push(" },");
433
661
  }
434
- function hasAnyErrors(group) {
435
- return [
436
- group.clientErrors,
437
- group.serverErrors,
438
- group.defaultErrors,
439
- group.otherErrors
440
- ].some((bucket) => bucket && Object.keys(bucket).length > 0);
662
+ function hasAnyErrors2(group) {
663
+ return Boolean(group && Object.keys(group).length > 0);
441
664
  }
442
665
  function isRequestMethod(method) {
443
666
  switch (method) {
@@ -484,33 +707,70 @@ function inferResponseType(contentType, statusCode) {
484
707
  }
485
708
  return "unknown";
486
709
  }
487
- function parseOperations(spec) {
710
+ function parseOperations(spec, nameMap) {
488
711
  const operations = [];
489
- for (const [path2, pathItem] of Object.entries(spec.paths)) {
490
- for (const [method, operation] of Object.entries(pathItem)) {
491
- const normalizedMethod = method.toLowerCase();
492
- if (!isRequestMethod(normalizedMethod)) continue;
493
- if (!operation.operationId) continue;
494
- const pathParams = extractPathParams(path2);
495
- const requestType = getRequestType(operation);
496
- const { successResponse, errors } = getResponseTypes(
497
- operation,
498
- operation.operationId
499
- );
500
- const resolvedParameters = collectParameters(pathItem, operation, spec);
501
- const requestHeaders = getRequestHeaders(resolvedParameters);
502
- const queryParams = getQueryParams(resolvedParameters);
503
- operations.push({
504
- operationId: operation.operationId,
505
- path: path2,
506
- method: normalizedMethod,
507
- pathParams,
508
- queryParams,
509
- requestType,
510
- responseType: successResponse,
511
- requestHeaders,
512
- errors
513
- });
712
+ if (spec.paths) {
713
+ for (const [path2, pathItem] of Object.entries(spec.paths)) {
714
+ for (const [method, operation] of Object.entries(pathItem)) {
715
+ const normalizedMethod = method.toLowerCase();
716
+ if (!isRequestMethod(normalizedMethod)) continue;
717
+ if (!operation.operationId) continue;
718
+ const pathParams = extractPathParams(path2);
719
+ const requestType = getRequestType(operation);
720
+ const { successResponse, errors } = getResponseTypes(
721
+ operation,
722
+ operation.operationId,
723
+ nameMap
724
+ );
725
+ const resolvedParameters = collectParameters(pathItem, operation, spec);
726
+ const requestHeaders = getRequestHeaders(resolvedParameters);
727
+ const queryParams = getQueryParams(resolvedParameters);
728
+ operations.push({
729
+ operationId: operation.operationId,
730
+ path: path2,
731
+ method: normalizedMethod,
732
+ pathParams,
733
+ queryParams,
734
+ requestType,
735
+ responseType: successResponse,
736
+ requestHeaders,
737
+ errors
738
+ });
739
+ }
740
+ }
741
+ }
742
+ if (spec.webhooks) {
743
+ for (const [webhookName, webhookItem] of Object.entries(spec.webhooks)) {
744
+ for (const [method, operation] of Object.entries(webhookItem)) {
745
+ const normalizedMethod = method.toLowerCase();
746
+ if (!isRequestMethod(normalizedMethod)) continue;
747
+ if (!operation.operationId) continue;
748
+ const path2 = webhookName;
749
+ const pathParams = extractPathParams(path2);
750
+ const requestType = getRequestType(operation);
751
+ const { successResponse, errors } = getResponseTypes(
752
+ operation,
753
+ operation.operationId
754
+ );
755
+ const resolvedParameters = collectParameters(
756
+ webhookItem,
757
+ operation,
758
+ spec
759
+ );
760
+ const requestHeaders = getRequestHeaders(resolvedParameters);
761
+ const queryParams = getQueryParams(resolvedParameters);
762
+ operations.push({
763
+ operationId: operation.operationId,
764
+ path: path2,
765
+ method: normalizedMethod,
766
+ pathParams,
767
+ queryParams,
768
+ requestType,
769
+ responseType: successResponse,
770
+ requestHeaders,
771
+ errors
772
+ });
773
+ }
514
774
  }
515
775
  }
516
776
  return operations;
@@ -519,21 +779,42 @@ function normalizeTypesConfig(config) {
519
779
  return {
520
780
  emit: config?.emit ?? true,
521
781
  helpers: config?.helpers ?? "package",
522
- helpersOutput: config?.helpersOutput ?? "./zenko-types"
782
+ helpersOutput: config?.helpersOutput ?? "./zenko-types",
783
+ treeShake: config?.treeShake ?? true
523
784
  };
524
785
  }
525
- function appendHelperTypesImport(buffer, config) {
786
+ function appendHelperTypesImport(buffer, config, operations) {
526
787
  if (!config.emit) return;
527
788
  switch (config.helpers) {
528
789
  case "package":
529
- buffer.push(
530
- 'import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "zenko";'
531
- );
790
+ if (config.treeShake) {
791
+ const usage = analyzeZenkoUsage(operations);
792
+ const importStatement = generateZenkoImport(usage, "package");
793
+ if (importStatement) {
794
+ buffer.push(importStatement);
795
+ }
796
+ } else {
797
+ buffer.push(
798
+ 'import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "zenko";'
799
+ );
800
+ }
532
801
  return;
533
802
  case "file":
534
- buffer.push(
535
- `import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "${config.helpersOutput}";`
536
- );
803
+ if (config.treeShake) {
804
+ const usage = analyzeZenkoUsage(operations);
805
+ const importStatement = generateZenkoImport(
806
+ usage,
807
+ "file",
808
+ config.helpersOutput
809
+ );
810
+ if (importStatement) {
811
+ buffer.push(importStatement);
812
+ }
813
+ } else {
814
+ buffer.push(
815
+ `import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "${config.helpersOutput}";`
816
+ );
817
+ }
537
818
  return;
538
819
  case "inline":
539
820
  buffer.push(
@@ -549,24 +830,19 @@ function appendHelperTypesImport(buffer, config) {
549
830
  "type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown);"
550
831
  );
551
832
  buffer.push(
552
- "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
833
+ "type OperationErrors<TError = unknown> = TError extends Record<string, unknown> ? TError : Record<string, TError>;"
553
834
  );
554
- buffer.push(" clientErrors?: TClient;");
555
- buffer.push(" serverErrors?: TServer;");
556
- buffer.push(" defaultErrors?: TDefault;");
557
- buffer.push(" otherErrors?: TOther;");
558
- buffer.push("};");
559
835
  buffer.push(
560
836
  "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
561
837
  );
562
- buffer.push(" method: TMethod;");
563
- buffer.push(" path: TPath;");
564
- buffer.push(" request?: TRequest;");
565
- buffer.push(" response?: TResponse;");
566
- buffer.push(" headers?: THeaders;");
567
- buffer.push(" errors?: TErrors;");
568
- buffer.push("};");
569
- return;
838
+ buffer.push(" method: TMethod");
839
+ buffer.push(" path: TPath");
840
+ buffer.push(" request?: TRequest");
841
+ buffer.push(" response?: TResponse");
842
+ buffer.push(" headers?: THeaders");
843
+ buffer.push(" errors?: TErrors");
844
+ buffer.push("}");
845
+ buffer.push("");
570
846
  }
571
847
  }
572
848
  function generateOperationTypes(buffer, operations, config) {
@@ -594,14 +870,11 @@ function generateOperationTypes(buffer, operations, config) {
594
870
  }
595
871
  }
596
872
  function buildOperationErrorsType(errors) {
597
- if (!errors || !hasAnyErrors(errors)) {
873
+ if (!errors || !hasAnyErrors2(errors)) {
598
874
  return "OperationErrors";
599
875
  }
600
- const client = buildErrorBucket(errors.clientErrors);
601
- const server = buildErrorBucket(errors.serverErrors);
602
- const fallback = buildErrorBucket(errors.defaultErrors);
603
- const other = buildErrorBucket(errors.otherErrors);
604
- return `OperationErrors<${client}, ${server}, ${fallback}, ${other}>`;
876
+ const errorBucket = buildErrorBucket(errors);
877
+ return `OperationErrors<${errorBucket}>`;
605
878
  }
606
879
  function buildErrorBucket(bucket) {
607
880
  if (!bucket || Object.keys(bucket).length === 0) {
@@ -708,10 +981,10 @@ function getRequestType(operation) {
708
981
  if (requestBody.$ref) {
709
982
  return extractRefName(requestBody.$ref);
710
983
  }
711
- const typeName = `${capitalize(operation.operationId)}Request`;
984
+ const typeName = `${capitalize(toCamelCase(operation.operationId))}Request`;
712
985
  return typeName;
713
986
  }
714
- function getResponseTypes(operation, operationId) {
987
+ function getResponseTypes(operation, operationId, nameMap) {
715
988
  const responses = operation.responses ?? {};
716
989
  const successCodes = /* @__PURE__ */ new Map();
717
990
  const errorEntries = [];
@@ -752,11 +1025,15 @@ function getResponseTypes(operation, operationId) {
752
1025
  successCodes.set(statusCode, resolvedSchema);
753
1026
  }
754
1027
  }
755
- const successResponse = selectSuccessResponse(successCodes, operationId);
756
- const errors = buildErrorGroups(errorEntries, operationId);
1028
+ const successResponse = selectSuccessResponse(
1029
+ successCodes,
1030
+ operationId,
1031
+ nameMap
1032
+ );
1033
+ const errors = buildErrorGroups(errorEntries, operationId, nameMap);
757
1034
  return { successResponse, errors };
758
1035
  }
759
- function selectSuccessResponse(responses, operationId) {
1036
+ function selectSuccessResponse(responses, operationId, nameMap) {
760
1037
  if (responses.size === 0) return void 0;
761
1038
  const preferredOrder = ["200", "201", "204"];
762
1039
  for (const code of preferredOrder) {
@@ -767,61 +1044,51 @@ function selectSuccessResponse(responses, operationId) {
767
1044
  }
768
1045
  return resolveResponseType(
769
1046
  schema,
770
- `${capitalize(operationId)}Response${code}`
1047
+ `${capitalize(toCamelCase(operationId))}Response`,
1048
+ nameMap
771
1049
  );
772
1050
  }
773
1051
  }
774
- const [firstCode, firstSchema] = responses.entries().next().value ?? [];
1052
+ const [, firstSchema] = responses.entries().next().value ?? [];
775
1053
  if (!firstSchema) return void 0;
776
1054
  if (typeof firstSchema === "string") {
777
1055
  return firstSchema;
778
1056
  }
779
1057
  return resolveResponseType(
780
1058
  firstSchema,
781
- `${capitalize(operationId)}Response${firstCode ?? "Default"}`
1059
+ `${capitalize(toCamelCase(operationId))}Response`,
1060
+ nameMap
782
1061
  );
783
1062
  }
784
- function buildErrorGroups(errors = [], operationId) {
1063
+ function buildErrorGroups(errors = [], operationId, nameMap) {
785
1064
  if (!errors.length) return void 0;
786
1065
  const group = {};
787
1066
  for (const { code, schema } of errors) {
788
- const category = getStatusCategory(code);
789
1067
  const identifier = mapStatusToIdentifier(code);
790
1068
  const typeName = resolveResponseType(
791
1069
  schema,
792
- `${capitalize(operationId)}${capitalize(identifier)}`
1070
+ `${capitalize(toCamelCase(operationId))}${capitalize(identifier)}`,
1071
+ nameMap
793
1072
  );
794
- switch (category) {
795
- case "client":
796
- group.clientErrors ??= {};
797
- group.clientErrors[identifier] = typeName;
798
- break;
799
- case "server":
800
- group.serverErrors ??= {};
801
- group.serverErrors[identifier] = typeName;
802
- break;
803
- case "default":
804
- group.defaultErrors ??= {};
805
- group.defaultErrors[identifier] = typeName;
806
- break;
807
- default:
808
- group.otherErrors ??= {};
809
- group.otherErrors[identifier] = typeName;
810
- break;
811
- }
1073
+ group[identifier] = typeName;
812
1074
  }
813
1075
  return group;
814
1076
  }
815
- function resolveResponseType(schema, fallbackName) {
1077
+ function resolveResponseType(schema, fallbackName, nameMap) {
816
1078
  if (typeof schema === "string") {
817
1079
  return schema;
818
1080
  }
819
1081
  if (schema.$ref) {
820
- return extractRefName(schema.$ref);
1082
+ const refName = extractRefName(schema.$ref);
1083
+ return nameMap?.get(refName) || refName;
821
1084
  }
822
1085
  if (schema.type === "array" && schema.items?.$ref) {
823
1086
  const itemRef = extractRefName(schema.items.$ref);
824
- return `z.array(${itemRef})`;
1087
+ const sanitizedItemRef = nameMap?.get(itemRef) || itemRef;
1088
+ return `z.array(${sanitizedItemRef})`;
1089
+ }
1090
+ if (schema.allOf && Array.isArray(schema.allOf)) {
1091
+ return fallbackName;
825
1092
  }
826
1093
  return fallbackName;
827
1094
  }
@@ -904,19 +1171,30 @@ function convertQueryParamValue(schema, accessor) {
904
1171
  return `String(${accessor})`;
905
1172
  }
906
1173
  }
907
- function generateZodSchema(name, schema, generatedTypes, options) {
1174
+ function generateZodSchema(name, schema, generatedTypes, options, nameMap) {
908
1175
  if (generatedTypes.has(name)) return "";
909
1176
  generatedTypes.add(name);
910
1177
  if (schema.enum) {
911
1178
  const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
912
1179
  return `export const ${name} = z.enum([${enumValues}]);`;
913
1180
  }
1181
+ if (schema.allOf && Array.isArray(schema.allOf)) {
1182
+ const allOfParts = schema.allOf.map(
1183
+ (part) => getZodTypeFromSchema(part, options, nameMap)
1184
+ );
1185
+ if (allOfParts.length === 0) return `export const ${name} = z.object({});`;
1186
+ if (allOfParts.length === 1)
1187
+ return `export const ${name} = ${allOfParts[0]};`;
1188
+ const first = allOfParts[0];
1189
+ const rest = allOfParts.slice(1).map((part) => `.and(${part})`).join("");
1190
+ return `export const ${name} = ${first}${rest};`;
1191
+ }
914
1192
  if (schema.type === "object" || schema.properties) {
915
- return `export const ${name} = ${buildZodObject(schema, options)};`;
1193
+ return `export const ${name} = ${buildZodObject(schema, options, nameMap)};`;
916
1194
  }
917
1195
  if (schema.type === "array") {
918
1196
  const itemSchema = schema.items ?? { type: "unknown" };
919
- const itemType = getZodTypeFromSchema(itemSchema, options);
1197
+ const itemType = getZodTypeFromSchema(itemSchema, options, nameMap);
920
1198
  const builder = applyStrictArrayBounds(
921
1199
  schema,
922
1200
  `z.array(${itemType})`,
@@ -925,18 +1203,29 @@ function generateZodSchema(name, schema, generatedTypes, options) {
925
1203
  );
926
1204
  return `export const ${name} = ${builder};`;
927
1205
  }
928
- return `export const ${name} = ${getZodTypeFromSchema(schema, options)};`;
1206
+ return `export const ${name} = ${getZodTypeFromSchema(schema, options, nameMap)};`;
929
1207
  }
930
- function getZodTypeFromSchema(schema, options) {
1208
+ function getZodTypeFromSchema(schema, options, nameMap) {
931
1209
  if (schema.$ref) {
932
- return extractRefName(schema.$ref);
1210
+ const refName = extractRefName(schema.$ref);
1211
+ return nameMap?.get(refName) || refName;
933
1212
  }
934
1213
  if (schema.enum) {
935
1214
  const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
936
1215
  return `z.enum([${enumValues}])`;
937
1216
  }
938
- if (schema.type === "object" || schema.properties) {
939
- return buildZodObject(schema, options);
1217
+ if (schema.allOf && Array.isArray(schema.allOf)) {
1218
+ const allOfParts = schema.allOf.map(
1219
+ (part) => getZodTypeFromSchema(part, options, nameMap)
1220
+ );
1221
+ if (allOfParts.length === 0) return "z.object({})";
1222
+ if (allOfParts.length === 1) return allOfParts[0];
1223
+ const first = allOfParts[0];
1224
+ const rest = allOfParts.slice(1).map((part) => `.and(${part})`).join("");
1225
+ return `${first}${rest}`;
1226
+ }
1227
+ if (schema.type === "object" || schema.properties || schema.allOf || schema.oneOf || schema.anyOf) {
1228
+ return buildZodObject(schema, options, nameMap);
940
1229
  }
941
1230
  switch (schema.type) {
942
1231
  case "string":
@@ -946,7 +1235,8 @@ function getZodTypeFromSchema(schema, options) {
946
1235
  case "array":
947
1236
  return `z.array(${getZodTypeFromSchema(
948
1237
  schema.items ?? { type: "unknown" },
949
- options
1238
+ options,
1239
+ nameMap
950
1240
  )})`;
951
1241
  case "null":
952
1242
  return "z.null()";
@@ -958,13 +1248,13 @@ function getZodTypeFromSchema(schema, options) {
958
1248
  return "z.unknown()";
959
1249
  }
960
1250
  }
961
- function buildZodObject(schema, options) {
1251
+ function buildZodObject(schema, options, nameMap) {
962
1252
  const properties = [];
963
1253
  for (const [propName, propSchema] of Object.entries(
964
1254
  schema.properties || {}
965
1255
  )) {
966
1256
  const isRequired = schema.required?.includes(propName) ?? false;
967
- const zodType = getZodTypeFromSchema(propSchema, options);
1257
+ const zodType = getZodTypeFromSchema(propSchema, options, nameMap);
968
1258
  const finalType = isRequired ? zodType : `${zodType}.optional()`;
969
1259
  properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
970
1260
  }
@@ -1072,57 +1362,6 @@ function applyNumericBounds(schema, builder) {
1072
1362
  }
1073
1363
  return builder;
1074
1364
  }
1075
- function generateHelperFile() {
1076
- const output = [];
1077
- output.push("// Generated helper types for Zenko");
1078
- output.push(
1079
- "// This file provides type definitions for operation objects and path functions"
1080
- );
1081
- output.push("");
1082
- output.push(
1083
- "export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
1084
- );
1085
- output.push("");
1086
- output.push(
1087
- 'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
1088
- );
1089
- output.push("");
1090
- output.push(
1091
- "export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
1092
- );
1093
- output.push("");
1094
- output.push(
1095
- "export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
1096
- );
1097
- output.push("");
1098
- output.push(
1099
- "export type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
1100
- );
1101
- output.push(" clientErrors?: TClient");
1102
- output.push(" serverErrors?: TServer");
1103
- output.push(" defaultErrors?: TDefault");
1104
- output.push(" otherErrors?: TOther");
1105
- output.push("}");
1106
- output.push("");
1107
- output.push(
1108
- "export type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
1109
- );
1110
- output.push(" method: TMethod");
1111
- output.push(" path: TPath");
1112
- output.push(" request?: TRequest");
1113
- output.push(" response?: TResponse");
1114
- output.push(" headers?: THeaders");
1115
- output.push(" errors?: TErrors");
1116
- output.push("}");
1117
- output.push("");
1118
- return output.join("\n");
1119
- }
1120
- function toCamelCase(str) {
1121
- return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1122
- }
1123
- function capitalize(str) {
1124
- return str.charAt(0).toUpperCase() + str.slice(1);
1125
- }
1126
1365
 
1127
1366
  // src/cli.ts
1128
1367
  async function main() {
@@ -1289,7 +1528,10 @@ async function generateSingle(options) {
1289
1528
  fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
1290
1529
  fs.writeFileSync(resolvedOutput, result.output);
1291
1530
  console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
1292
- console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths).length} paths`);
1531
+ console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths || {}).length} paths`);
1532
+ if (spec.webhooks) {
1533
+ console.log(`\u{1FA9D} Processed ${Object.keys(spec.webhooks).length} webhooks`);
1534
+ }
1293
1535
  if (result.helperFile) {
1294
1536
  const helperPath = path.isAbsolute(result.helperFile.path) ? result.helperFile.path : path.resolve(path.dirname(resolvedOutput), result.helperFile.path);
1295
1537
  const absoluteResolvedOutput = path.resolve(resolvedOutput);