@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.
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-DT8SgPxp.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-DT8SgPxp.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.14.2";
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-DT8SgPxp.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-DT8SgPxp.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.14.2";
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
@@ -746,6 +746,9 @@ function buildErrorType(c) {
746
746
  }
747
747
  return c.contractSource.error ?? "unknown";
748
748
  }
749
+ function filterFieldLiterals(fields) {
750
+ return fields?.length ? fields.map((f) => JSON.stringify(f)) : [];
751
+ }
749
752
  function emitRouterTypeBlock(tree, indent, outDir, serialization) {
750
753
  const pad = " ".repeat(indent);
751
754
  const lines = [];
@@ -772,7 +775,8 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
772
775
  const params = buildParamsType(c.params);
773
776
  const safeMethod = JSON.stringify(method);
774
777
  const safeUrl = JSON.stringify(c.path);
775
- const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
778
+ const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
779
+ const filterFields = filterLiterals.length ? filterLiterals.join(" | ") : "never";
776
780
  const stream = c.contractSource.stream ? "true" : "false";
777
781
  const binary = c.contractSource.binaryResponse ? "true" : "false";
778
782
  lines.push(
@@ -812,6 +816,7 @@ function buildRequestModel(c) {
812
816
  const TA = buildRouterTypeAccess(c.name);
813
817
  const withParams = hasPathParams(c.params);
814
818
  const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
819
+ const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
815
820
  const fields = [];
816
821
  if (withParams) fields.push(`params: ${TA}['params']`);
817
822
  if (hasQuery) fields.push(`query?: ${TA}['query']`);
@@ -841,7 +846,12 @@ function buildRequestModel(c) {
841
846
  // (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
842
847
  // clean prefix that partial-matches every parametrized variant — making it
843
848
  // directly usable for `invalidateQueries`.
844
- queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`
849
+ queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`,
850
+ // Runtime counterpart to the type-level `filterFields` union: the same
851
+ // discovered field list, emitted as a literal `[...] as const` so apps can
852
+ // validate a dynamic/user-supplied field string with `isFilterField(...)`
853
+ // instead of casting. Omitted for routes with no filter.
854
+ ...filterLiterals.length ? { filterFieldsExpr: `[${filterLiterals.join(", ")}] as const` } : {}
845
855
  };
846
856
  }
847
857
  function renderFetcherRequest(req, binaryResponse) {
@@ -876,9 +886,24 @@ function emitReqHelper() {
876
886
  ""
877
887
  ];
878
888
  }
889
+ function emitFilterFieldGuard() {
890
+ return [
891
+ "/** 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. */",
892
+ "export function isFilterField<const K extends string>(",
893
+ " fields: readonly K[],",
894
+ " value: string,",
895
+ "): value is K {",
896
+ " return (fields as readonly string[]).includes(value);",
897
+ "}",
898
+ ""
899
+ ];
900
+ }
879
901
  function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
880
902
  const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
881
903
  lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
904
+ if (req.filterFieldsExpr) {
905
+ lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
906
+ }
882
907
  if (streamExpr) {
883
908
  lines.push(`${pad} stream: () => ${streamExpr},`);
884
909
  }
@@ -1150,6 +1175,9 @@ function buildApiFile(routes, outDir, opts = {}) {
1150
1175
  lines.push("};");
1151
1176
  lines.push("");
1152
1177
  lines.push(...emitReqHelper());
1178
+ if (contracted.some((r) => r.contract?.contractSource.filterFields?.length)) {
1179
+ lines.push(...emitFilterFieldGuard());
1180
+ }
1153
1181
  lines.push("export function createApi(fetcher: Fetcher) {");
1154
1182
  lines.push(" return {");
1155
1183
  lines.push(
@@ -2389,8 +2417,8 @@ import chokidar from "chokidar";
2389
2417
  import { join as join14, resolve as resolve3 } from "path";
2390
2418
  import fg3 from "fast-glob";
2391
2419
  import {
2392
- Node as Node8,
2393
- Project as Project3
2420
+ Node as Node9,
2421
+ Project as Project4
2394
2422
  } from "ts-morph";
2395
2423
 
2396
2424
  // src/discovery/dto-type-resolver.ts
@@ -3382,6 +3410,12 @@ function toFilterFieldType(name, r) {
3382
3410
  }
3383
3411
 
3384
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
+ }
3385
3419
  function classifyFilterForHint(typeInit) {
3386
3420
  if (Node5.isStringLiteral(typeInit)) {
3387
3421
  switch (typeInit.getLiteralValue()) {
@@ -3455,7 +3489,9 @@ function extractFilterForHints(classDecl, project) {
3455
3489
  }
3456
3490
  return hints;
3457
3491
  }
3458
- function extractApplyFilterInfo(method, sourceFile, project) {
3492
+ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3493
+ const declFile = method.getSourceFile();
3494
+ const mixinEntity = resolveMixinEntityClass(mixin, project);
3459
3495
  for (const param of method.getParameters()) {
3460
3496
  const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
3461
3497
  if (!filterDecorator) continue;
@@ -3475,12 +3511,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
3475
3511
  }
3476
3512
  }
3477
3513
  const filterClassName = filterClassArg.getText();
3478
- const resolved = findType(filterClassName, sourceFile, project);
3479
- if (resolved && resolved.kind === "class") {
3480
- const classDecl = resolved.decl;
3514
+ const resolved = findType(filterClassName, declFile, project);
3515
+ const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
3516
+ if (classDecl) {
3481
3517
  let fieldTypes = extractClassPropertyTypes(classDecl, project);
3482
3518
  if (fieldTypes.length === 0) {
3483
- fieldTypes = extractFilterableEntityFields(classDecl, project);
3519
+ fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
3484
3520
  }
3485
3521
  const filterForHints = extractFilterForHints(classDecl, project);
3486
3522
  if (filterForHints.size > 0) {
@@ -3565,22 +3601,29 @@ function extractClassPropertyTypes(classDecl, project) {
3565
3601
  }
3566
3602
  return fields;
3567
3603
  }
3568
- 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) {
3569
3619
  const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
3570
3620
  if (!filterableDecorator) return [];
3571
3621
  const args = filterableDecorator.getArguments();
3572
3622
  if (args.length === 0) return [];
3573
3623
  const optionsArg = args[0];
3574
3624
  if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
3575
- const entityProp = optionsArg.getProperty("entity");
3576
- if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return [];
3577
- const entityInit = entityProp.getInitializer();
3578
- if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
3579
- const entityName = entityInit.getText();
3580
- const filterSourceFile = filterClass.getSourceFile();
3581
- const resolvedEntity = findType(entityName, filterSourceFile, project);
3582
- if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
3583
- const entityDecl = resolvedEntity.decl;
3625
+ const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
3626
+ if (!entityDecl) return [];
3584
3627
  const fields = collectEntityFields(
3585
3628
  entityDecl,
3586
3629
  entityDecl.getSourceFile(),
@@ -4005,9 +4048,9 @@ function unwrapNamedContainer(node, names) {
4005
4048
  }
4006
4049
  return node;
4007
4050
  }
4008
- function extractDtoContract(method, sourceFile, project) {
4051
+ function extractDtoContract(method, sourceFile, project, mixin) {
4009
4052
  let body = extractBodyType(method, sourceFile, project);
4010
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
4053
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
4011
4054
  const query = extractQueryType(method, sourceFile, project);
4012
4055
  const uploads = extractUploadedFiles(method);
4013
4056
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -4117,12 +4160,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
4117
4160
  return null;
4118
4161
  }
4119
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
+
4120
4245
  // src/discovery/zod-ast-to-ts.ts
4121
- import { Node as Node7, SyntaxKind as SyntaxKind4 } from "ts-morph";
4246
+ import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
4122
4247
  function zodAstToTs(node) {
4123
- if (!Node7.isCallExpression(node)) return "unknown";
4248
+ if (!Node8.isCallExpression(node)) return "unknown";
4124
4249
  const expr = node.getExpression();
4125
- if (Node7.isPropertyAccessExpression(expr)) {
4250
+ if (Node8.isPropertyAccessExpression(expr)) {
4126
4251
  const methodName = expr.getName();
4127
4252
  const receiver = expr.getExpression();
4128
4253
  if (methodName === "optional") {
@@ -4146,17 +4271,17 @@ function zodAstToTs(node) {
4146
4271
  case "literal": {
4147
4272
  const lit = args[0];
4148
4273
  if (!lit) return "unknown";
4149
- if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
4150
- 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();
4151
4276
  if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
4152
4277
  if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
4153
4278
  return "unknown";
4154
4279
  }
4155
4280
  case "enum": {
4156
4281
  const arrArg = args[0];
4157
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4282
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4158
4283
  const members = arrArg.getElements().map(
4159
- (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4284
+ (el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
4160
4285
  );
4161
4286
  return members.join(" | ");
4162
4287
  }
@@ -4167,10 +4292,10 @@ function zodAstToTs(node) {
4167
4292
  }
4168
4293
  case "object": {
4169
4294
  const objArg = args[0];
4170
- if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
4295
+ if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
4171
4296
  const lines = [];
4172
4297
  for (const prop of objArg.getProperties()) {
4173
- if (!Node7.isPropertyAssignment(prop)) continue;
4298
+ if (!Node8.isPropertyAssignment(prop)) continue;
4174
4299
  const key = prop.getName();
4175
4300
  const valNode = prop.getInitializer();
4176
4301
  if (!valNode) continue;
@@ -4182,7 +4307,7 @@ function zodAstToTs(node) {
4182
4307
  }
4183
4308
  case "union": {
4184
4309
  const arrArg = args[0];
4185
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4310
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4186
4311
  return arrArg.getElements().map(zodAstToTs).join(" | ");
4187
4312
  }
4188
4313
  case "record": {
@@ -4192,7 +4317,7 @@ function zodAstToTs(node) {
4192
4317
  }
4193
4318
  case "tuple": {
4194
4319
  const arrArg = args[0];
4195
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
4320
+ if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
4196
4321
  return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
4197
4322
  }
4198
4323
  default:
@@ -4202,18 +4327,18 @@ function zodAstToTs(node) {
4202
4327
  return "unknown";
4203
4328
  }
4204
4329
  function isOptionalChain(node) {
4205
- if (!Node7.isCallExpression(node)) return false;
4330
+ if (!Node8.isCallExpression(node)) return false;
4206
4331
  const expr = node.getExpression();
4207
- return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4332
+ return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
4208
4333
  }
4209
4334
  function parseDefineContractCall(callExpr) {
4210
- if (!Node7.isCallExpression(callExpr)) return null;
4335
+ if (!Node8.isCallExpression(callExpr)) return null;
4211
4336
  const callee = callExpr.getExpression();
4212
- 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() : "";
4213
4338
  if (calleeName !== "defineContract") return null;
4214
4339
  const args = callExpr.getArguments();
4215
4340
  const optsArg = args[0];
4216
- if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
4341
+ if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
4217
4342
  let query = null;
4218
4343
  let body = null;
4219
4344
  let response = "unknown";
@@ -4221,7 +4346,7 @@ function parseDefineContractCall(callExpr) {
4221
4346
  let bodyZodText = null;
4222
4347
  let queryZodText = null;
4223
4348
  for (const prop of optsArg.getProperties()) {
4224
- if (!Node7.isPropertyAssignment(prop)) continue;
4349
+ if (!Node8.isPropertyAssignment(prop)) continue;
4225
4350
  const propName = prop.getName();
4226
4351
  const val = prop.getInitializer();
4227
4352
  if (!val) continue;
@@ -4250,6 +4375,7 @@ async function discoverContractsFast(opts) {
4250
4375
  project.addSourceFileAtPath(f);
4251
4376
  }
4252
4377
  bindDiscoveryContext(project, cwd, tsconfigPath);
4378
+ clearMixinTypeProject();
4253
4379
  return extractAllRoutes(project);
4254
4380
  }
4255
4381
  function resolveTsconfigPath(cwd, tsconfig) {
@@ -4257,14 +4383,14 @@ function resolveTsconfigPath(cwd, tsconfig) {
4257
4383
  }
4258
4384
  function createDiscoveryProject(tsconfigPath) {
4259
4385
  try {
4260
- return new Project3({
4386
+ return new Project4({
4261
4387
  tsConfigFilePath: tsconfigPath,
4262
4388
  skipAddingFilesFromTsConfig: true,
4263
4389
  skipLoadingLibFiles: true,
4264
4390
  skipFileDependencyResolution: true
4265
4391
  });
4266
4392
  } catch {
4267
- return new Project3({
4393
+ return new Project4({
4268
4394
  skipAddingFilesFromTsConfig: true,
4269
4395
  skipLoadingLibFiles: true,
4270
4396
  skipFileDependencyResolution: true,
@@ -4371,15 +4497,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
4371
4497
  runExtraction() {
4372
4498
  clearTypeResolutionCaches(this.project);
4373
4499
  clearEnumCache(this.project);
4500
+ clearMixinTypeProject();
4374
4501
  return extractRoutesFrom(this.project, this.controllerPaths);
4375
4502
  }
4376
4503
  };
4377
4504
  function decoratorStringArg(decoratorExpr) {
4378
4505
  if (!decoratorExpr) return void 0;
4379
- if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4380
- if (Node8.isArrayLiteralExpression(decoratorExpr)) {
4506
+ if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
4507
+ if (Node9.isArrayLiteralExpression(decoratorExpr)) {
4381
4508
  const first = decoratorExpr.getElements()[0];
4382
- if (first && Node8.isStringLiteral(first)) return first.getLiteralValue();
4509
+ if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
4383
4510
  }
4384
4511
  return void 0;
4385
4512
  }
@@ -4401,7 +4528,8 @@ function joinPaths(prefix, suffix) {
4401
4528
  if (!prefix && !suffix) return "/";
4402
4529
  if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
4403
4530
  if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
4404
- 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;
4405
4533
  const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
4406
4534
  const combined = p + s;
4407
4535
  return combined === "" ? "/" : combined;
@@ -4455,7 +4583,8 @@ function buildRoute(args) {
4455
4583
  methodAs,
4456
4584
  sourceFile,
4457
4585
  seenNames,
4458
- contractSource
4586
+ contractSource,
4587
+ mixin
4459
4588
  } = args;
4460
4589
  const routeName = resolveRouteName(className, methodName, classAs, methodAs);
4461
4590
  const qualifiedRef = `${className}.${methodName}`;
@@ -4471,7 +4600,12 @@ function buildRoute(args) {
4471
4600
  path: combinedPath,
4472
4601
  name: routeName,
4473
4602
  params: extractParams(combinedPath),
4474
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
4603
+ controllerRef: {
4604
+ className,
4605
+ methodName,
4606
+ filePath: sourceFile.getFilePath(),
4607
+ ...mixin ? { mixin } : {}
4608
+ },
4475
4609
  contract: { contractSource }
4476
4610
  };
4477
4611
  }
@@ -4485,16 +4619,17 @@ function extractContractRoute(args) {
4485
4619
  className,
4486
4620
  sourceFile,
4487
4621
  project,
4488
- seenNames
4622
+ seenNames,
4623
+ mixin
4489
4624
  } = args;
4490
4625
  const firstDecoratorArg = applyContractDecorator.getArguments()[0];
4491
4626
  if (!firstDecoratorArg) return null;
4492
4627
  let contractDef = null;
4493
4628
  let bodyZodRef = null;
4494
4629
  let queryZodRef = null;
4495
- if (Node8.isCallExpression(firstDecoratorArg)) {
4630
+ if (Node9.isCallExpression(firstDecoratorArg)) {
4496
4631
  contractDef = parseDefineContractCall(firstDecoratorArg);
4497
- } else if (Node8.isIdentifier(firstDecoratorArg)) {
4632
+ } else if (Node9.isIdentifier(firstDecoratorArg)) {
4498
4633
  const identName = firstDecoratorArg.getText();
4499
4634
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
4500
4635
  if (!resolvedVar) {
@@ -4537,6 +4672,7 @@ function extractContractRoute(args) {
4537
4672
  methodAs,
4538
4673
  sourceFile,
4539
4674
  seenNames,
4675
+ mixin,
4540
4676
  contractSource: {
4541
4677
  query: contractDef.query,
4542
4678
  body: contractDef.body,
@@ -4553,13 +4689,14 @@ function extractContractRoute(args) {
4553
4689
  });
4554
4690
  }
4555
4691
  function extractDtoRoute(args) {
4556
- const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
4692
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
4557
4693
  if (!verb) return null;
4558
4694
  const combined = joinPaths(prefix, verb.handlerPath);
4559
4695
  const methodName = method.getName();
4560
4696
  const classAs = readAsDecorator(cls, `class ${className}`);
4561
4697
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4562
- const dtoContract = extractDtoContract(method, sourceFile, project);
4698
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4699
+ const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4563
4700
  return buildRoute({
4564
4701
  className,
4565
4702
  methodName,
@@ -4569,10 +4706,11 @@ function extractDtoRoute(args) {
4569
4706
  methodAs,
4570
4707
  sourceFile,
4571
4708
  seenNames,
4709
+ mixin,
4572
4710
  contractSource: {
4573
4711
  query: dtoContract?.query ?? null,
4574
4712
  body: dtoContract?.body ?? null,
4575
- response: dtoContract?.response ?? "unknown",
4713
+ response: mixinResponse ?? dtoContract?.response ?? "unknown",
4576
4714
  error: dtoContract?.error ?? null,
4577
4715
  queryRef: dtoContract?.queryRef ?? null,
4578
4716
  bodyRef: dtoContract?.bodyRef ?? null,
@@ -4601,7 +4739,17 @@ function extractFromSourceFile(sourceFile, project) {
4601
4739
  const firstArg2 = controllerDecorator.getArguments()[0];
4602
4740
  const prefix = decoratorStringArg(firstArg2) ?? "";
4603
4741
  const className = cls.getName() ?? "Unknown";
4604
- 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) {
4605
4753
  const verb = resolveVerb(method);
4606
4754
  const applyContractDecorator = method.getDecorator("ApplyContract");
4607
4755
  const route = applyContractDecorator ? extractContractRoute({
@@ -4613,7 +4761,8 @@ function extractFromSourceFile(sourceFile, project) {
4613
4761
  className,
4614
4762
  sourceFile,
4615
4763
  project,
4616
- seenNames
4764
+ seenNames,
4765
+ mixin
4617
4766
  }) : extractDtoRoute({
4618
4767
  cls,
4619
4768
  method,
@@ -4622,7 +4771,8 @@ function extractFromSourceFile(sourceFile, project) {
4622
4771
  className,
4623
4772
  sourceFile,
4624
4773
  project,
4625
- seenNames
4774
+ seenNames,
4775
+ mixin
4626
4776
  });
4627
4777
  if (route) routes.push(route);
4628
4778
  }
@@ -4906,7 +5056,7 @@ function createChainModuleRenderer(opts) {
4906
5056
  }
4907
5057
 
4908
5058
  // src/index.ts
4909
- var VERSION = "0.14.2";
5059
+ var VERSION = "0.16.0";
4910
5060
  export {
4911
5061
  CodegenError,
4912
5062
  ConfigError,