@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.
@@ -1,6 +1,6 @@
1
1
  import * as _nestjs_common from '@nestjs/common';
2
2
  import { DynamicModule, OnApplicationBootstrap, OnModuleDestroy, ExecutionContext } from '@nestjs/common';
3
- import { U as UserConfig } from '../index-CjIDPMsV.cjs';
3
+ import { U as UserConfig } from '../index-LQNP7Ms3.cjs';
4
4
  import 'ts-morph';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import * as _nestjs_common from '@nestjs/common';
2
2
  import { DynamicModule, OnApplicationBootstrap, OnModuleDestroy, ExecutionContext } from '@nestjs/common';
3
- import { U as UserConfig } from '../index-CjIDPMsV.js';
3
+ import { U as UserConfig } from '../index-LQNP7Ms3.js';
4
4
  import 'ts-morph';
5
5
 
6
6
  /**
@@ -2234,8 +2234,8 @@ import chokidar from "chokidar";
2234
2234
  import { join as join13, resolve as resolve3 } from "path";
2235
2235
  import fg2 from "fast-glob";
2236
2236
  import {
2237
- Node as Node8,
2238
- Project as Project3
2237
+ Node as Node9,
2238
+ Project as Project4
2239
2239
  } from "ts-morph";
2240
2240
 
2241
2241
  // src/discovery/dto-type-resolver.ts
@@ -3227,6 +3227,12 @@ function toFilterFieldType(name, r) {
3227
3227
  }
3228
3228
 
3229
3229
  // src/discovery/filter-for.ts
3230
+ function resolveMixinEntityClass(mixin, project) {
3231
+ const entityArg = mixin?.classArgs[0];
3232
+ if (!entityArg) return void 0;
3233
+ const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
3234
+ return file?.getClass(entityArg.name);
3235
+ }
3230
3236
  function classifyFilterForHint(typeInit) {
3231
3237
  if (Node5.isStringLiteral(typeInit)) {
3232
3238
  switch (typeInit.getLiteralValue()) {
@@ -3300,7 +3306,9 @@ function extractFilterForHints(classDecl, project) {
3300
3306
  }
3301
3307
  return hints;
3302
3308
  }
3303
- function extractApplyFilterInfo(method, sourceFile, project) {
3309
+ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3310
+ const declFile = method.getSourceFile();
3311
+ const mixinEntity = resolveMixinEntityClass(mixin, project);
3304
3312
  for (const param of method.getParameters()) {
3305
3313
  const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
3306
3314
  if (!filterDecorator) continue;
@@ -3320,12 +3328,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
3320
3328
  }
3321
3329
  }
3322
3330
  const filterClassName = filterClassArg.getText();
3323
- const resolved = findType(filterClassName, sourceFile, project);
3324
- if (resolved && resolved.kind === "class") {
3325
- const classDecl = resolved.decl;
3331
+ const resolved = findType(filterClassName, declFile, project);
3332
+ const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
3333
+ if (classDecl) {
3326
3334
  let fieldTypes = extractClassPropertyTypes(classDecl, project);
3327
3335
  if (fieldTypes.length === 0) {
3328
- fieldTypes = extractFilterableEntityFields(classDecl, project);
3336
+ fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
3329
3337
  }
3330
3338
  const filterForHints = extractFilterForHints(classDecl, project);
3331
3339
  if (filterForHints.size > 0) {
@@ -3410,22 +3418,29 @@ function extractClassPropertyTypes(classDecl, project) {
3410
3418
  }
3411
3419
  return fields;
3412
3420
  }
3413
- function extractFilterableEntityFields(filterClass, project) {
3421
+ function resolveLocalClassDeclaration(identifier) {
3422
+ if (!Node5.isIdentifier(identifier)) return void 0;
3423
+ return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
3424
+ }
3425
+ function resolveDeclaredEntity(optionsArg, filterClass, project) {
3426
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
3427
+ const entityProp = optionsArg.getProperty("entity");
3428
+ if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
3429
+ const entityInit = entityProp.getInitializer();
3430
+ if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
3431
+ const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
3432
+ if (!resolved || resolved.kind !== "class") return void 0;
3433
+ return resolved.decl;
3434
+ }
3435
+ function extractFilterableEntityFields(filterClass, project, entityOverride) {
3414
3436
  const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
3415
3437
  if (!filterableDecorator) return [];
3416
3438
  const args = filterableDecorator.getArguments();
3417
3439
  if (args.length === 0) return [];
3418
3440
  const optionsArg = args[0];
3419
3441
  if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
3420
- const entityProp = optionsArg.getProperty("entity");
3421
- if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return [];
3422
- const entityInit = entityProp.getInitializer();
3423
- if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
3424
- const entityName = entityInit.getText();
3425
- const filterSourceFile = filterClass.getSourceFile();
3426
- const resolvedEntity = findType(entityName, filterSourceFile, project);
3427
- if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
3428
- const entityDecl = resolvedEntity.decl;
3442
+ const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
3443
+ if (!entityDecl) return [];
3429
3444
  const fields = collectEntityFields(
3430
3445
  entityDecl,
3431
3446
  entityDecl.getSourceFile(),
@@ -3850,9 +3865,9 @@ function unwrapNamedContainer(node, names) {
3850
3865
  }
3851
3866
  return node;
3852
3867
  }
3853
- function extractDtoContract(method, sourceFile, project) {
3868
+ function extractDtoContract(method, sourceFile, project, mixin) {
3854
3869
  let body = extractBodyType(method, sourceFile, project);
3855
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
3870
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
3856
3871
  const query = extractQueryType(method, sourceFile, project);
3857
3872
  const uploads = extractUploadedFiles(method);
3858
3873
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -3962,12 +3977,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
3962
3977
  return null;
3963
3978
  }
3964
3979
 
3980
+ // src/discovery/heritage.ts
3981
+ import { Node as Node7, Project as Project3 } from "ts-morph";
3982
+ function unwrapExpression(node) {
3983
+ let current = node;
3984
+ while (Node7.isAsExpression(current) || Node7.isSatisfiesExpression(current) || Node7.isParenthesizedExpression(current) || Node7.isTypeAssertion(current)) {
3985
+ current = current.getExpression();
3986
+ }
3987
+ return current;
3988
+ }
3989
+ function resolveReturnedClass(factoryDecl) {
3990
+ const body = factoryDecl.getBody();
3991
+ if (!body) return void 0;
3992
+ const returns = body.getDescendants().filter((n) => Node7.isReturnStatement(n));
3993
+ for (const ret of returns) {
3994
+ const expr = ret.getExpression();
3995
+ if (!expr) continue;
3996
+ const inner = unwrapExpression(expr);
3997
+ if (Node7.isClassExpression(inner)) {
3998
+ return inner;
3999
+ }
4000
+ if (Node7.isIdentifier(inner)) {
4001
+ const name = inner.getText();
4002
+ const decl = body.getDescendants().find((n) => Node7.isClassDeclaration(n) && n.getName() === name);
4003
+ if (decl) return decl;
4004
+ }
4005
+ }
4006
+ return void 0;
4007
+ }
4008
+ function resolveInheritedMethods(cls) {
4009
+ const expr = cls.getExtends()?.getExpression();
4010
+ if (!expr || !Node7.isCallExpression(expr)) return void 0;
4011
+ const callee = expr.getExpression();
4012
+ if (!Node7.isIdentifier(callee)) return void 0;
4013
+ const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isFunctionDeclaration(n));
4014
+ if (!factoryDecl) return void 0;
4015
+ const returnedClass = resolveReturnedClass(factoryDecl);
4016
+ if (!returnedClass) return void 0;
4017
+ const classArgs = [];
4018
+ for (const arg of expr.getArguments()) {
4019
+ if (!Node7.isIdentifier(arg)) continue;
4020
+ const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isClassDeclaration(n));
4021
+ if (decl) {
4022
+ classArgs.push({
4023
+ name: decl.getName() ?? arg.getText(),
4024
+ filePath: decl.getSourceFile().getFilePath()
4025
+ });
4026
+ }
4027
+ }
4028
+ return {
4029
+ methods: returnedClass.getMethods(),
4030
+ factoryName: callee.getText(),
4031
+ factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
4032
+ classArgs
4033
+ };
4034
+ }
4035
+ var mixinTypeProject;
4036
+ function getMixinTypeProject() {
4037
+ mixinTypeProject ??= new Project3({
4038
+ skipAddingFilesFromTsConfig: true,
4039
+ compilerOptions: { strict: true }
4040
+ });
4041
+ return mixinTypeProject;
4042
+ }
4043
+ function clearMixinTypeProject() {
4044
+ mixinTypeProject = void 0;
4045
+ }
4046
+ function resolveInstantiatedReturnType(cls, methodName) {
4047
+ const filePath = cls.getSourceFile().getFilePath();
4048
+ const className = cls.getName();
4049
+ if (!className) return void 0;
4050
+ const project = getMixinTypeProject();
4051
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
4052
+ const typedCls = sourceFile?.getClass(className);
4053
+ if (!typedCls) return void 0;
4054
+ const prop = typedCls.getType().getProperty(methodName);
4055
+ if (!prop) return void 0;
4056
+ const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
4057
+ if (!returnType) return void 0;
4058
+ const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
4059
+ return unwrapped.getText(typedCls);
4060
+ }
4061
+
3965
4062
  // src/discovery/zod-ast-to-ts.ts
3966
- import { Node as Node7, SyntaxKind as SyntaxKind4 } from "ts-morph";
4063
+ import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
3967
4064
  function zodAstToTs(node) {
3968
- if (!Node7.isCallExpression(node)) return "unknown";
4065
+ if (!Node8.isCallExpression(node)) return "unknown";
3969
4066
  const expr = node.getExpression();
3970
- if (Node7.isPropertyAccessExpression(expr)) {
4067
+ if (Node8.isPropertyAccessExpression(expr)) {
3971
4068
  const methodName = expr.getName();
3972
4069
  const receiver = expr.getExpression();
3973
4070
  if (methodName === "optional") {
@@ -3991,17 +4088,17 @@ function zodAstToTs(node) {
3991
4088
  case "literal": {
3992
4089
  const lit = args[0];
3993
4090
  if (!lit) return "unknown";
3994
- if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
3995
- if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
4091
+ if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
4092
+ if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
3996
4093
  if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
3997
4094
  if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
3998
4095
  return "unknown";
3999
4096
  }
4000
4097
  case "enum": {
4001
4098
  const arrArg = args[0];
4002
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4099
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4003
4100
  const members = arrArg.getElements().map(
4004
- (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4101
+ (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4005
4102
  );
4006
4103
  return members.join(" | ");
4007
4104
  }
@@ -4012,10 +4109,10 @@ function zodAstToTs(node) {
4012
4109
  }
4013
4110
  case "object": {
4014
4111
  const objArg = args[0];
4015
- if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
4112
+ if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
4016
4113
  const lines = [];
4017
4114
  for (const prop of objArg.getProperties()) {
4018
- if (!Node7.isPropertyAssignment(prop)) continue;
4115
+ if (!Node8.isPropertyAssignment(prop)) continue;
4019
4116
  const key = prop.getName();
4020
4117
  const valNode = prop.getInitializer();
4021
4118
  if (!valNode) continue;
@@ -4027,7 +4124,7 @@ function zodAstToTs(node) {
4027
4124
  }
4028
4125
  case "union": {
4029
4126
  const arrArg = args[0];
4030
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4127
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4031
4128
  return arrArg.getElements().map(zodAstToTs).join(" | ");
4032
4129
  }
4033
4130
  case "record": {
@@ -4037,7 +4134,7 @@ function zodAstToTs(node) {
4037
4134
  }
4038
4135
  case "tuple": {
4039
4136
  const arrArg = args[0];
4040
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4137
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4041
4138
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
4042
4139
  }
4043
4140
  default:
@@ -4047,18 +4144,18 @@ function zodAstToTs(node) {
4047
4144
  return "unknown";
4048
4145
  }
4049
4146
  function isOptionalChain(node) {
4050
- if (!Node7.isCallExpression(node)) return false;
4147
+ if (!Node8.isCallExpression(node)) return false;
4051
4148
  const expr = node.getExpression();
4052
- return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4149
+ return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4053
4150
  }
4054
4151
  function parseDefineContractCall(callExpr) {
4055
- if (!Node7.isCallExpression(callExpr)) return null;
4152
+ if (!Node8.isCallExpression(callExpr)) return null;
4056
4153
  const callee = callExpr.getExpression();
4057
- const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
4154
+ const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
4058
4155
  if (calleeName !== "defineContract") return null;
4059
4156
  const args = callExpr.getArguments();
4060
4157
  const optsArg = args[0];
4061
- if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
4158
+ if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
4062
4159
  let query = null;
4063
4160
  let body = null;
4064
4161
  let response = "unknown";
@@ -4066,7 +4163,7 @@ function parseDefineContractCall(callExpr) {
4066
4163
  let bodyZodText = null;
4067
4164
  let queryZodText = null;
4068
4165
  for (const prop of optsArg.getProperties()) {
4069
- if (!Node7.isPropertyAssignment(prop)) continue;
4166
+ if (!Node8.isPropertyAssignment(prop)) continue;
4070
4167
  const propName = prop.getName();
4071
4168
  const val = prop.getInitializer();
4072
4169
  if (!val) continue;
@@ -4091,14 +4188,14 @@ function resolveTsconfigPath(cwd, tsconfig) {
4091
4188
  }
4092
4189
  function createDiscoveryProject(tsconfigPath) {
4093
4190
  try {
4094
- return new Project3({
4191
+ return new Project4({
4095
4192
  tsConfigFilePath: tsconfigPath,
4096
4193
  skipAddingFilesFromTsConfig: true,
4097
4194
  skipLoadingLibFiles: true,
4098
4195
  skipFileDependencyResolution: true
4099
4196
  });
4100
4197
  } catch {
4101
- return new Project3({
4198
+ return new Project4({
4102
4199
  skipAddingFilesFromTsConfig: true,
4103
4200
  skipLoadingLibFiles: true,
4104
4201
  skipFileDependencyResolution: true,
@@ -4198,15 +4295,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
4198
4295
  runExtraction() {
4199
4296
  clearTypeResolutionCaches(this.project);
4200
4297
  clearEnumCache(this.project);
4298
+ clearMixinTypeProject();
4201
4299
  return extractRoutesFrom(this.project, this.controllerPaths);
4202
4300
  }
4203
4301
  };
4204
4302
  function decoratorStringArg(decoratorExpr) {
4205
4303
  if (!decoratorExpr) return void 0;
4206
- if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4207
- if (Node8.isArrayLiteralExpression(decoratorExpr)) {
4304
+ if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4305
+ if (Node9.isArrayLiteralExpression(decoratorExpr)) {
4208
4306
  const first = decoratorExpr.getElements()[0];
4209
- if (first && Node8.isStringLiteral(first)) return first.getLiteralValue();
4307
+ if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
4210
4308
  }
4211
4309
  return void 0;
4212
4310
  }
@@ -4228,7 +4326,8 @@ function joinPaths(prefix, suffix) {
4228
4326
  if (!prefix && !suffix) return "/";
4229
4327
  if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
4230
4328
  if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
4231
- const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
4329
+ const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
4330
+ const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
4232
4331
  const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
4233
4332
  const combined = p + s;
4234
4333
  return combined === "" ? "/" : combined;
@@ -4282,7 +4381,8 @@ function buildRoute(args) {
4282
4381
  methodAs,
4283
4382
  sourceFile,
4284
4383
  seenNames,
4285
- contractSource
4384
+ contractSource,
4385
+ mixin
4286
4386
  } = args;
4287
4387
  const routeName = resolveRouteName(className, methodName, classAs, methodAs);
4288
4388
  const qualifiedRef = `${className}.${methodName}`;
@@ -4298,7 +4398,12 @@ function buildRoute(args) {
4298
4398
  path: combinedPath,
4299
4399
  name: routeName,
4300
4400
  params: extractParams(combinedPath),
4301
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
4401
+ controllerRef: {
4402
+ className,
4403
+ methodName,
4404
+ filePath: sourceFile.getFilePath(),
4405
+ ...mixin ? { mixin } : {}
4406
+ },
4302
4407
  contract: { contractSource }
4303
4408
  };
4304
4409
  }
@@ -4312,16 +4417,17 @@ function extractContractRoute(args) {
4312
4417
  className,
4313
4418
  sourceFile,
4314
4419
  project,
4315
- seenNames
4420
+ seenNames,
4421
+ mixin
4316
4422
  } = args;
4317
4423
  const firstDecoratorArg = applyContractDecorator.getArguments()[0];
4318
4424
  if (!firstDecoratorArg) return null;
4319
4425
  let contractDef = null;
4320
4426
  let bodyZodRef = null;
4321
4427
  let queryZodRef = null;
4322
- if (Node8.isCallExpression(firstDecoratorArg)) {
4428
+ if (Node9.isCallExpression(firstDecoratorArg)) {
4323
4429
  contractDef = parseDefineContractCall(firstDecoratorArg);
4324
- } else if (Node8.isIdentifier(firstDecoratorArg)) {
4430
+ } else if (Node9.isIdentifier(firstDecoratorArg)) {
4325
4431
  const identName = firstDecoratorArg.getText();
4326
4432
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
4327
4433
  if (!resolvedVar) {
@@ -4364,6 +4470,7 @@ function extractContractRoute(args) {
4364
4470
  methodAs,
4365
4471
  sourceFile,
4366
4472
  seenNames,
4473
+ mixin,
4367
4474
  contractSource: {
4368
4475
  query: contractDef.query,
4369
4476
  body: contractDef.body,
@@ -4380,13 +4487,14 @@ function extractContractRoute(args) {
4380
4487
  });
4381
4488
  }
4382
4489
  function extractDtoRoute(args) {
4383
- const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
4490
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
4384
4491
  if (!verb) return null;
4385
4492
  const combined = joinPaths(prefix, verb.handlerPath);
4386
4493
  const methodName = method.getName();
4387
4494
  const classAs = readAsDecorator(cls, `class ${className}`);
4388
4495
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4389
- const dtoContract = extractDtoContract(method, sourceFile, project);
4496
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4497
+ const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4390
4498
  return buildRoute({
4391
4499
  className,
4392
4500
  methodName,
@@ -4396,10 +4504,11 @@ function extractDtoRoute(args) {
4396
4504
  methodAs,
4397
4505
  sourceFile,
4398
4506
  seenNames,
4507
+ mixin,
4399
4508
  contractSource: {
4400
4509
  query: dtoContract?.query ?? null,
4401
4510
  body: dtoContract?.body ?? null,
4402
- response: dtoContract?.response ?? "unknown",
4511
+ response: mixinResponse ?? dtoContract?.response ?? "unknown",
4403
4512
  error: dtoContract?.error ?? null,
4404
4513
  queryRef: dtoContract?.queryRef ?? null,
4405
4514
  bodyRef: dtoContract?.bodyRef ?? null,
@@ -4428,7 +4537,17 @@ function extractFromSourceFile(sourceFile, project) {
4428
4537
  const firstArg2 = controllerDecorator.getArguments()[0];
4429
4538
  const prefix = decoratorStringArg(firstArg2) ?? "";
4430
4539
  const className = cls.getName() ?? "Unknown";
4431
- for (const method of cls.getMethods()) {
4540
+ const inherited = resolveInheritedMethods(cls);
4541
+ const mixinBinding = inherited ? {
4542
+ factoryName: inherited.factoryName,
4543
+ factoryFilePath: inherited.factoryFilePath,
4544
+ classArgs: inherited.classArgs
4545
+ } : void 0;
4546
+ const methods = [
4547
+ ...cls.getMethods().map((method) => ({ method })),
4548
+ ...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
4549
+ ];
4550
+ for (const { method, mixin } of methods) {
4432
4551
  const verb = resolveVerb(method);
4433
4552
  const applyContractDecorator = method.getDecorator("ApplyContract");
4434
4553
  const route = applyContractDecorator ? extractContractRoute({
@@ -4440,7 +4559,8 @@ function extractFromSourceFile(sourceFile, project) {
4440
4559
  className,
4441
4560
  sourceFile,
4442
4561
  project,
4443
- seenNames
4562
+ seenNames,
4563
+ mixin
4444
4564
  }) : extractDtoRoute({
4445
4565
  cls,
4446
4566
  method,
@@ -4449,7 +4569,8 @@ function extractFromSourceFile(sourceFile, project) {
4449
4569
  className,
4450
4570
  sourceFile,
4451
4571
  project,
4452
- seenNames
4572
+ seenNames,
4573
+ mixin
4453
4574
  });
4454
4575
  if (route) routes.push(route);
4455
4576
  }
@@ -4650,7 +4771,7 @@ async function watch(config, onChange, options = {}) {
4650
4771
  }
4651
4772
 
4652
4773
  // src/index.ts
4653
- var VERSION = "0.15.0";
4774
+ var VERSION = "0.16.0";
4654
4775
 
4655
4776
  // src/generate-manifest.ts
4656
4777
  var MANIFEST_FILE = ".codegen-manifest.json";