@dudousxd/nestjs-codegen 0.14.2 → 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.
@@ -702,6 +702,9 @@ function buildErrorType(c) {
702
702
  }
703
703
  return c.contractSource.error ?? "unknown";
704
704
  }
705
+ function filterFieldLiterals(fields) {
706
+ return fields?.length ? fields.map((f) => JSON.stringify(f)) : [];
707
+ }
705
708
  function emitRouterTypeBlock(tree, indent, outDir, serialization) {
706
709
  const pad = " ".repeat(indent);
707
710
  const lines = [];
@@ -728,7 +731,8 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
728
731
  const params = buildParamsType(c.params);
729
732
  const safeMethod = JSON.stringify(method);
730
733
  const safeUrl = JSON.stringify(c.path);
731
- const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
734
+ const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
735
+ const filterFields = filterLiterals.length ? filterLiterals.join(" | ") : "never";
732
736
  const stream = c.contractSource.stream ? "true" : "false";
733
737
  const binary = c.contractSource.binaryResponse ? "true" : "false";
734
738
  lines.push(
@@ -768,6 +772,7 @@ function buildRequestModel(c) {
768
772
  const TA = buildRouterTypeAccess(c.name);
769
773
  const withParams = hasPathParams(c.params);
770
774
  const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
775
+ const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
771
776
  const fields = [];
772
777
  if (withParams) fields.push(`params: ${TA}['params']`);
773
778
  if (hasQuery) fields.push(`query?: ${TA}['query']`);
@@ -797,7 +802,12 @@ function buildRequestModel(c) {
797
802
  // (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
798
803
  // clean prefix that partial-matches every parametrized variant — making it
799
804
  // directly usable for `invalidateQueries`.
800
- queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`
805
+ queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`,
806
+ // Runtime counterpart to the type-level `filterFields` union: the same
807
+ // discovered field list, emitted as a literal `[...] as const` so apps can
808
+ // validate a dynamic/user-supplied field string with `isFilterField(...)`
809
+ // instead of casting. Omitted for routes with no filter.
810
+ ...filterLiterals.length ? { filterFieldsExpr: `[${filterLiterals.join(", ")}] as const` } : {}
801
811
  };
802
812
  }
803
813
  function renderFetcherRequest(req, binaryResponse) {
@@ -832,9 +842,24 @@ function emitReqHelper() {
832
842
  ""
833
843
  ];
834
844
  }
845
+ function emitFilterFieldGuard() {
846
+ return [
847
+ "/** Runtime guard: narrows `value` to one of the leaf's `filterFields` (a `readonly K[] as const`), so a dynamic field string can be passed to `.where()` without a cast. */",
848
+ "export function isFilterField<const K extends string>(",
849
+ " fields: readonly K[],",
850
+ " value: string,",
851
+ "): value is K {",
852
+ " return (fields as readonly string[]).includes(value);",
853
+ "}",
854
+ ""
855
+ ];
856
+ }
835
857
  function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
836
858
  const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
837
859
  lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
860
+ if (req.filterFieldsExpr) {
861
+ lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
862
+ }
838
863
  if (streamExpr) {
839
864
  lines.push(`${pad} stream: () => ${streamExpr},`);
840
865
  }
@@ -1106,6 +1131,9 @@ function buildApiFile(routes, outDir, opts = {}) {
1106
1131
  lines.push("};");
1107
1132
  lines.push("");
1108
1133
  lines.push(...emitReqHelper());
1134
+ if (contracted.some((r) => r.contract?.contractSource.filterFields?.length)) {
1135
+ lines.push(...emitFilterFieldGuard());
1136
+ }
1109
1137
  lines.push("export function createApi(fetcher: Fetcher) {");
1110
1138
  lines.push(" return {");
1111
1139
  lines.push(
@@ -2206,8 +2234,8 @@ import chokidar from "chokidar";
2206
2234
  import { join as join13, resolve as resolve3 } from "path";
2207
2235
  import fg2 from "fast-glob";
2208
2236
  import {
2209
- Node as Node8,
2210
- Project as Project3
2237
+ Node as Node9,
2238
+ Project as Project4
2211
2239
  } from "ts-morph";
2212
2240
 
2213
2241
  // src/discovery/dto-type-resolver.ts
@@ -3199,6 +3227,12 @@ function toFilterFieldType(name, r) {
3199
3227
  }
3200
3228
 
3201
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
+ }
3202
3236
  function classifyFilterForHint(typeInit) {
3203
3237
  if (Node5.isStringLiteral(typeInit)) {
3204
3238
  switch (typeInit.getLiteralValue()) {
@@ -3272,7 +3306,9 @@ function extractFilterForHints(classDecl, project) {
3272
3306
  }
3273
3307
  return hints;
3274
3308
  }
3275
- function extractApplyFilterInfo(method, sourceFile, project) {
3309
+ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3310
+ const declFile = method.getSourceFile();
3311
+ const mixinEntity = resolveMixinEntityClass(mixin, project);
3276
3312
  for (const param of method.getParameters()) {
3277
3313
  const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
3278
3314
  if (!filterDecorator) continue;
@@ -3292,12 +3328,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
3292
3328
  }
3293
3329
  }
3294
3330
  const filterClassName = filterClassArg.getText();
3295
- const resolved = findType(filterClassName, sourceFile, project);
3296
- if (resolved && resolved.kind === "class") {
3297
- const classDecl = resolved.decl;
3331
+ const resolved = findType(filterClassName, declFile, project);
3332
+ const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
3333
+ if (classDecl) {
3298
3334
  let fieldTypes = extractClassPropertyTypes(classDecl, project);
3299
3335
  if (fieldTypes.length === 0) {
3300
- fieldTypes = extractFilterableEntityFields(classDecl, project);
3336
+ fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
3301
3337
  }
3302
3338
  const filterForHints = extractFilterForHints(classDecl, project);
3303
3339
  if (filterForHints.size > 0) {
@@ -3382,22 +3418,29 @@ function extractClassPropertyTypes(classDecl, project) {
3382
3418
  }
3383
3419
  return fields;
3384
3420
  }
3385
- 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) {
3386
3436
  const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
3387
3437
  if (!filterableDecorator) return [];
3388
3438
  const args = filterableDecorator.getArguments();
3389
3439
  if (args.length === 0) return [];
3390
3440
  const optionsArg = args[0];
3391
3441
  if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
3392
- const entityProp = optionsArg.getProperty("entity");
3393
- if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return [];
3394
- const entityInit = entityProp.getInitializer();
3395
- if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
3396
- const entityName = entityInit.getText();
3397
- const filterSourceFile = filterClass.getSourceFile();
3398
- const resolvedEntity = findType(entityName, filterSourceFile, project);
3399
- if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
3400
- const entityDecl = resolvedEntity.decl;
3442
+ const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
3443
+ if (!entityDecl) return [];
3401
3444
  const fields = collectEntityFields(
3402
3445
  entityDecl,
3403
3446
  entityDecl.getSourceFile(),
@@ -3822,9 +3865,9 @@ function unwrapNamedContainer(node, names) {
3822
3865
  }
3823
3866
  return node;
3824
3867
  }
3825
- function extractDtoContract(method, sourceFile, project) {
3868
+ function extractDtoContract(method, sourceFile, project, mixin) {
3826
3869
  let body = extractBodyType(method, sourceFile, project);
3827
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
3870
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
3828
3871
  const query = extractQueryType(method, sourceFile, project);
3829
3872
  const uploads = extractUploadedFiles(method);
3830
3873
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -3934,12 +3977,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
3934
3977
  return null;
3935
3978
  }
3936
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
+
3937
4062
  // src/discovery/zod-ast-to-ts.ts
3938
- import { Node as Node7, SyntaxKind as SyntaxKind4 } from "ts-morph";
4063
+ import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
3939
4064
  function zodAstToTs(node) {
3940
- if (!Node7.isCallExpression(node)) return "unknown";
4065
+ if (!Node8.isCallExpression(node)) return "unknown";
3941
4066
  const expr = node.getExpression();
3942
- if (Node7.isPropertyAccessExpression(expr)) {
4067
+ if (Node8.isPropertyAccessExpression(expr)) {
3943
4068
  const methodName = expr.getName();
3944
4069
  const receiver = expr.getExpression();
3945
4070
  if (methodName === "optional") {
@@ -3963,17 +4088,17 @@ function zodAstToTs(node) {
3963
4088
  case "literal": {
3964
4089
  const lit = args[0];
3965
4090
  if (!lit) return "unknown";
3966
- if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
3967
- 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();
3968
4093
  if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
3969
4094
  if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
3970
4095
  return "unknown";
3971
4096
  }
3972
4097
  case "enum": {
3973
4098
  const arrArg = args[0];
3974
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4099
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
3975
4100
  const members = arrArg.getElements().map(
3976
- (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4101
+ (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
3977
4102
  );
3978
4103
  return members.join(" | ");
3979
4104
  }
@@ -3984,10 +4109,10 @@ function zodAstToTs(node) {
3984
4109
  }
3985
4110
  case "object": {
3986
4111
  const objArg = args[0];
3987
- if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
4112
+ if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
3988
4113
  const lines = [];
3989
4114
  for (const prop of objArg.getProperties()) {
3990
- if (!Node7.isPropertyAssignment(prop)) continue;
4115
+ if (!Node8.isPropertyAssignment(prop)) continue;
3991
4116
  const key = prop.getName();
3992
4117
  const valNode = prop.getInitializer();
3993
4118
  if (!valNode) continue;
@@ -3999,7 +4124,7 @@ function zodAstToTs(node) {
3999
4124
  }
4000
4125
  case "union": {
4001
4126
  const arrArg = args[0];
4002
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4127
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4003
4128
  return arrArg.getElements().map(zodAstToTs).join(" | ");
4004
4129
  }
4005
4130
  case "record": {
@@ -4009,7 +4134,7 @@ function zodAstToTs(node) {
4009
4134
  }
4010
4135
  case "tuple": {
4011
4136
  const arrArg = args[0];
4012
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4137
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4013
4138
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
4014
4139
  }
4015
4140
  default:
@@ -4019,18 +4144,18 @@ function zodAstToTs(node) {
4019
4144
  return "unknown";
4020
4145
  }
4021
4146
  function isOptionalChain(node) {
4022
- if (!Node7.isCallExpression(node)) return false;
4147
+ if (!Node8.isCallExpression(node)) return false;
4023
4148
  const expr = node.getExpression();
4024
- return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4149
+ return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4025
4150
  }
4026
4151
  function parseDefineContractCall(callExpr) {
4027
- if (!Node7.isCallExpression(callExpr)) return null;
4152
+ if (!Node8.isCallExpression(callExpr)) return null;
4028
4153
  const callee = callExpr.getExpression();
4029
- 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() : "";
4030
4155
  if (calleeName !== "defineContract") return null;
4031
4156
  const args = callExpr.getArguments();
4032
4157
  const optsArg = args[0];
4033
- if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
4158
+ if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
4034
4159
  let query = null;
4035
4160
  let body = null;
4036
4161
  let response = "unknown";
@@ -4038,7 +4163,7 @@ function parseDefineContractCall(callExpr) {
4038
4163
  let bodyZodText = null;
4039
4164
  let queryZodText = null;
4040
4165
  for (const prop of optsArg.getProperties()) {
4041
- if (!Node7.isPropertyAssignment(prop)) continue;
4166
+ if (!Node8.isPropertyAssignment(prop)) continue;
4042
4167
  const propName = prop.getName();
4043
4168
  const val = prop.getInitializer();
4044
4169
  if (!val) continue;
@@ -4063,14 +4188,14 @@ function resolveTsconfigPath(cwd, tsconfig) {
4063
4188
  }
4064
4189
  function createDiscoveryProject(tsconfigPath) {
4065
4190
  try {
4066
- return new Project3({
4191
+ return new Project4({
4067
4192
  tsConfigFilePath: tsconfigPath,
4068
4193
  skipAddingFilesFromTsConfig: true,
4069
4194
  skipLoadingLibFiles: true,
4070
4195
  skipFileDependencyResolution: true
4071
4196
  });
4072
4197
  } catch {
4073
- return new Project3({
4198
+ return new Project4({
4074
4199
  skipAddingFilesFromTsConfig: true,
4075
4200
  skipLoadingLibFiles: true,
4076
4201
  skipFileDependencyResolution: true,
@@ -4170,15 +4295,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
4170
4295
  runExtraction() {
4171
4296
  clearTypeResolutionCaches(this.project);
4172
4297
  clearEnumCache(this.project);
4298
+ clearMixinTypeProject();
4173
4299
  return extractRoutesFrom(this.project, this.controllerPaths);
4174
4300
  }
4175
4301
  };
4176
4302
  function decoratorStringArg(decoratorExpr) {
4177
4303
  if (!decoratorExpr) return void 0;
4178
- if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4179
- if (Node8.isArrayLiteralExpression(decoratorExpr)) {
4304
+ if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4305
+ if (Node9.isArrayLiteralExpression(decoratorExpr)) {
4180
4306
  const first = decoratorExpr.getElements()[0];
4181
- if (first && Node8.isStringLiteral(first)) return first.getLiteralValue();
4307
+ if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
4182
4308
  }
4183
4309
  return void 0;
4184
4310
  }
@@ -4200,7 +4326,8 @@ function joinPaths(prefix, suffix) {
4200
4326
  if (!prefix && !suffix) return "/";
4201
4327
  if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
4202
4328
  if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
4203
- 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;
4204
4331
  const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
4205
4332
  const combined = p + s;
4206
4333
  return combined === "" ? "/" : combined;
@@ -4254,7 +4381,8 @@ function buildRoute(args) {
4254
4381
  methodAs,
4255
4382
  sourceFile,
4256
4383
  seenNames,
4257
- contractSource
4384
+ contractSource,
4385
+ mixin
4258
4386
  } = args;
4259
4387
  const routeName = resolveRouteName(className, methodName, classAs, methodAs);
4260
4388
  const qualifiedRef = `${className}.${methodName}`;
@@ -4270,7 +4398,12 @@ function buildRoute(args) {
4270
4398
  path: combinedPath,
4271
4399
  name: routeName,
4272
4400
  params: extractParams(combinedPath),
4273
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
4401
+ controllerRef: {
4402
+ className,
4403
+ methodName,
4404
+ filePath: sourceFile.getFilePath(),
4405
+ ...mixin ? { mixin } : {}
4406
+ },
4274
4407
  contract: { contractSource }
4275
4408
  };
4276
4409
  }
@@ -4284,16 +4417,17 @@ function extractContractRoute(args) {
4284
4417
  className,
4285
4418
  sourceFile,
4286
4419
  project,
4287
- seenNames
4420
+ seenNames,
4421
+ mixin
4288
4422
  } = args;
4289
4423
  const firstDecoratorArg = applyContractDecorator.getArguments()[0];
4290
4424
  if (!firstDecoratorArg) return null;
4291
4425
  let contractDef = null;
4292
4426
  let bodyZodRef = null;
4293
4427
  let queryZodRef = null;
4294
- if (Node8.isCallExpression(firstDecoratorArg)) {
4428
+ if (Node9.isCallExpression(firstDecoratorArg)) {
4295
4429
  contractDef = parseDefineContractCall(firstDecoratorArg);
4296
- } else if (Node8.isIdentifier(firstDecoratorArg)) {
4430
+ } else if (Node9.isIdentifier(firstDecoratorArg)) {
4297
4431
  const identName = firstDecoratorArg.getText();
4298
4432
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
4299
4433
  if (!resolvedVar) {
@@ -4336,6 +4470,7 @@ function extractContractRoute(args) {
4336
4470
  methodAs,
4337
4471
  sourceFile,
4338
4472
  seenNames,
4473
+ mixin,
4339
4474
  contractSource: {
4340
4475
  query: contractDef.query,
4341
4476
  body: contractDef.body,
@@ -4352,13 +4487,14 @@ function extractContractRoute(args) {
4352
4487
  });
4353
4488
  }
4354
4489
  function extractDtoRoute(args) {
4355
- const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
4490
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
4356
4491
  if (!verb) return null;
4357
4492
  const combined = joinPaths(prefix, verb.handlerPath);
4358
4493
  const methodName = method.getName();
4359
4494
  const classAs = readAsDecorator(cls, `class ${className}`);
4360
4495
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4361
- const dtoContract = extractDtoContract(method, sourceFile, project);
4496
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4497
+ const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4362
4498
  return buildRoute({
4363
4499
  className,
4364
4500
  methodName,
@@ -4368,10 +4504,11 @@ function extractDtoRoute(args) {
4368
4504
  methodAs,
4369
4505
  sourceFile,
4370
4506
  seenNames,
4507
+ mixin,
4371
4508
  contractSource: {
4372
4509
  query: dtoContract?.query ?? null,
4373
4510
  body: dtoContract?.body ?? null,
4374
- response: dtoContract?.response ?? "unknown",
4511
+ response: mixinResponse ?? dtoContract?.response ?? "unknown",
4375
4512
  error: dtoContract?.error ?? null,
4376
4513
  queryRef: dtoContract?.queryRef ?? null,
4377
4514
  bodyRef: dtoContract?.bodyRef ?? null,
@@ -4400,7 +4537,17 @@ function extractFromSourceFile(sourceFile, project) {
4400
4537
  const firstArg2 = controllerDecorator.getArguments()[0];
4401
4538
  const prefix = decoratorStringArg(firstArg2) ?? "";
4402
4539
  const className = cls.getName() ?? "Unknown";
4403
- 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) {
4404
4551
  const verb = resolveVerb(method);
4405
4552
  const applyContractDecorator = method.getDecorator("ApplyContract");
4406
4553
  const route = applyContractDecorator ? extractContractRoute({
@@ -4412,7 +4559,8 @@ function extractFromSourceFile(sourceFile, project) {
4412
4559
  className,
4413
4560
  sourceFile,
4414
4561
  project,
4415
- seenNames
4562
+ seenNames,
4563
+ mixin
4416
4564
  }) : extractDtoRoute({
4417
4565
  cls,
4418
4566
  method,
@@ -4421,7 +4569,8 @@ function extractFromSourceFile(sourceFile, project) {
4421
4569
  className,
4422
4570
  sourceFile,
4423
4571
  project,
4424
- seenNames
4572
+ seenNames,
4573
+ mixin
4425
4574
  });
4426
4575
  if (route) routes.push(route);
4427
4576
  }
@@ -4622,7 +4771,7 @@ async function watch(config, onChange, options = {}) {
4622
4771
  }
4623
4772
 
4624
4773
  // src/index.ts
4625
- var VERSION = "0.14.2";
4774
+ var VERSION = "0.16.0";
4626
4775
 
4627
4776
  // src/generate-manifest.ts
4628
4777
  var MANIFEST_FILE = ".codegen-manifest.json";
@@ -4822,10 +4971,26 @@ var NestjsCodegenModule = class {
4822
4971
  NestjsCodegenModule = __decorateClass([
4823
4972
  Module({})
4824
4973
  ], NestjsCodegenModule);
4974
+
4975
+ // src/nest/query-list.ts
4976
+ import { createParamDecorator } from "@nestjs/common";
4977
+ function toStringList(raw) {
4978
+ if (raw === void 0 || raw === null) return [];
4979
+ const arr = Array.isArray(raw) ? raw : String(raw).split(",");
4980
+ return arr.map((entry) => String(entry).trim()).filter((entry) => entry.length > 0);
4981
+ }
4982
+ function resolveQueryList(key, ctx) {
4983
+ const request = ctx.switchToHttp().getRequest();
4984
+ return toStringList(key ? request.query?.[key] : void 0);
4985
+ }
4986
+ var QueryList = createParamDecorator(resolveQueryList);
4825
4987
  export {
4826
4988
  CODEGEN_MODULE_OPTIONS,
4827
4989
  NestjsCodegenModule,
4828
4990
  NestjsCodegenService,
4829
- shouldRun
4991
+ QueryList,
4992
+ resolveQueryList,
4993
+ shouldRun,
4994
+ toStringList
4830
4995
  };
4831
4996
  //# sourceMappingURL=index.js.map