@dudousxd/nestjs-codegen 0.15.0 → 0.16.0

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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-CjIDPMsV.cjs';
2
- export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-CjIDPMsV.cjs';
1
+ import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-LQNP7Ms3.cjs';
2
+ export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-LQNP7Ms3.cjs';
3
3
  import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
4
4
 
5
5
  declare function defineConfig(c: UserConfig): UserConfig;
@@ -373,6 +373,6 @@ interface FastDiscoveryOptions {
373
373
  }
374
374
  declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
375
375
 
376
- declare const VERSION = "0.15.0";
376
+ declare const VERSION = "0.16.0";
377
377
 
378
378
  export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, DriftGuardError, type EntryPoint, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-CjIDPMsV.js';
2
- export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-CjIDPMsV.js';
1
+ import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-LQNP7Ms3.js';
2
+ export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-LQNP7Ms3.js';
3
3
  import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
4
4
 
5
5
  declare function defineConfig(c: UserConfig): UserConfig;
@@ -373,6 +373,6 @@ interface FastDiscoveryOptions {
373
373
  }
374
374
  declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
375
375
 
376
- declare const VERSION = "0.15.0";
376
+ declare const VERSION = "0.16.0";
377
377
 
378
378
  export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, DriftGuardError, type EntryPoint, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
package/dist/index.js CHANGED
@@ -2417,8 +2417,8 @@ import chokidar from "chokidar";
2417
2417
  import { join as join14, resolve as resolve3 } from "path";
2418
2418
  import fg3 from "fast-glob";
2419
2419
  import {
2420
- Node as Node8,
2421
- Project as Project3
2420
+ Node as Node9,
2421
+ Project as Project4
2422
2422
  } from "ts-morph";
2423
2423
 
2424
2424
  // src/discovery/dto-type-resolver.ts
@@ -3410,6 +3410,12 @@ function toFilterFieldType(name, r) {
3410
3410
  }
3411
3411
 
3412
3412
  // src/discovery/filter-for.ts
3413
+ function resolveMixinEntityClass(mixin, project) {
3414
+ const entityArg = mixin?.classArgs[0];
3415
+ if (!entityArg) return void 0;
3416
+ const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
3417
+ return file?.getClass(entityArg.name);
3418
+ }
3413
3419
  function classifyFilterForHint(typeInit) {
3414
3420
  if (Node5.isStringLiteral(typeInit)) {
3415
3421
  switch (typeInit.getLiteralValue()) {
@@ -3483,7 +3489,9 @@ function extractFilterForHints(classDecl, project) {
3483
3489
  }
3484
3490
  return hints;
3485
3491
  }
3486
- function extractApplyFilterInfo(method, sourceFile, project) {
3492
+ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3493
+ const declFile = method.getSourceFile();
3494
+ const mixinEntity = resolveMixinEntityClass(mixin, project);
3487
3495
  for (const param of method.getParameters()) {
3488
3496
  const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
3489
3497
  if (!filterDecorator) continue;
@@ -3503,12 +3511,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
3503
3511
  }
3504
3512
  }
3505
3513
  const filterClassName = filterClassArg.getText();
3506
- const resolved = findType(filterClassName, sourceFile, project);
3507
- if (resolved && resolved.kind === "class") {
3508
- const classDecl = resolved.decl;
3514
+ const resolved = findType(filterClassName, declFile, project);
3515
+ const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
3516
+ if (classDecl) {
3509
3517
  let fieldTypes = extractClassPropertyTypes(classDecl, project);
3510
3518
  if (fieldTypes.length === 0) {
3511
- fieldTypes = extractFilterableEntityFields(classDecl, project);
3519
+ fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
3512
3520
  }
3513
3521
  const filterForHints = extractFilterForHints(classDecl, project);
3514
3522
  if (filterForHints.size > 0) {
@@ -3593,22 +3601,29 @@ function extractClassPropertyTypes(classDecl, project) {
3593
3601
  }
3594
3602
  return fields;
3595
3603
  }
3596
- function extractFilterableEntityFields(filterClass, project) {
3604
+ function resolveLocalClassDeclaration(identifier) {
3605
+ if (!Node5.isIdentifier(identifier)) return void 0;
3606
+ return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
3607
+ }
3608
+ function resolveDeclaredEntity(optionsArg, filterClass, project) {
3609
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
3610
+ const entityProp = optionsArg.getProperty("entity");
3611
+ if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
3612
+ const entityInit = entityProp.getInitializer();
3613
+ if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
3614
+ const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
3615
+ if (!resolved || resolved.kind !== "class") return void 0;
3616
+ return resolved.decl;
3617
+ }
3618
+ function extractFilterableEntityFields(filterClass, project, entityOverride) {
3597
3619
  const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
3598
3620
  if (!filterableDecorator) return [];
3599
3621
  const args = filterableDecorator.getArguments();
3600
3622
  if (args.length === 0) return [];
3601
3623
  const optionsArg = args[0];
3602
3624
  if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
3603
- const entityProp = optionsArg.getProperty("entity");
3604
- if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return [];
3605
- const entityInit = entityProp.getInitializer();
3606
- if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
3607
- const entityName = entityInit.getText();
3608
- const filterSourceFile = filterClass.getSourceFile();
3609
- const resolvedEntity = findType(entityName, filterSourceFile, project);
3610
- if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
3611
- const entityDecl = resolvedEntity.decl;
3625
+ const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
3626
+ if (!entityDecl) return [];
3612
3627
  const fields = collectEntityFields(
3613
3628
  entityDecl,
3614
3629
  entityDecl.getSourceFile(),
@@ -4033,9 +4048,9 @@ function unwrapNamedContainer(node, names) {
4033
4048
  }
4034
4049
  return node;
4035
4050
  }
4036
- function extractDtoContract(method, sourceFile, project) {
4051
+ function extractDtoContract(method, sourceFile, project, mixin) {
4037
4052
  let body = extractBodyType(method, sourceFile, project);
4038
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
4053
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
4039
4054
  const query = extractQueryType(method, sourceFile, project);
4040
4055
  const uploads = extractUploadedFiles(method);
4041
4056
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -4145,12 +4160,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
4145
4160
  return null;
4146
4161
  }
4147
4162
 
4163
+ // src/discovery/heritage.ts
4164
+ import { Node as Node7, Project as Project3 } from "ts-morph";
4165
+ function unwrapExpression(node) {
4166
+ let current = node;
4167
+ while (Node7.isAsExpression(current) || Node7.isSatisfiesExpression(current) || Node7.isParenthesizedExpression(current) || Node7.isTypeAssertion(current)) {
4168
+ current = current.getExpression();
4169
+ }
4170
+ return current;
4171
+ }
4172
+ function resolveReturnedClass(factoryDecl) {
4173
+ const body = factoryDecl.getBody();
4174
+ if (!body) return void 0;
4175
+ const returns = body.getDescendants().filter((n) => Node7.isReturnStatement(n));
4176
+ for (const ret of returns) {
4177
+ const expr = ret.getExpression();
4178
+ if (!expr) continue;
4179
+ const inner = unwrapExpression(expr);
4180
+ if (Node7.isClassExpression(inner)) {
4181
+ return inner;
4182
+ }
4183
+ if (Node7.isIdentifier(inner)) {
4184
+ const name = inner.getText();
4185
+ const decl = body.getDescendants().find((n) => Node7.isClassDeclaration(n) && n.getName() === name);
4186
+ if (decl) return decl;
4187
+ }
4188
+ }
4189
+ return void 0;
4190
+ }
4191
+ function resolveInheritedMethods(cls) {
4192
+ const expr = cls.getExtends()?.getExpression();
4193
+ if (!expr || !Node7.isCallExpression(expr)) return void 0;
4194
+ const callee = expr.getExpression();
4195
+ if (!Node7.isIdentifier(callee)) return void 0;
4196
+ const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isFunctionDeclaration(n));
4197
+ if (!factoryDecl) return void 0;
4198
+ const returnedClass = resolveReturnedClass(factoryDecl);
4199
+ if (!returnedClass) return void 0;
4200
+ const classArgs = [];
4201
+ for (const arg of expr.getArguments()) {
4202
+ if (!Node7.isIdentifier(arg)) continue;
4203
+ const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isClassDeclaration(n));
4204
+ if (decl) {
4205
+ classArgs.push({
4206
+ name: decl.getName() ?? arg.getText(),
4207
+ filePath: decl.getSourceFile().getFilePath()
4208
+ });
4209
+ }
4210
+ }
4211
+ return {
4212
+ methods: returnedClass.getMethods(),
4213
+ factoryName: callee.getText(),
4214
+ factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
4215
+ classArgs
4216
+ };
4217
+ }
4218
+ var mixinTypeProject;
4219
+ function getMixinTypeProject() {
4220
+ mixinTypeProject ??= new Project3({
4221
+ skipAddingFilesFromTsConfig: true,
4222
+ compilerOptions: { strict: true }
4223
+ });
4224
+ return mixinTypeProject;
4225
+ }
4226
+ function clearMixinTypeProject() {
4227
+ mixinTypeProject = void 0;
4228
+ }
4229
+ function resolveInstantiatedReturnType(cls, methodName) {
4230
+ const filePath = cls.getSourceFile().getFilePath();
4231
+ const className = cls.getName();
4232
+ if (!className) return void 0;
4233
+ const project = getMixinTypeProject();
4234
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
4235
+ const typedCls = sourceFile?.getClass(className);
4236
+ if (!typedCls) return void 0;
4237
+ const prop = typedCls.getType().getProperty(methodName);
4238
+ if (!prop) return void 0;
4239
+ const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
4240
+ if (!returnType) return void 0;
4241
+ const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
4242
+ return unwrapped.getText(typedCls);
4243
+ }
4244
+
4148
4245
  // src/discovery/zod-ast-to-ts.ts
4149
- import { Node as Node7, SyntaxKind as SyntaxKind4 } from "ts-morph";
4246
+ import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
4150
4247
  function zodAstToTs(node) {
4151
- if (!Node7.isCallExpression(node)) return "unknown";
4248
+ if (!Node8.isCallExpression(node)) return "unknown";
4152
4249
  const expr = node.getExpression();
4153
- if (Node7.isPropertyAccessExpression(expr)) {
4250
+ if (Node8.isPropertyAccessExpression(expr)) {
4154
4251
  const methodName = expr.getName();
4155
4252
  const receiver = expr.getExpression();
4156
4253
  if (methodName === "optional") {
@@ -4174,17 +4271,17 @@ function zodAstToTs(node) {
4174
4271
  case "literal": {
4175
4272
  const lit = args[0];
4176
4273
  if (!lit) return "unknown";
4177
- if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
4178
- if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
4274
+ if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
4275
+ if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
4179
4276
  if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
4180
4277
  if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
4181
4278
  return "unknown";
4182
4279
  }
4183
4280
  case "enum": {
4184
4281
  const arrArg = args[0];
4185
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4282
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4186
4283
  const members = arrArg.getElements().map(
4187
- (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4284
+ (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4188
4285
  );
4189
4286
  return members.join(" | ");
4190
4287
  }
@@ -4195,10 +4292,10 @@ function zodAstToTs(node) {
4195
4292
  }
4196
4293
  case "object": {
4197
4294
  const objArg = args[0];
4198
- if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
4295
+ if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
4199
4296
  const lines = [];
4200
4297
  for (const prop of objArg.getProperties()) {
4201
- if (!Node7.isPropertyAssignment(prop)) continue;
4298
+ if (!Node8.isPropertyAssignment(prop)) continue;
4202
4299
  const key = prop.getName();
4203
4300
  const valNode = prop.getInitializer();
4204
4301
  if (!valNode) continue;
@@ -4210,7 +4307,7 @@ function zodAstToTs(node) {
4210
4307
  }
4211
4308
  case "union": {
4212
4309
  const arrArg = args[0];
4213
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4310
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4214
4311
  return arrArg.getElements().map(zodAstToTs).join(" | ");
4215
4312
  }
4216
4313
  case "record": {
@@ -4220,7 +4317,7 @@ function zodAstToTs(node) {
4220
4317
  }
4221
4318
  case "tuple": {
4222
4319
  const arrArg = args[0];
4223
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4320
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4224
4321
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
4225
4322
  }
4226
4323
  default:
@@ -4230,18 +4327,18 @@ function zodAstToTs(node) {
4230
4327
  return "unknown";
4231
4328
  }
4232
4329
  function isOptionalChain(node) {
4233
- if (!Node7.isCallExpression(node)) return false;
4330
+ if (!Node8.isCallExpression(node)) return false;
4234
4331
  const expr = node.getExpression();
4235
- return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4332
+ return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4236
4333
  }
4237
4334
  function parseDefineContractCall(callExpr) {
4238
- if (!Node7.isCallExpression(callExpr)) return null;
4335
+ if (!Node8.isCallExpression(callExpr)) return null;
4239
4336
  const callee = callExpr.getExpression();
4240
- const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
4337
+ const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
4241
4338
  if (calleeName !== "defineContract") return null;
4242
4339
  const args = callExpr.getArguments();
4243
4340
  const optsArg = args[0];
4244
- if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
4341
+ if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
4245
4342
  let query = null;
4246
4343
  let body = null;
4247
4344
  let response = "unknown";
@@ -4249,7 +4346,7 @@ function parseDefineContractCall(callExpr) {
4249
4346
  let bodyZodText = null;
4250
4347
  let queryZodText = null;
4251
4348
  for (const prop of optsArg.getProperties()) {
4252
- if (!Node7.isPropertyAssignment(prop)) continue;
4349
+ if (!Node8.isPropertyAssignment(prop)) continue;
4253
4350
  const propName = prop.getName();
4254
4351
  const val = prop.getInitializer();
4255
4352
  if (!val) continue;
@@ -4278,6 +4375,7 @@ async function discoverContractsFast(opts) {
4278
4375
  project.addSourceFileAtPath(f);
4279
4376
  }
4280
4377
  bindDiscoveryContext(project, cwd, tsconfigPath);
4378
+ clearMixinTypeProject();
4281
4379
  return extractAllRoutes(project);
4282
4380
  }
4283
4381
  function resolveTsconfigPath(cwd, tsconfig) {
@@ -4285,14 +4383,14 @@ function resolveTsconfigPath(cwd, tsconfig) {
4285
4383
  }
4286
4384
  function createDiscoveryProject(tsconfigPath) {
4287
4385
  try {
4288
- return new Project3({
4386
+ return new Project4({
4289
4387
  tsConfigFilePath: tsconfigPath,
4290
4388
  skipAddingFilesFromTsConfig: true,
4291
4389
  skipLoadingLibFiles: true,
4292
4390
  skipFileDependencyResolution: true
4293
4391
  });
4294
4392
  } catch {
4295
- return new Project3({
4393
+ return new Project4({
4296
4394
  skipAddingFilesFromTsConfig: true,
4297
4395
  skipLoadingLibFiles: true,
4298
4396
  skipFileDependencyResolution: true,
@@ -4399,15 +4497,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
4399
4497
  runExtraction() {
4400
4498
  clearTypeResolutionCaches(this.project);
4401
4499
  clearEnumCache(this.project);
4500
+ clearMixinTypeProject();
4402
4501
  return extractRoutesFrom(this.project, this.controllerPaths);
4403
4502
  }
4404
4503
  };
4405
4504
  function decoratorStringArg(decoratorExpr) {
4406
4505
  if (!decoratorExpr) return void 0;
4407
- if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4408
- if (Node8.isArrayLiteralExpression(decoratorExpr)) {
4506
+ if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4507
+ if (Node9.isArrayLiteralExpression(decoratorExpr)) {
4409
4508
  const first = decoratorExpr.getElements()[0];
4410
- if (first && Node8.isStringLiteral(first)) return first.getLiteralValue();
4509
+ if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
4411
4510
  }
4412
4511
  return void 0;
4413
4512
  }
@@ -4429,7 +4528,8 @@ function joinPaths(prefix, suffix) {
4429
4528
  if (!prefix && !suffix) return "/";
4430
4529
  if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
4431
4530
  if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
4432
- const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
4531
+ const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
4532
+ const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
4433
4533
  const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
4434
4534
  const combined = p + s;
4435
4535
  return combined === "" ? "/" : combined;
@@ -4483,7 +4583,8 @@ function buildRoute(args) {
4483
4583
  methodAs,
4484
4584
  sourceFile,
4485
4585
  seenNames,
4486
- contractSource
4586
+ contractSource,
4587
+ mixin
4487
4588
  } = args;
4488
4589
  const routeName = resolveRouteName(className, methodName, classAs, methodAs);
4489
4590
  const qualifiedRef = `${className}.${methodName}`;
@@ -4499,7 +4600,12 @@ function buildRoute(args) {
4499
4600
  path: combinedPath,
4500
4601
  name: routeName,
4501
4602
  params: extractParams(combinedPath),
4502
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
4603
+ controllerRef: {
4604
+ className,
4605
+ methodName,
4606
+ filePath: sourceFile.getFilePath(),
4607
+ ...mixin ? { mixin } : {}
4608
+ },
4503
4609
  contract: { contractSource }
4504
4610
  };
4505
4611
  }
@@ -4513,16 +4619,17 @@ function extractContractRoute(args) {
4513
4619
  className,
4514
4620
  sourceFile,
4515
4621
  project,
4516
- seenNames
4622
+ seenNames,
4623
+ mixin
4517
4624
  } = args;
4518
4625
  const firstDecoratorArg = applyContractDecorator.getArguments()[0];
4519
4626
  if (!firstDecoratorArg) return null;
4520
4627
  let contractDef = null;
4521
4628
  let bodyZodRef = null;
4522
4629
  let queryZodRef = null;
4523
- if (Node8.isCallExpression(firstDecoratorArg)) {
4630
+ if (Node9.isCallExpression(firstDecoratorArg)) {
4524
4631
  contractDef = parseDefineContractCall(firstDecoratorArg);
4525
- } else if (Node8.isIdentifier(firstDecoratorArg)) {
4632
+ } else if (Node9.isIdentifier(firstDecoratorArg)) {
4526
4633
  const identName = firstDecoratorArg.getText();
4527
4634
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
4528
4635
  if (!resolvedVar) {
@@ -4565,6 +4672,7 @@ function extractContractRoute(args) {
4565
4672
  methodAs,
4566
4673
  sourceFile,
4567
4674
  seenNames,
4675
+ mixin,
4568
4676
  contractSource: {
4569
4677
  query: contractDef.query,
4570
4678
  body: contractDef.body,
@@ -4581,13 +4689,14 @@ function extractContractRoute(args) {
4581
4689
  });
4582
4690
  }
4583
4691
  function extractDtoRoute(args) {
4584
- const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
4692
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
4585
4693
  if (!verb) return null;
4586
4694
  const combined = joinPaths(prefix, verb.handlerPath);
4587
4695
  const methodName = method.getName();
4588
4696
  const classAs = readAsDecorator(cls, `class ${className}`);
4589
4697
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4590
- const dtoContract = extractDtoContract(method, sourceFile, project);
4698
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4699
+ const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4591
4700
  return buildRoute({
4592
4701
  className,
4593
4702
  methodName,
@@ -4597,10 +4706,11 @@ function extractDtoRoute(args) {
4597
4706
  methodAs,
4598
4707
  sourceFile,
4599
4708
  seenNames,
4709
+ mixin,
4600
4710
  contractSource: {
4601
4711
  query: dtoContract?.query ?? null,
4602
4712
  body: dtoContract?.body ?? null,
4603
- response: dtoContract?.response ?? "unknown",
4713
+ response: mixinResponse ?? dtoContract?.response ?? "unknown",
4604
4714
  error: dtoContract?.error ?? null,
4605
4715
  queryRef: dtoContract?.queryRef ?? null,
4606
4716
  bodyRef: dtoContract?.bodyRef ?? null,
@@ -4629,7 +4739,17 @@ function extractFromSourceFile(sourceFile, project) {
4629
4739
  const firstArg2 = controllerDecorator.getArguments()[0];
4630
4740
  const prefix = decoratorStringArg(firstArg2) ?? "";
4631
4741
  const className = cls.getName() ?? "Unknown";
4632
- for (const method of cls.getMethods()) {
4742
+ const inherited = resolveInheritedMethods(cls);
4743
+ const mixinBinding = inherited ? {
4744
+ factoryName: inherited.factoryName,
4745
+ factoryFilePath: inherited.factoryFilePath,
4746
+ classArgs: inherited.classArgs
4747
+ } : void 0;
4748
+ const methods = [
4749
+ ...cls.getMethods().map((method) => ({ method })),
4750
+ ...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
4751
+ ];
4752
+ for (const { method, mixin } of methods) {
4633
4753
  const verb = resolveVerb(method);
4634
4754
  const applyContractDecorator = method.getDecorator("ApplyContract");
4635
4755
  const route = applyContractDecorator ? extractContractRoute({
@@ -4641,7 +4761,8 @@ function extractFromSourceFile(sourceFile, project) {
4641
4761
  className,
4642
4762
  sourceFile,
4643
4763
  project,
4644
- seenNames
4764
+ seenNames,
4765
+ mixin
4645
4766
  }) : extractDtoRoute({
4646
4767
  cls,
4647
4768
  method,
@@ -4650,7 +4771,8 @@ function extractFromSourceFile(sourceFile, project) {
4650
4771
  className,
4651
4772
  sourceFile,
4652
4773
  project,
4653
- seenNames
4774
+ seenNames,
4775
+ mixin
4654
4776
  });
4655
4777
  if (route) routes.push(route);
4656
4778
  }
@@ -4934,7 +5056,7 @@ function createChainModuleRenderer(opts) {
4934
5056
  }
4935
5057
 
4936
5058
  // src/index.ts
4937
- var VERSION = "0.15.0";
5059
+ var VERSION = "0.16.0";
4938
5060
  export {
4939
5061
  CodegenError,
4940
5062
  ConfigError,