@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/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-
|
|
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-
|
|
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.
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
|
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
|
|
2393
|
-
Project as
|
|
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,
|
|
3479
|
-
|
|
3480
|
-
|
|
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
|
|
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
|
|
3576
|
-
if (!
|
|
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
|
|
4246
|
+
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
4122
4247
|
function zodAstToTs(node) {
|
|
4123
|
-
if (!
|
|
4248
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
4124
4249
|
const expr = node.getExpression();
|
|
4125
|
-
if (
|
|
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 (
|
|
4150
|
-
if (
|
|
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 || !
|
|
4282
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4158
4283
|
const members = arrArg.getElements().map(
|
|
4159
|
-
(el) =>
|
|
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 || !
|
|
4295
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
4171
4296
|
const lines = [];
|
|
4172
4297
|
for (const prop of objArg.getProperties()) {
|
|
4173
|
-
if (!
|
|
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 || !
|
|
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 || !
|
|
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 (!
|
|
4330
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
4206
4331
|
const expr = node.getExpression();
|
|
4207
|
-
return
|
|
4332
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
4208
4333
|
}
|
|
4209
4334
|
function parseDefineContractCall(callExpr) {
|
|
4210
|
-
if (!
|
|
4335
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
4211
4336
|
const callee = callExpr.getExpression();
|
|
4212
|
-
const calleeName =
|
|
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 || !
|
|
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 (!
|
|
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
|
|
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
|
|
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 (
|
|
4380
|
-
if (
|
|
4506
|
+
if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
4507
|
+
if (Node9.isArrayLiteralExpression(decoratorExpr)) {
|
|
4381
4508
|
const first = decoratorExpr.getElements()[0];
|
|
4382
|
-
if (first &&
|
|
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
|
|
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: {
|
|
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 (
|
|
4630
|
+
if (Node9.isCallExpression(firstDecoratorArg)) {
|
|
4496
4631
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
4497
|
-
} else if (
|
|
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
|
-
|
|
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.
|
|
5059
|
+
var VERSION = "0.16.0";
|
|
4910
5060
|
export {
|
|
4911
5061
|
CodegenError,
|
|
4912
5062
|
ConfigError,
|