@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/CHANGELOG.md +42 -0
- package/dist/cli/main.cjs +177 -55
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +176 -54
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-CjIDPMsV.d.cts → index-LQNP7Ms3.d.cts} +23 -0
- package/dist/{index-CjIDPMsV.d.ts → index-LQNP7Ms3.d.ts} +23 -0
- package/dist/index.cjs +177 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +176 -54
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +176 -55
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +175 -54
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @dudousxd/nestjs-codegen
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Discover routes on mixin (factory-produced) controllers.
|
|
8
|
+
|
|
9
|
+
A `@Controller` class whose heritage clause is a factory call now contributes the
|
|
10
|
+
decorated methods of the class that factory returns:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
@Controller("base/util/search")
|
|
14
|
+
export class SearchUtilController extends createTableController(Util, {
|
|
15
|
+
dto: UtilDTO,
|
|
16
|
+
}) {}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
NestJS already routes inherited methods at runtime via the prototype chain; the
|
|
20
|
+
static discovery pass now follows the same link. Each such route carries a new
|
|
21
|
+
`controllerRef.mixin` binding recording the factory and its call-site class
|
|
22
|
+
arguments — the decorator arguments _inside_ a factory reference its own
|
|
23
|
+
parameters, so the concrete entity is only knowable from the call site.
|
|
24
|
+
|
|
25
|
+
That binding drives two things:
|
|
26
|
+
|
|
27
|
+
- **`filterFields`** are derived from the call-site entity, so
|
|
28
|
+
`@ApplyFilter(GeneratedFilter)` works even though the filter class is declared
|
|
29
|
+
inside the factory and its `@Filterable({ entity })` names a parameter. This is
|
|
30
|
+
also what flips these routes from mutation to query in the emitted client.
|
|
31
|
+
- **Response types** are instantiated against the derived class, so
|
|
32
|
+
`Paginated<D>` resolves to the concrete `D`. This needs the type checker, which
|
|
33
|
+
needs lib files — the discovery Project sets `skipLoadingLibFiles` for
|
|
34
|
+
cold-start speed, so mixin response types resolve through a second Project
|
|
35
|
+
built lazily on the first mixin controller.
|
|
36
|
+
|
|
37
|
+
**Behaviour change:** `joinPaths` now always emits a leading slash.
|
|
38
|
+
`@Controller('items')` + `@Post(':id')` previously produced `items/:id` while
|
|
39
|
+
`@Controller('items')` alone produced `/items` — the prefix+suffix branch was the
|
|
40
|
+
only one that did not add it. The client's `buildUrl()` normalises before
|
|
41
|
+
requesting, so no URL was ever broken by this, but the raw value reaches the
|
|
42
|
+
emitted `ROUTES` map and the OpenAPI export, where a path without a leading slash
|
|
43
|
+
is invalid.
|
|
44
|
+
|
|
3
45
|
## 0.15.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
package/dist/cli/main.cjs
CHANGED
|
@@ -2442,7 +2442,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
2442
2442
|
// src/discovery/contracts-fast.ts
|
|
2443
2443
|
var import_node_path15 = require("path");
|
|
2444
2444
|
var import_fast_glob3 = __toESM(require("fast-glob"), 1);
|
|
2445
|
-
var
|
|
2445
|
+
var import_ts_morph10 = require("ts-morph");
|
|
2446
2446
|
|
|
2447
2447
|
// src/discovery/dto-type-resolver.ts
|
|
2448
2448
|
var import_ts_morph7 = require("ts-morph");
|
|
@@ -3421,6 +3421,12 @@ function toFilterFieldType(name, r) {
|
|
|
3421
3421
|
}
|
|
3422
3422
|
|
|
3423
3423
|
// src/discovery/filter-for.ts
|
|
3424
|
+
function resolveMixinEntityClass(mixin, project) {
|
|
3425
|
+
const entityArg = mixin?.classArgs[0];
|
|
3426
|
+
if (!entityArg) return void 0;
|
|
3427
|
+
const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
|
|
3428
|
+
return file?.getClass(entityArg.name);
|
|
3429
|
+
}
|
|
3424
3430
|
function classifyFilterForHint(typeInit) {
|
|
3425
3431
|
if (import_ts_morph6.Node.isStringLiteral(typeInit)) {
|
|
3426
3432
|
switch (typeInit.getLiteralValue()) {
|
|
@@ -3494,7 +3500,9 @@ function extractFilterForHints(classDecl, project) {
|
|
|
3494
3500
|
}
|
|
3495
3501
|
return hints;
|
|
3496
3502
|
}
|
|
3497
|
-
function extractApplyFilterInfo(method, sourceFile, project) {
|
|
3503
|
+
function extractApplyFilterInfo(method, sourceFile, project, mixin) {
|
|
3504
|
+
const declFile = method.getSourceFile();
|
|
3505
|
+
const mixinEntity = resolveMixinEntityClass(mixin, project);
|
|
3498
3506
|
for (const param of method.getParameters()) {
|
|
3499
3507
|
const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
|
|
3500
3508
|
if (!filterDecorator) continue;
|
|
@@ -3514,12 +3522,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
3514
3522
|
}
|
|
3515
3523
|
}
|
|
3516
3524
|
const filterClassName = filterClassArg.getText();
|
|
3517
|
-
const resolved = findType(filterClassName,
|
|
3518
|
-
|
|
3519
|
-
|
|
3525
|
+
const resolved = findType(filterClassName, declFile, project);
|
|
3526
|
+
const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
|
|
3527
|
+
if (classDecl) {
|
|
3520
3528
|
let fieldTypes = extractClassPropertyTypes(classDecl, project);
|
|
3521
3529
|
if (fieldTypes.length === 0) {
|
|
3522
|
-
fieldTypes = extractFilterableEntityFields(classDecl, project);
|
|
3530
|
+
fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
|
|
3523
3531
|
}
|
|
3524
3532
|
const filterForHints = extractFilterForHints(classDecl, project);
|
|
3525
3533
|
if (filterForHints.size > 0) {
|
|
@@ -3604,22 +3612,29 @@ function extractClassPropertyTypes(classDecl, project) {
|
|
|
3604
3612
|
}
|
|
3605
3613
|
return fields;
|
|
3606
3614
|
}
|
|
3607
|
-
function
|
|
3615
|
+
function resolveLocalClassDeclaration(identifier) {
|
|
3616
|
+
if (!import_ts_morph6.Node.isIdentifier(identifier)) return void 0;
|
|
3617
|
+
return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && import_ts_morph6.Node.isClassDeclaration(n));
|
|
3618
|
+
}
|
|
3619
|
+
function resolveDeclaredEntity(optionsArg, filterClass, project) {
|
|
3620
|
+
if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return void 0;
|
|
3621
|
+
const entityProp = optionsArg.getProperty("entity");
|
|
3622
|
+
if (!entityProp || !import_ts_morph6.Node.isPropertyAssignment(entityProp)) return void 0;
|
|
3623
|
+
const entityInit = entityProp.getInitializer();
|
|
3624
|
+
if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return void 0;
|
|
3625
|
+
const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
|
|
3626
|
+
if (!resolved || resolved.kind !== "class") return void 0;
|
|
3627
|
+
return resolved.decl;
|
|
3628
|
+
}
|
|
3629
|
+
function extractFilterableEntityFields(filterClass, project, entityOverride) {
|
|
3608
3630
|
const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
|
|
3609
3631
|
if (!filterableDecorator) return [];
|
|
3610
3632
|
const args = filterableDecorator.getArguments();
|
|
3611
3633
|
if (args.length === 0) return [];
|
|
3612
3634
|
const optionsArg = args[0];
|
|
3613
3635
|
if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return [];
|
|
3614
|
-
const
|
|
3615
|
-
if (!
|
|
3616
|
-
const entityInit = entityProp.getInitializer();
|
|
3617
|
-
if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return [];
|
|
3618
|
-
const entityName = entityInit.getText();
|
|
3619
|
-
const filterSourceFile = filterClass.getSourceFile();
|
|
3620
|
-
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
3621
|
-
if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
|
|
3622
|
-
const entityDecl = resolvedEntity.decl;
|
|
3636
|
+
const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
|
|
3637
|
+
if (!entityDecl) return [];
|
|
3623
3638
|
const fields = collectEntityFields(
|
|
3624
3639
|
entityDecl,
|
|
3625
3640
|
entityDecl.getSourceFile(),
|
|
@@ -4044,9 +4059,9 @@ function unwrapNamedContainer(node, names) {
|
|
|
4044
4059
|
}
|
|
4045
4060
|
return node;
|
|
4046
4061
|
}
|
|
4047
|
-
function extractDtoContract(method, sourceFile, project) {
|
|
4062
|
+
function extractDtoContract(method, sourceFile, project, mixin) {
|
|
4048
4063
|
let body = extractBodyType(method, sourceFile, project);
|
|
4049
|
-
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
4064
|
+
const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
|
|
4050
4065
|
const query = extractQueryType(method, sourceFile, project);
|
|
4051
4066
|
const uploads = extractUploadedFiles(method);
|
|
4052
4067
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
@@ -4156,12 +4171,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
4156
4171
|
return null;
|
|
4157
4172
|
}
|
|
4158
4173
|
|
|
4159
|
-
// src/discovery/
|
|
4174
|
+
// src/discovery/heritage.ts
|
|
4160
4175
|
var import_ts_morph8 = require("ts-morph");
|
|
4176
|
+
function unwrapExpression(node) {
|
|
4177
|
+
let current = node;
|
|
4178
|
+
while (import_ts_morph8.Node.isAsExpression(current) || import_ts_morph8.Node.isSatisfiesExpression(current) || import_ts_morph8.Node.isParenthesizedExpression(current) || import_ts_morph8.Node.isTypeAssertion(current)) {
|
|
4179
|
+
current = current.getExpression();
|
|
4180
|
+
}
|
|
4181
|
+
return current;
|
|
4182
|
+
}
|
|
4183
|
+
function resolveReturnedClass(factoryDecl) {
|
|
4184
|
+
const body = factoryDecl.getBody();
|
|
4185
|
+
if (!body) return void 0;
|
|
4186
|
+
const returns = body.getDescendants().filter((n) => import_ts_morph8.Node.isReturnStatement(n));
|
|
4187
|
+
for (const ret of returns) {
|
|
4188
|
+
const expr = ret.getExpression();
|
|
4189
|
+
if (!expr) continue;
|
|
4190
|
+
const inner = unwrapExpression(expr);
|
|
4191
|
+
if (import_ts_morph8.Node.isClassExpression(inner)) {
|
|
4192
|
+
return inner;
|
|
4193
|
+
}
|
|
4194
|
+
if (import_ts_morph8.Node.isIdentifier(inner)) {
|
|
4195
|
+
const name = inner.getText();
|
|
4196
|
+
const decl = body.getDescendants().find((n) => import_ts_morph8.Node.isClassDeclaration(n) && n.getName() === name);
|
|
4197
|
+
if (decl) return decl;
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
return void 0;
|
|
4201
|
+
}
|
|
4202
|
+
function resolveInheritedMethods(cls) {
|
|
4203
|
+
const expr = cls.getExtends()?.getExpression();
|
|
4204
|
+
if (!expr || !import_ts_morph8.Node.isCallExpression(expr)) return void 0;
|
|
4205
|
+
const callee = expr.getExpression();
|
|
4206
|
+
if (!import_ts_morph8.Node.isIdentifier(callee)) return void 0;
|
|
4207
|
+
const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => import_ts_morph8.Node.isFunctionDeclaration(n));
|
|
4208
|
+
if (!factoryDecl) return void 0;
|
|
4209
|
+
const returnedClass = resolveReturnedClass(factoryDecl);
|
|
4210
|
+
if (!returnedClass) return void 0;
|
|
4211
|
+
const classArgs = [];
|
|
4212
|
+
for (const arg of expr.getArguments()) {
|
|
4213
|
+
if (!import_ts_morph8.Node.isIdentifier(arg)) continue;
|
|
4214
|
+
const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => import_ts_morph8.Node.isClassDeclaration(n));
|
|
4215
|
+
if (decl) {
|
|
4216
|
+
classArgs.push({
|
|
4217
|
+
name: decl.getName() ?? arg.getText(),
|
|
4218
|
+
filePath: decl.getSourceFile().getFilePath()
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
return {
|
|
4223
|
+
methods: returnedClass.getMethods(),
|
|
4224
|
+
factoryName: callee.getText(),
|
|
4225
|
+
factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
|
|
4226
|
+
classArgs
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
var mixinTypeProject;
|
|
4230
|
+
function getMixinTypeProject() {
|
|
4231
|
+
mixinTypeProject ??= new import_ts_morph8.Project({
|
|
4232
|
+
skipAddingFilesFromTsConfig: true,
|
|
4233
|
+
compilerOptions: { strict: true }
|
|
4234
|
+
});
|
|
4235
|
+
return mixinTypeProject;
|
|
4236
|
+
}
|
|
4237
|
+
function clearMixinTypeProject() {
|
|
4238
|
+
mixinTypeProject = void 0;
|
|
4239
|
+
}
|
|
4240
|
+
function resolveInstantiatedReturnType(cls, methodName) {
|
|
4241
|
+
const filePath = cls.getSourceFile().getFilePath();
|
|
4242
|
+
const className = cls.getName();
|
|
4243
|
+
if (!className) return void 0;
|
|
4244
|
+
const project = getMixinTypeProject();
|
|
4245
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
|
|
4246
|
+
const typedCls = sourceFile?.getClass(className);
|
|
4247
|
+
if (!typedCls) return void 0;
|
|
4248
|
+
const prop = typedCls.getType().getProperty(methodName);
|
|
4249
|
+
if (!prop) return void 0;
|
|
4250
|
+
const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
|
|
4251
|
+
if (!returnType) return void 0;
|
|
4252
|
+
const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
|
|
4253
|
+
return unwrapped.getText(typedCls);
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
4257
|
+
var import_ts_morph9 = require("ts-morph");
|
|
4161
4258
|
function zodAstToTs(node) {
|
|
4162
|
-
if (!
|
|
4259
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
|
|
4163
4260
|
const expr = node.getExpression();
|
|
4164
|
-
if (
|
|
4261
|
+
if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
|
|
4165
4262
|
const methodName = expr.getName();
|
|
4166
4263
|
const receiver = expr.getExpression();
|
|
4167
4264
|
if (methodName === "optional") {
|
|
@@ -4185,17 +4282,17 @@ function zodAstToTs(node) {
|
|
|
4185
4282
|
case "literal": {
|
|
4186
4283
|
const lit = args[0];
|
|
4187
4284
|
if (!lit) return "unknown";
|
|
4188
|
-
if (
|
|
4189
|
-
if (
|
|
4190
|
-
if (lit.getKind() ===
|
|
4191
|
-
if (lit.getKind() ===
|
|
4285
|
+
if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
4286
|
+
if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
4287
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
|
|
4288
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
|
|
4192
4289
|
return "unknown";
|
|
4193
4290
|
}
|
|
4194
4291
|
case "enum": {
|
|
4195
4292
|
const arrArg = args[0];
|
|
4196
|
-
if (!arrArg || !
|
|
4293
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4197
4294
|
const members = arrArg.getElements().map(
|
|
4198
|
-
(el) =>
|
|
4295
|
+
(el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
4199
4296
|
);
|
|
4200
4297
|
return members.join(" | ");
|
|
4201
4298
|
}
|
|
@@ -4206,10 +4303,10 @@ function zodAstToTs(node) {
|
|
|
4206
4303
|
}
|
|
4207
4304
|
case "object": {
|
|
4208
4305
|
const objArg = args[0];
|
|
4209
|
-
if (!objArg || !
|
|
4306
|
+
if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
4210
4307
|
const lines = [];
|
|
4211
4308
|
for (const prop of objArg.getProperties()) {
|
|
4212
|
-
if (!
|
|
4309
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
4213
4310
|
const key = prop.getName();
|
|
4214
4311
|
const valNode = prop.getInitializer();
|
|
4215
4312
|
if (!valNode) continue;
|
|
@@ -4221,7 +4318,7 @@ function zodAstToTs(node) {
|
|
|
4221
4318
|
}
|
|
4222
4319
|
case "union": {
|
|
4223
4320
|
const arrArg = args[0];
|
|
4224
|
-
if (!arrArg || !
|
|
4321
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4225
4322
|
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
4226
4323
|
}
|
|
4227
4324
|
case "record": {
|
|
@@ -4231,7 +4328,7 @@ function zodAstToTs(node) {
|
|
|
4231
4328
|
}
|
|
4232
4329
|
case "tuple": {
|
|
4233
4330
|
const arrArg = args[0];
|
|
4234
|
-
if (!arrArg || !
|
|
4331
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4235
4332
|
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
4236
4333
|
}
|
|
4237
4334
|
default:
|
|
@@ -4241,18 +4338,18 @@ function zodAstToTs(node) {
|
|
|
4241
4338
|
return "unknown";
|
|
4242
4339
|
}
|
|
4243
4340
|
function isOptionalChain(node) {
|
|
4244
|
-
if (!
|
|
4341
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return false;
|
|
4245
4342
|
const expr = node.getExpression();
|
|
4246
|
-
return
|
|
4343
|
+
return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
4247
4344
|
}
|
|
4248
4345
|
function parseDefineContractCall(callExpr) {
|
|
4249
|
-
if (!
|
|
4346
|
+
if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
|
|
4250
4347
|
const callee = callExpr.getExpression();
|
|
4251
|
-
const calleeName =
|
|
4348
|
+
const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
4252
4349
|
if (calleeName !== "defineContract") return null;
|
|
4253
4350
|
const args = callExpr.getArguments();
|
|
4254
4351
|
const optsArg = args[0];
|
|
4255
|
-
if (!optsArg || !
|
|
4352
|
+
if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
4256
4353
|
let query = null;
|
|
4257
4354
|
let body = null;
|
|
4258
4355
|
let response = "unknown";
|
|
@@ -4260,7 +4357,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
4260
4357
|
let bodyZodText = null;
|
|
4261
4358
|
let queryZodText = null;
|
|
4262
4359
|
for (const prop of optsArg.getProperties()) {
|
|
4263
|
-
if (!
|
|
4360
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
4264
4361
|
const propName = prop.getName();
|
|
4265
4362
|
const val = prop.getInitializer();
|
|
4266
4363
|
if (!val) continue;
|
|
@@ -4289,6 +4386,7 @@ async function discoverContractsFast(opts) {
|
|
|
4289
4386
|
project.addSourceFileAtPath(f);
|
|
4290
4387
|
}
|
|
4291
4388
|
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
4389
|
+
clearMixinTypeProject();
|
|
4292
4390
|
return extractAllRoutes(project);
|
|
4293
4391
|
}
|
|
4294
4392
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
@@ -4296,14 +4394,14 @@ function resolveTsconfigPath(cwd, tsconfig) {
|
|
|
4296
4394
|
}
|
|
4297
4395
|
function createDiscoveryProject(tsconfigPath) {
|
|
4298
4396
|
try {
|
|
4299
|
-
return new
|
|
4397
|
+
return new import_ts_morph10.Project({
|
|
4300
4398
|
tsConfigFilePath: tsconfigPath,
|
|
4301
4399
|
skipAddingFilesFromTsConfig: true,
|
|
4302
4400
|
skipLoadingLibFiles: true,
|
|
4303
4401
|
skipFileDependencyResolution: true
|
|
4304
4402
|
});
|
|
4305
4403
|
} catch {
|
|
4306
|
-
return new
|
|
4404
|
+
return new import_ts_morph10.Project({
|
|
4307
4405
|
skipAddingFilesFromTsConfig: true,
|
|
4308
4406
|
skipLoadingLibFiles: true,
|
|
4309
4407
|
skipFileDependencyResolution: true,
|
|
@@ -4410,15 +4508,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4410
4508
|
runExtraction() {
|
|
4411
4509
|
clearTypeResolutionCaches(this.project);
|
|
4412
4510
|
clearEnumCache(this.project);
|
|
4511
|
+
clearMixinTypeProject();
|
|
4413
4512
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
4414
4513
|
}
|
|
4415
4514
|
};
|
|
4416
4515
|
function decoratorStringArg(decoratorExpr) {
|
|
4417
4516
|
if (!decoratorExpr) return void 0;
|
|
4418
|
-
if (
|
|
4419
|
-
if (
|
|
4517
|
+
if (import_ts_morph10.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
4518
|
+
if (import_ts_morph10.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
4420
4519
|
const first = decoratorExpr.getElements()[0];
|
|
4421
|
-
if (first &&
|
|
4520
|
+
if (first && import_ts_morph10.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
4422
4521
|
}
|
|
4423
4522
|
return void 0;
|
|
4424
4523
|
}
|
|
@@ -4440,7 +4539,8 @@ function joinPaths(prefix, suffix) {
|
|
|
4440
4539
|
if (!prefix && !suffix) return "/";
|
|
4441
4540
|
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4442
4541
|
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4443
|
-
const
|
|
4542
|
+
const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4543
|
+
const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
4444
4544
|
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4445
4545
|
const combined = p + s;
|
|
4446
4546
|
return combined === "" ? "/" : combined;
|
|
@@ -4494,7 +4594,8 @@ function buildRoute(args) {
|
|
|
4494
4594
|
methodAs,
|
|
4495
4595
|
sourceFile,
|
|
4496
4596
|
seenNames,
|
|
4497
|
-
contractSource
|
|
4597
|
+
contractSource,
|
|
4598
|
+
mixin
|
|
4498
4599
|
} = args;
|
|
4499
4600
|
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
4500
4601
|
const qualifiedRef = `${className}.${methodName}`;
|
|
@@ -4510,7 +4611,12 @@ function buildRoute(args) {
|
|
|
4510
4611
|
path: combinedPath,
|
|
4511
4612
|
name: routeName,
|
|
4512
4613
|
params: extractParams(combinedPath),
|
|
4513
|
-
controllerRef: {
|
|
4614
|
+
controllerRef: {
|
|
4615
|
+
className,
|
|
4616
|
+
methodName,
|
|
4617
|
+
filePath: sourceFile.getFilePath(),
|
|
4618
|
+
...mixin ? { mixin } : {}
|
|
4619
|
+
},
|
|
4514
4620
|
contract: { contractSource }
|
|
4515
4621
|
};
|
|
4516
4622
|
}
|
|
@@ -4524,16 +4630,17 @@ function extractContractRoute(args) {
|
|
|
4524
4630
|
className,
|
|
4525
4631
|
sourceFile,
|
|
4526
4632
|
project,
|
|
4527
|
-
seenNames
|
|
4633
|
+
seenNames,
|
|
4634
|
+
mixin
|
|
4528
4635
|
} = args;
|
|
4529
4636
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
4530
4637
|
if (!firstDecoratorArg) return null;
|
|
4531
4638
|
let contractDef = null;
|
|
4532
4639
|
let bodyZodRef = null;
|
|
4533
4640
|
let queryZodRef = null;
|
|
4534
|
-
if (
|
|
4641
|
+
if (import_ts_morph10.Node.isCallExpression(firstDecoratorArg)) {
|
|
4535
4642
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
4536
|
-
} else if (
|
|
4643
|
+
} else if (import_ts_morph10.Node.isIdentifier(firstDecoratorArg)) {
|
|
4537
4644
|
const identName = firstDecoratorArg.getText();
|
|
4538
4645
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4539
4646
|
if (!resolvedVar) {
|
|
@@ -4576,6 +4683,7 @@ function extractContractRoute(args) {
|
|
|
4576
4683
|
methodAs,
|
|
4577
4684
|
sourceFile,
|
|
4578
4685
|
seenNames,
|
|
4686
|
+
mixin,
|
|
4579
4687
|
contractSource: {
|
|
4580
4688
|
query: contractDef.query,
|
|
4581
4689
|
body: contractDef.body,
|
|
@@ -4592,13 +4700,14 @@ function extractContractRoute(args) {
|
|
|
4592
4700
|
});
|
|
4593
4701
|
}
|
|
4594
4702
|
function extractDtoRoute(args) {
|
|
4595
|
-
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
4703
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
|
|
4596
4704
|
if (!verb) return null;
|
|
4597
4705
|
const combined = joinPaths(prefix, verb.handlerPath);
|
|
4598
4706
|
const methodName = method.getName();
|
|
4599
4707
|
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
4600
4708
|
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
4601
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
4709
|
+
const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
|
|
4710
|
+
const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
|
|
4602
4711
|
return buildRoute({
|
|
4603
4712
|
className,
|
|
4604
4713
|
methodName,
|
|
@@ -4608,10 +4717,11 @@ function extractDtoRoute(args) {
|
|
|
4608
4717
|
methodAs,
|
|
4609
4718
|
sourceFile,
|
|
4610
4719
|
seenNames,
|
|
4720
|
+
mixin,
|
|
4611
4721
|
contractSource: {
|
|
4612
4722
|
query: dtoContract?.query ?? null,
|
|
4613
4723
|
body: dtoContract?.body ?? null,
|
|
4614
|
-
response: dtoContract?.response ?? "unknown",
|
|
4724
|
+
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
4615
4725
|
error: dtoContract?.error ?? null,
|
|
4616
4726
|
queryRef: dtoContract?.queryRef ?? null,
|
|
4617
4727
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
@@ -4640,7 +4750,17 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4640
4750
|
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
4641
4751
|
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
4642
4752
|
const className = cls.getName() ?? "Unknown";
|
|
4643
|
-
|
|
4753
|
+
const inherited = resolveInheritedMethods(cls);
|
|
4754
|
+
const mixinBinding = inherited ? {
|
|
4755
|
+
factoryName: inherited.factoryName,
|
|
4756
|
+
factoryFilePath: inherited.factoryFilePath,
|
|
4757
|
+
classArgs: inherited.classArgs
|
|
4758
|
+
} : void 0;
|
|
4759
|
+
const methods = [
|
|
4760
|
+
...cls.getMethods().map((method) => ({ method })),
|
|
4761
|
+
...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
|
|
4762
|
+
];
|
|
4763
|
+
for (const { method, mixin } of methods) {
|
|
4644
4764
|
const verb = resolveVerb(method);
|
|
4645
4765
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
4646
4766
|
const route = applyContractDecorator ? extractContractRoute({
|
|
@@ -4652,7 +4772,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4652
4772
|
className,
|
|
4653
4773
|
sourceFile,
|
|
4654
4774
|
project,
|
|
4655
|
-
seenNames
|
|
4775
|
+
seenNames,
|
|
4776
|
+
mixin
|
|
4656
4777
|
}) : extractDtoRoute({
|
|
4657
4778
|
cls,
|
|
4658
4779
|
method,
|
|
@@ -4661,7 +4782,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4661
4782
|
className,
|
|
4662
4783
|
sourceFile,
|
|
4663
4784
|
project,
|
|
4664
|
-
seenNames
|
|
4785
|
+
seenNames,
|
|
4786
|
+
mixin
|
|
4665
4787
|
});
|
|
4666
4788
|
if (route) routes.push(route);
|
|
4667
4789
|
}
|
|
@@ -4862,7 +4984,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4862
4984
|
}
|
|
4863
4985
|
|
|
4864
4986
|
// src/index.ts
|
|
4865
|
-
var VERSION = "0.
|
|
4987
|
+
var VERSION = "0.16.0";
|
|
4866
4988
|
|
|
4867
4989
|
// src/cli/codegen.ts
|
|
4868
4990
|
async function runCodegen(opts = {}) {
|