@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/CHANGELOG.md +49 -0
- package/dist/cli/main.cjs +207 -57
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +206 -56
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-DT8SgPxp.d.cts → index-LQNP7Ms3.d.cts} +30 -0
- package/dist/{index-DT8SgPxp.d.ts → index-LQNP7Ms3.d.ts} +30 -0
- package/dist/index.cjs +207 -57
- 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 +206 -56
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +227 -59
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +56 -3
- package/dist/nest/index.d.ts +56 -3
- package/dist/nest/index.js +222 -57
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/main.js
CHANGED
|
@@ -741,6 +741,9 @@ function buildErrorType(c) {
|
|
|
741
741
|
}
|
|
742
742
|
return c.contractSource.error ?? "unknown";
|
|
743
743
|
}
|
|
744
|
+
function filterFieldLiterals(fields) {
|
|
745
|
+
return fields?.length ? fields.map((f) => JSON.stringify(f)) : [];
|
|
746
|
+
}
|
|
744
747
|
function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
745
748
|
const pad = " ".repeat(indent);
|
|
746
749
|
const lines = [];
|
|
@@ -767,7 +770,8 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
767
770
|
const params = buildParamsType(c.params);
|
|
768
771
|
const safeMethod = JSON.stringify(method);
|
|
769
772
|
const safeUrl = JSON.stringify(c.path);
|
|
770
|
-
const
|
|
773
|
+
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
774
|
+
const filterFields = filterLiterals.length ? filterLiterals.join(" | ") : "never";
|
|
771
775
|
const stream = c.contractSource.stream ? "true" : "false";
|
|
772
776
|
const binary = c.contractSource.binaryResponse ? "true" : "false";
|
|
773
777
|
lines.push(
|
|
@@ -807,6 +811,7 @@ function buildRequestModel(c) {
|
|
|
807
811
|
const TA = buildRouterTypeAccess(c.name);
|
|
808
812
|
const withParams = hasPathParams(c.params);
|
|
809
813
|
const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
|
|
814
|
+
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
810
815
|
const fields = [];
|
|
811
816
|
if (withParams) fields.push(`params: ${TA}['params']`);
|
|
812
817
|
if (hasQuery) fields.push(`query?: ${TA}['query']`);
|
|
@@ -836,7 +841,12 @@ function buildRequestModel(c) {
|
|
|
836
841
|
// (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
|
|
837
842
|
// clean prefix that partial-matches every parametrized variant — making it
|
|
838
843
|
// directly usable for `invalidateQueries`.
|
|
839
|
-
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)
|
|
844
|
+
queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`,
|
|
845
|
+
// Runtime counterpart to the type-level `filterFields` union: the same
|
|
846
|
+
// discovered field list, emitted as a literal `[...] as const` so apps can
|
|
847
|
+
// validate a dynamic/user-supplied field string with `isFilterField(...)`
|
|
848
|
+
// instead of casting. Omitted for routes with no filter.
|
|
849
|
+
...filterLiterals.length ? { filterFieldsExpr: `[${filterLiterals.join(", ")}] as const` } : {}
|
|
840
850
|
};
|
|
841
851
|
}
|
|
842
852
|
function renderFetcherRequest(req, binaryResponse) {
|
|
@@ -871,9 +881,24 @@ function emitReqHelper() {
|
|
|
871
881
|
""
|
|
872
882
|
];
|
|
873
883
|
}
|
|
884
|
+
function emitFilterFieldGuard() {
|
|
885
|
+
return [
|
|
886
|
+
"/** 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. */",
|
|
887
|
+
"export function isFilterField<const K extends string>(",
|
|
888
|
+
" fields: readonly K[],",
|
|
889
|
+
" value: string,",
|
|
890
|
+
"): value is K {",
|
|
891
|
+
" return (fields as readonly string[]).includes(value);",
|
|
892
|
+
"}",
|
|
893
|
+
""
|
|
894
|
+
];
|
|
895
|
+
}
|
|
874
896
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
875
897
|
const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
|
|
876
898
|
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
899
|
+
if (req.filterFieldsExpr) {
|
|
900
|
+
lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
901
|
+
}
|
|
877
902
|
if (streamExpr) {
|
|
878
903
|
lines.push(`${pad} stream: () => ${streamExpr},`);
|
|
879
904
|
}
|
|
@@ -1145,6 +1170,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1145
1170
|
lines.push("};");
|
|
1146
1171
|
lines.push("");
|
|
1147
1172
|
lines.push(...emitReqHelper());
|
|
1173
|
+
if (contracted.some((r) => r.contract?.contractSource.filterFields?.length)) {
|
|
1174
|
+
lines.push(...emitFilterFieldGuard());
|
|
1175
|
+
}
|
|
1148
1176
|
lines.push("export function createApi(fetcher: Fetcher) {");
|
|
1149
1177
|
lines.push(" return {");
|
|
1150
1178
|
lines.push(
|
|
@@ -2381,8 +2409,8 @@ import chokidar from "chokidar";
|
|
|
2381
2409
|
import { join as join14, resolve as resolve3 } from "path";
|
|
2382
2410
|
import fg3 from "fast-glob";
|
|
2383
2411
|
import {
|
|
2384
|
-
Node as
|
|
2385
|
-
Project as
|
|
2412
|
+
Node as Node9,
|
|
2413
|
+
Project as Project4
|
|
2386
2414
|
} from "ts-morph";
|
|
2387
2415
|
|
|
2388
2416
|
// src/discovery/dto-type-resolver.ts
|
|
@@ -3374,6 +3402,12 @@ function toFilterFieldType(name, r) {
|
|
|
3374
3402
|
}
|
|
3375
3403
|
|
|
3376
3404
|
// src/discovery/filter-for.ts
|
|
3405
|
+
function resolveMixinEntityClass(mixin, project) {
|
|
3406
|
+
const entityArg = mixin?.classArgs[0];
|
|
3407
|
+
if (!entityArg) return void 0;
|
|
3408
|
+
const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
|
|
3409
|
+
return file?.getClass(entityArg.name);
|
|
3410
|
+
}
|
|
3377
3411
|
function classifyFilterForHint(typeInit) {
|
|
3378
3412
|
if (Node5.isStringLiteral(typeInit)) {
|
|
3379
3413
|
switch (typeInit.getLiteralValue()) {
|
|
@@ -3447,7 +3481,9 @@ function extractFilterForHints(classDecl, project) {
|
|
|
3447
3481
|
}
|
|
3448
3482
|
return hints;
|
|
3449
3483
|
}
|
|
3450
|
-
function extractApplyFilterInfo(method, sourceFile, project) {
|
|
3484
|
+
function extractApplyFilterInfo(method, sourceFile, project, mixin) {
|
|
3485
|
+
const declFile = method.getSourceFile();
|
|
3486
|
+
const mixinEntity = resolveMixinEntityClass(mixin, project);
|
|
3451
3487
|
for (const param of method.getParameters()) {
|
|
3452
3488
|
const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
|
|
3453
3489
|
if (!filterDecorator) continue;
|
|
@@ -3467,12 +3503,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
3467
3503
|
}
|
|
3468
3504
|
}
|
|
3469
3505
|
const filterClassName = filterClassArg.getText();
|
|
3470
|
-
const resolved = findType(filterClassName,
|
|
3471
|
-
|
|
3472
|
-
|
|
3506
|
+
const resolved = findType(filterClassName, declFile, project);
|
|
3507
|
+
const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
|
|
3508
|
+
if (classDecl) {
|
|
3473
3509
|
let fieldTypes = extractClassPropertyTypes(classDecl, project);
|
|
3474
3510
|
if (fieldTypes.length === 0) {
|
|
3475
|
-
fieldTypes = extractFilterableEntityFields(classDecl, project);
|
|
3511
|
+
fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
|
|
3476
3512
|
}
|
|
3477
3513
|
const filterForHints = extractFilterForHints(classDecl, project);
|
|
3478
3514
|
if (filterForHints.size > 0) {
|
|
@@ -3557,22 +3593,29 @@ function extractClassPropertyTypes(classDecl, project) {
|
|
|
3557
3593
|
}
|
|
3558
3594
|
return fields;
|
|
3559
3595
|
}
|
|
3560
|
-
function
|
|
3596
|
+
function resolveLocalClassDeclaration(identifier) {
|
|
3597
|
+
if (!Node5.isIdentifier(identifier)) return void 0;
|
|
3598
|
+
return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
|
|
3599
|
+
}
|
|
3600
|
+
function resolveDeclaredEntity(optionsArg, filterClass, project) {
|
|
3601
|
+
if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
|
|
3602
|
+
const entityProp = optionsArg.getProperty("entity");
|
|
3603
|
+
if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
|
|
3604
|
+
const entityInit = entityProp.getInitializer();
|
|
3605
|
+
if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
|
|
3606
|
+
const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
|
|
3607
|
+
if (!resolved || resolved.kind !== "class") return void 0;
|
|
3608
|
+
return resolved.decl;
|
|
3609
|
+
}
|
|
3610
|
+
function extractFilterableEntityFields(filterClass, project, entityOverride) {
|
|
3561
3611
|
const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
|
|
3562
3612
|
if (!filterableDecorator) return [];
|
|
3563
3613
|
const args = filterableDecorator.getArguments();
|
|
3564
3614
|
if (args.length === 0) return [];
|
|
3565
3615
|
const optionsArg = args[0];
|
|
3566
3616
|
if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
|
|
3567
|
-
const
|
|
3568
|
-
if (!
|
|
3569
|
-
const entityInit = entityProp.getInitializer();
|
|
3570
|
-
if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
|
|
3571
|
-
const entityName = entityInit.getText();
|
|
3572
|
-
const filterSourceFile = filterClass.getSourceFile();
|
|
3573
|
-
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
3574
|
-
if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
|
|
3575
|
-
const entityDecl = resolvedEntity.decl;
|
|
3617
|
+
const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
|
|
3618
|
+
if (!entityDecl) return [];
|
|
3576
3619
|
const fields = collectEntityFields(
|
|
3577
3620
|
entityDecl,
|
|
3578
3621
|
entityDecl.getSourceFile(),
|
|
@@ -3997,9 +4040,9 @@ function unwrapNamedContainer(node, names) {
|
|
|
3997
4040
|
}
|
|
3998
4041
|
return node;
|
|
3999
4042
|
}
|
|
4000
|
-
function extractDtoContract(method, sourceFile, project) {
|
|
4043
|
+
function extractDtoContract(method, sourceFile, project, mixin) {
|
|
4001
4044
|
let body = extractBodyType(method, sourceFile, project);
|
|
4002
|
-
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
4045
|
+
const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
|
|
4003
4046
|
const query = extractQueryType(method, sourceFile, project);
|
|
4004
4047
|
const uploads = extractUploadedFiles(method);
|
|
4005
4048
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
@@ -4109,12 +4152,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
4109
4152
|
return null;
|
|
4110
4153
|
}
|
|
4111
4154
|
|
|
4155
|
+
// src/discovery/heritage.ts
|
|
4156
|
+
import { Node as Node7, Project as Project3 } from "ts-morph";
|
|
4157
|
+
function unwrapExpression(node) {
|
|
4158
|
+
let current = node;
|
|
4159
|
+
while (Node7.isAsExpression(current) || Node7.isSatisfiesExpression(current) || Node7.isParenthesizedExpression(current) || Node7.isTypeAssertion(current)) {
|
|
4160
|
+
current = current.getExpression();
|
|
4161
|
+
}
|
|
4162
|
+
return current;
|
|
4163
|
+
}
|
|
4164
|
+
function resolveReturnedClass(factoryDecl) {
|
|
4165
|
+
const body = factoryDecl.getBody();
|
|
4166
|
+
if (!body) return void 0;
|
|
4167
|
+
const returns = body.getDescendants().filter((n) => Node7.isReturnStatement(n));
|
|
4168
|
+
for (const ret of returns) {
|
|
4169
|
+
const expr = ret.getExpression();
|
|
4170
|
+
if (!expr) continue;
|
|
4171
|
+
const inner = unwrapExpression(expr);
|
|
4172
|
+
if (Node7.isClassExpression(inner)) {
|
|
4173
|
+
return inner;
|
|
4174
|
+
}
|
|
4175
|
+
if (Node7.isIdentifier(inner)) {
|
|
4176
|
+
const name = inner.getText();
|
|
4177
|
+
const decl = body.getDescendants().find((n) => Node7.isClassDeclaration(n) && n.getName() === name);
|
|
4178
|
+
if (decl) return decl;
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
return void 0;
|
|
4182
|
+
}
|
|
4183
|
+
function resolveInheritedMethods(cls) {
|
|
4184
|
+
const expr = cls.getExtends()?.getExpression();
|
|
4185
|
+
if (!expr || !Node7.isCallExpression(expr)) return void 0;
|
|
4186
|
+
const callee = expr.getExpression();
|
|
4187
|
+
if (!Node7.isIdentifier(callee)) return void 0;
|
|
4188
|
+
const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isFunctionDeclaration(n));
|
|
4189
|
+
if (!factoryDecl) return void 0;
|
|
4190
|
+
const returnedClass = resolveReturnedClass(factoryDecl);
|
|
4191
|
+
if (!returnedClass) return void 0;
|
|
4192
|
+
const classArgs = [];
|
|
4193
|
+
for (const arg of expr.getArguments()) {
|
|
4194
|
+
if (!Node7.isIdentifier(arg)) continue;
|
|
4195
|
+
const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isClassDeclaration(n));
|
|
4196
|
+
if (decl) {
|
|
4197
|
+
classArgs.push({
|
|
4198
|
+
name: decl.getName() ?? arg.getText(),
|
|
4199
|
+
filePath: decl.getSourceFile().getFilePath()
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
return {
|
|
4204
|
+
methods: returnedClass.getMethods(),
|
|
4205
|
+
factoryName: callee.getText(),
|
|
4206
|
+
factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
|
|
4207
|
+
classArgs
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
var mixinTypeProject;
|
|
4211
|
+
function getMixinTypeProject() {
|
|
4212
|
+
mixinTypeProject ??= new Project3({
|
|
4213
|
+
skipAddingFilesFromTsConfig: true,
|
|
4214
|
+
compilerOptions: { strict: true }
|
|
4215
|
+
});
|
|
4216
|
+
return mixinTypeProject;
|
|
4217
|
+
}
|
|
4218
|
+
function clearMixinTypeProject() {
|
|
4219
|
+
mixinTypeProject = void 0;
|
|
4220
|
+
}
|
|
4221
|
+
function resolveInstantiatedReturnType(cls, methodName) {
|
|
4222
|
+
const filePath = cls.getSourceFile().getFilePath();
|
|
4223
|
+
const className = cls.getName();
|
|
4224
|
+
if (!className) return void 0;
|
|
4225
|
+
const project = getMixinTypeProject();
|
|
4226
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
|
|
4227
|
+
const typedCls = sourceFile?.getClass(className);
|
|
4228
|
+
if (!typedCls) return void 0;
|
|
4229
|
+
const prop = typedCls.getType().getProperty(methodName);
|
|
4230
|
+
if (!prop) return void 0;
|
|
4231
|
+
const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
|
|
4232
|
+
if (!returnType) return void 0;
|
|
4233
|
+
const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
|
|
4234
|
+
return unwrapped.getText(typedCls);
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4112
4237
|
// src/discovery/zod-ast-to-ts.ts
|
|
4113
|
-
import { Node as
|
|
4238
|
+
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
4114
4239
|
function zodAstToTs(node) {
|
|
4115
|
-
if (!
|
|
4240
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
4116
4241
|
const expr = node.getExpression();
|
|
4117
|
-
if (
|
|
4242
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
4118
4243
|
const methodName = expr.getName();
|
|
4119
4244
|
const receiver = expr.getExpression();
|
|
4120
4245
|
if (methodName === "optional") {
|
|
@@ -4138,17 +4263,17 @@ function zodAstToTs(node) {
|
|
|
4138
4263
|
case "literal": {
|
|
4139
4264
|
const lit = args[0];
|
|
4140
4265
|
if (!lit) return "unknown";
|
|
4141
|
-
if (
|
|
4142
|
-
if (
|
|
4266
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
4267
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
4143
4268
|
if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
|
|
4144
4269
|
if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
|
|
4145
4270
|
return "unknown";
|
|
4146
4271
|
}
|
|
4147
4272
|
case "enum": {
|
|
4148
4273
|
const arrArg = args[0];
|
|
4149
|
-
if (!arrArg || !
|
|
4274
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4150
4275
|
const members = arrArg.getElements().map(
|
|
4151
|
-
(el) =>
|
|
4276
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
4152
4277
|
);
|
|
4153
4278
|
return members.join(" | ");
|
|
4154
4279
|
}
|
|
@@ -4159,10 +4284,10 @@ function zodAstToTs(node) {
|
|
|
4159
4284
|
}
|
|
4160
4285
|
case "object": {
|
|
4161
4286
|
const objArg = args[0];
|
|
4162
|
-
if (!objArg || !
|
|
4287
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
4163
4288
|
const lines = [];
|
|
4164
4289
|
for (const prop of objArg.getProperties()) {
|
|
4165
|
-
if (!
|
|
4290
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
4166
4291
|
const key = prop.getName();
|
|
4167
4292
|
const valNode = prop.getInitializer();
|
|
4168
4293
|
if (!valNode) continue;
|
|
@@ -4174,7 +4299,7 @@ function zodAstToTs(node) {
|
|
|
4174
4299
|
}
|
|
4175
4300
|
case "union": {
|
|
4176
4301
|
const arrArg = args[0];
|
|
4177
|
-
if (!arrArg || !
|
|
4302
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4178
4303
|
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
4179
4304
|
}
|
|
4180
4305
|
case "record": {
|
|
@@ -4184,7 +4309,7 @@ function zodAstToTs(node) {
|
|
|
4184
4309
|
}
|
|
4185
4310
|
case "tuple": {
|
|
4186
4311
|
const arrArg = args[0];
|
|
4187
|
-
if (!arrArg || !
|
|
4312
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4188
4313
|
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
4189
4314
|
}
|
|
4190
4315
|
default:
|
|
@@ -4194,18 +4319,18 @@ function zodAstToTs(node) {
|
|
|
4194
4319
|
return "unknown";
|
|
4195
4320
|
}
|
|
4196
4321
|
function isOptionalChain(node) {
|
|
4197
|
-
if (!
|
|
4322
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
4198
4323
|
const expr = node.getExpression();
|
|
4199
|
-
return
|
|
4324
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
4200
4325
|
}
|
|
4201
4326
|
function parseDefineContractCall(callExpr) {
|
|
4202
|
-
if (!
|
|
4327
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
4203
4328
|
const callee = callExpr.getExpression();
|
|
4204
|
-
const calleeName =
|
|
4329
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
4205
4330
|
if (calleeName !== "defineContract") return null;
|
|
4206
4331
|
const args = callExpr.getArguments();
|
|
4207
4332
|
const optsArg = args[0];
|
|
4208
|
-
if (!optsArg || !
|
|
4333
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
4209
4334
|
let query = null;
|
|
4210
4335
|
let body = null;
|
|
4211
4336
|
let response = "unknown";
|
|
@@ -4213,7 +4338,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
4213
4338
|
let bodyZodText = null;
|
|
4214
4339
|
let queryZodText = null;
|
|
4215
4340
|
for (const prop of optsArg.getProperties()) {
|
|
4216
|
-
if (!
|
|
4341
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
4217
4342
|
const propName = prop.getName();
|
|
4218
4343
|
const val = prop.getInitializer();
|
|
4219
4344
|
if (!val) continue;
|
|
@@ -4242,6 +4367,7 @@ async function discoverContractsFast(opts) {
|
|
|
4242
4367
|
project.addSourceFileAtPath(f);
|
|
4243
4368
|
}
|
|
4244
4369
|
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
4370
|
+
clearMixinTypeProject();
|
|
4245
4371
|
return extractAllRoutes(project);
|
|
4246
4372
|
}
|
|
4247
4373
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
@@ -4249,14 +4375,14 @@ function resolveTsconfigPath(cwd, tsconfig) {
|
|
|
4249
4375
|
}
|
|
4250
4376
|
function createDiscoveryProject(tsconfigPath) {
|
|
4251
4377
|
try {
|
|
4252
|
-
return new
|
|
4378
|
+
return new Project4({
|
|
4253
4379
|
tsConfigFilePath: tsconfigPath,
|
|
4254
4380
|
skipAddingFilesFromTsConfig: true,
|
|
4255
4381
|
skipLoadingLibFiles: true,
|
|
4256
4382
|
skipFileDependencyResolution: true
|
|
4257
4383
|
});
|
|
4258
4384
|
} catch {
|
|
4259
|
-
return new
|
|
4385
|
+
return new Project4({
|
|
4260
4386
|
skipAddingFilesFromTsConfig: true,
|
|
4261
4387
|
skipLoadingLibFiles: true,
|
|
4262
4388
|
skipFileDependencyResolution: true,
|
|
@@ -4363,15 +4489,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4363
4489
|
runExtraction() {
|
|
4364
4490
|
clearTypeResolutionCaches(this.project);
|
|
4365
4491
|
clearEnumCache(this.project);
|
|
4492
|
+
clearMixinTypeProject();
|
|
4366
4493
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
4367
4494
|
}
|
|
4368
4495
|
};
|
|
4369
4496
|
function decoratorStringArg(decoratorExpr) {
|
|
4370
4497
|
if (!decoratorExpr) return void 0;
|
|
4371
|
-
if (
|
|
4372
|
-
if (
|
|
4498
|
+
if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
4499
|
+
if (Node9.isArrayLiteralExpression(decoratorExpr)) {
|
|
4373
4500
|
const first = decoratorExpr.getElements()[0];
|
|
4374
|
-
if (first &&
|
|
4501
|
+
if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
|
|
4375
4502
|
}
|
|
4376
4503
|
return void 0;
|
|
4377
4504
|
}
|
|
@@ -4393,7 +4520,8 @@ function joinPaths(prefix, suffix) {
|
|
|
4393
4520
|
if (!prefix && !suffix) return "/";
|
|
4394
4521
|
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4395
4522
|
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4396
|
-
const
|
|
4523
|
+
const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4524
|
+
const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
4397
4525
|
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4398
4526
|
const combined = p + s;
|
|
4399
4527
|
return combined === "" ? "/" : combined;
|
|
@@ -4447,7 +4575,8 @@ function buildRoute(args) {
|
|
|
4447
4575
|
methodAs,
|
|
4448
4576
|
sourceFile,
|
|
4449
4577
|
seenNames,
|
|
4450
|
-
contractSource
|
|
4578
|
+
contractSource,
|
|
4579
|
+
mixin
|
|
4451
4580
|
} = args;
|
|
4452
4581
|
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
4453
4582
|
const qualifiedRef = `${className}.${methodName}`;
|
|
@@ -4463,7 +4592,12 @@ function buildRoute(args) {
|
|
|
4463
4592
|
path: combinedPath,
|
|
4464
4593
|
name: routeName,
|
|
4465
4594
|
params: extractParams(combinedPath),
|
|
4466
|
-
controllerRef: {
|
|
4595
|
+
controllerRef: {
|
|
4596
|
+
className,
|
|
4597
|
+
methodName,
|
|
4598
|
+
filePath: sourceFile.getFilePath(),
|
|
4599
|
+
...mixin ? { mixin } : {}
|
|
4600
|
+
},
|
|
4467
4601
|
contract: { contractSource }
|
|
4468
4602
|
};
|
|
4469
4603
|
}
|
|
@@ -4477,16 +4611,17 @@ function extractContractRoute(args) {
|
|
|
4477
4611
|
className,
|
|
4478
4612
|
sourceFile,
|
|
4479
4613
|
project,
|
|
4480
|
-
seenNames
|
|
4614
|
+
seenNames,
|
|
4615
|
+
mixin
|
|
4481
4616
|
} = args;
|
|
4482
4617
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
4483
4618
|
if (!firstDecoratorArg) return null;
|
|
4484
4619
|
let contractDef = null;
|
|
4485
4620
|
let bodyZodRef = null;
|
|
4486
4621
|
let queryZodRef = null;
|
|
4487
|
-
if (
|
|
4622
|
+
if (Node9.isCallExpression(firstDecoratorArg)) {
|
|
4488
4623
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
4489
|
-
} else if (
|
|
4624
|
+
} else if (Node9.isIdentifier(firstDecoratorArg)) {
|
|
4490
4625
|
const identName = firstDecoratorArg.getText();
|
|
4491
4626
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4492
4627
|
if (!resolvedVar) {
|
|
@@ -4529,6 +4664,7 @@ function extractContractRoute(args) {
|
|
|
4529
4664
|
methodAs,
|
|
4530
4665
|
sourceFile,
|
|
4531
4666
|
seenNames,
|
|
4667
|
+
mixin,
|
|
4532
4668
|
contractSource: {
|
|
4533
4669
|
query: contractDef.query,
|
|
4534
4670
|
body: contractDef.body,
|
|
@@ -4545,13 +4681,14 @@ function extractContractRoute(args) {
|
|
|
4545
4681
|
});
|
|
4546
4682
|
}
|
|
4547
4683
|
function extractDtoRoute(args) {
|
|
4548
|
-
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
4684
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
|
|
4549
4685
|
if (!verb) return null;
|
|
4550
4686
|
const combined = joinPaths(prefix, verb.handlerPath);
|
|
4551
4687
|
const methodName = method.getName();
|
|
4552
4688
|
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
4553
4689
|
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
4554
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
4690
|
+
const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
|
|
4691
|
+
const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
|
|
4555
4692
|
return buildRoute({
|
|
4556
4693
|
className,
|
|
4557
4694
|
methodName,
|
|
@@ -4561,10 +4698,11 @@ function extractDtoRoute(args) {
|
|
|
4561
4698
|
methodAs,
|
|
4562
4699
|
sourceFile,
|
|
4563
4700
|
seenNames,
|
|
4701
|
+
mixin,
|
|
4564
4702
|
contractSource: {
|
|
4565
4703
|
query: dtoContract?.query ?? null,
|
|
4566
4704
|
body: dtoContract?.body ?? null,
|
|
4567
|
-
response: dtoContract?.response ?? "unknown",
|
|
4705
|
+
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
4568
4706
|
error: dtoContract?.error ?? null,
|
|
4569
4707
|
queryRef: dtoContract?.queryRef ?? null,
|
|
4570
4708
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
@@ -4593,7 +4731,17 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4593
4731
|
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
4594
4732
|
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
4595
4733
|
const className = cls.getName() ?? "Unknown";
|
|
4596
|
-
|
|
4734
|
+
const inherited = resolveInheritedMethods(cls);
|
|
4735
|
+
const mixinBinding = inherited ? {
|
|
4736
|
+
factoryName: inherited.factoryName,
|
|
4737
|
+
factoryFilePath: inherited.factoryFilePath,
|
|
4738
|
+
classArgs: inherited.classArgs
|
|
4739
|
+
} : void 0;
|
|
4740
|
+
const methods = [
|
|
4741
|
+
...cls.getMethods().map((method) => ({ method })),
|
|
4742
|
+
...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
|
|
4743
|
+
];
|
|
4744
|
+
for (const { method, mixin } of methods) {
|
|
4597
4745
|
const verb = resolveVerb(method);
|
|
4598
4746
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
4599
4747
|
const route = applyContractDecorator ? extractContractRoute({
|
|
@@ -4605,7 +4753,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4605
4753
|
className,
|
|
4606
4754
|
sourceFile,
|
|
4607
4755
|
project,
|
|
4608
|
-
seenNames
|
|
4756
|
+
seenNames,
|
|
4757
|
+
mixin
|
|
4609
4758
|
}) : extractDtoRoute({
|
|
4610
4759
|
cls,
|
|
4611
4760
|
method,
|
|
@@ -4614,7 +4763,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4614
4763
|
className,
|
|
4615
4764
|
sourceFile,
|
|
4616
4765
|
project,
|
|
4617
|
-
seenNames
|
|
4766
|
+
seenNames,
|
|
4767
|
+
mixin
|
|
4618
4768
|
});
|
|
4619
4769
|
if (route) routes.push(route);
|
|
4620
4770
|
}
|
|
@@ -4815,7 +4965,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4815
4965
|
}
|
|
4816
4966
|
|
|
4817
4967
|
// src/index.ts
|
|
4818
|
-
var VERSION = "0.
|
|
4968
|
+
var VERSION = "0.16.0";
|
|
4819
4969
|
|
|
4820
4970
|
// src/cli/codegen.ts
|
|
4821
4971
|
async function runCodegen(opts = {}) {
|