@dudousxd/nestjs-codegen 0.25.2 → 0.26.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 +24 -0
- package/dist/cli/main.cjs +791 -208
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +796 -211
- 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-Ce3YnfwS.d.cts → index-Bbw73Kkb.d.cts} +8 -1
- package/dist/{index-Ce3YnfwS.d.ts → index-Bbw73Kkb.d.ts} +8 -1
- package/dist/index.cjs +794 -211
- 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 +799 -214
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +833 -252
- 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 +835 -252
- package/dist/nest/index.js.map +1 -1
- package/package.json +11 -7
package/dist/cli/main.js
CHANGED
|
@@ -518,22 +518,22 @@ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from
|
|
|
518
518
|
|
|
519
519
|
// src/extension/registry.ts
|
|
520
520
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
521
|
-
import { Project as
|
|
521
|
+
import { Project as Project5 } from "ts-morph";
|
|
522
522
|
|
|
523
523
|
// src/discovery/contracts-fast.ts
|
|
524
524
|
import { existsSync } from "fs";
|
|
525
525
|
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
526
526
|
import fg2 from "fast-glob";
|
|
527
527
|
import {
|
|
528
|
-
Node as
|
|
529
|
-
Project as
|
|
530
|
-
ts
|
|
528
|
+
Node as Node11,
|
|
529
|
+
Project as Project4,
|
|
530
|
+
ts as ts3
|
|
531
531
|
} from "ts-morph";
|
|
532
532
|
|
|
533
533
|
// src/discovery/dto-type-resolver.ts
|
|
534
534
|
import {
|
|
535
|
-
Node as
|
|
536
|
-
SyntaxKind as
|
|
535
|
+
Node as Node10,
|
|
536
|
+
SyntaxKind as SyntaxKind4
|
|
537
537
|
} from "ts-morph";
|
|
538
538
|
|
|
539
539
|
// src/discovery/dto-to-ir.ts
|
|
@@ -2071,6 +2071,674 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2071
2071
|
return fields;
|
|
2072
2072
|
}
|
|
2073
2073
|
|
|
2074
|
+
// src/discovery/pipe-schema.ts
|
|
2075
|
+
import { Node as Node9 } from "ts-morph";
|
|
2076
|
+
|
|
2077
|
+
// src/discovery/schema-type-inference.ts
|
|
2078
|
+
import {
|
|
2079
|
+
ts as ts2
|
|
2080
|
+
} from "ts-morph";
|
|
2081
|
+
|
|
2082
|
+
// src/discovery/schema-type-program.ts
|
|
2083
|
+
import { Node as Node7, Project as Project3, ts } from "ts-morph";
|
|
2084
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2085
|
+
var SchemaTypeProgram = class {
|
|
2086
|
+
constructor(discovery) {
|
|
2087
|
+
this.discovery = discovery;
|
|
2088
|
+
const base = discovery.getCompilerOptions();
|
|
2089
|
+
this.options = {
|
|
2090
|
+
...base,
|
|
2091
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2092
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2093
|
+
target: base.target ?? ts.ScriptTarget.ES2022,
|
|
2094
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2095
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2096
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
|
|
2097
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2098
|
+
strictNullChecks: true,
|
|
2099
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2100
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2101
|
+
types: [],
|
|
2102
|
+
noEmit: true
|
|
2103
|
+
};
|
|
2104
|
+
this.project = new Project3({
|
|
2105
|
+
skipAddingFilesFromTsConfig: true,
|
|
2106
|
+
compilerOptions: this.options
|
|
2107
|
+
});
|
|
2108
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2109
|
+
this.flush();
|
|
2110
|
+
}
|
|
2111
|
+
project;
|
|
2112
|
+
options;
|
|
2113
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2114
|
+
scratch = new Project3({
|
|
2115
|
+
skipAddingFilesFromTsConfig: true,
|
|
2116
|
+
skipLoadingLibFiles: true,
|
|
2117
|
+
skipFileDependencyResolution: true
|
|
2118
|
+
});
|
|
2119
|
+
needs = /* @__PURE__ */ new Map();
|
|
2120
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2121
|
+
written = /* @__PURE__ */ new Map();
|
|
2122
|
+
/**
|
|
2123
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2124
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2125
|
+
* program once.
|
|
2126
|
+
*/
|
|
2127
|
+
sourceFileFor(sf) {
|
|
2128
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2129
|
+
this.flush();
|
|
2130
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2131
|
+
}
|
|
2132
|
+
// -------------------------------------------------------------------------
|
|
2133
|
+
queue = [];
|
|
2134
|
+
request(path, want) {
|
|
2135
|
+
let needs = this.needs.get(path);
|
|
2136
|
+
const isNew = !needs;
|
|
2137
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2138
|
+
let grew = isNew;
|
|
2139
|
+
if (want.schema && !needs.schema) {
|
|
2140
|
+
needs.schema = true;
|
|
2141
|
+
grew = true;
|
|
2142
|
+
}
|
|
2143
|
+
if (want.all && !needs.all) {
|
|
2144
|
+
needs.all = true;
|
|
2145
|
+
grew = true;
|
|
2146
|
+
}
|
|
2147
|
+
for (const name of want.names ?? []) {
|
|
2148
|
+
if (!needs.names.has(name)) {
|
|
2149
|
+
needs.names.add(name);
|
|
2150
|
+
grew = true;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
this.needs.set(path, needs);
|
|
2154
|
+
if (grew) this.queue.push(path);
|
|
2155
|
+
}
|
|
2156
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2157
|
+
flush() {
|
|
2158
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2159
|
+
while (this.queue.length > 0) {
|
|
2160
|
+
const path = this.queue.pop();
|
|
2161
|
+
const original = this.original(path);
|
|
2162
|
+
if (!original) continue;
|
|
2163
|
+
const needs = this.needs.get(path);
|
|
2164
|
+
const text = this.prune(original, needs);
|
|
2165
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2166
|
+
}
|
|
2167
|
+
for (const [path, text] of changed) {
|
|
2168
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2169
|
+
this.written.set(path, text);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2173
|
+
original(path) {
|
|
2174
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2178
|
+
* requesting from each kept one's target the names it brings in.
|
|
2179
|
+
*/
|
|
2180
|
+
prune(sf, needs) {
|
|
2181
|
+
const locals = topLevelDeclarations(sf);
|
|
2182
|
+
const roots = [];
|
|
2183
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2184
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2185
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2186
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2187
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2188
|
+
if (spec === void 0) {
|
|
2189
|
+
for (const named of exp.getNamedExports()) {
|
|
2190
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2191
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2192
|
+
}
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2196
|
+
for (const named of exp.getNamedExports()) {
|
|
2197
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2198
|
+
const list = reexported.get(exposed) ?? [];
|
|
2199
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2200
|
+
reexported.set(exposed, list);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const unresolvedNames = [];
|
|
2204
|
+
for (const name of needs.names) {
|
|
2205
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2206
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2207
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2208
|
+
}
|
|
2209
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2210
|
+
const queue = [...roots];
|
|
2211
|
+
const seen = new Set(queue);
|
|
2212
|
+
while (queue.length > 0) {
|
|
2213
|
+
const node = queue.pop();
|
|
2214
|
+
const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
|
|
2215
|
+
for (const id of ids) {
|
|
2216
|
+
const name = id.getText();
|
|
2217
|
+
if (referenced.has(name)) continue;
|
|
2218
|
+
referenced.add(name);
|
|
2219
|
+
for (const local of locals.get(name) ?? []) {
|
|
2220
|
+
if (seen.has(local)) continue;
|
|
2221
|
+
seen.add(local);
|
|
2222
|
+
queue.push(local);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
const chars = sf.getFullText().split("");
|
|
2227
|
+
const blank = (node) => {
|
|
2228
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2229
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2233
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2234
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2235
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2236
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2237
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2238
|
+
imported: n.getName()
|
|
2239
|
+
})).filter((n) => referenced.has(n.local));
|
|
2240
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2241
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2242
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2243
|
+
blank(decl);
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
if (target) {
|
|
2247
|
+
this.request(target, {
|
|
2248
|
+
all: wantsNamespace,
|
|
2249
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2254
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2255
|
+
if (spec === void 0) continue;
|
|
2256
|
+
const target = this.resolve(spec, sf);
|
|
2257
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2258
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2259
|
+
blank(exp);
|
|
2260
|
+
} else if (target) {
|
|
2261
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2262
|
+
}
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
const wanted = exp.getNamedExports().filter(
|
|
2266
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2267
|
+
).map((n) => n.getNameNode().getText());
|
|
2268
|
+
if (wanted.length === 0) blank(exp);
|
|
2269
|
+
else if (target) this.request(target, { names: wanted });
|
|
2270
|
+
}
|
|
2271
|
+
return chars.join("");
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2275
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2276
|
+
*/
|
|
2277
|
+
resolve(specifier, from) {
|
|
2278
|
+
const resolved = ts.resolveModuleName(
|
|
2279
|
+
specifier,
|
|
2280
|
+
from.getFilePath(),
|
|
2281
|
+
this.options,
|
|
2282
|
+
ts.sys
|
|
2283
|
+
).resolvedModule;
|
|
2284
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2285
|
+
const file = resolved.resolvedFileName;
|
|
2286
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2287
|
+
return file;
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
function schemaPositions(sf) {
|
|
2291
|
+
const roots = [];
|
|
2292
|
+
sf.forEachDescendant((node) => {
|
|
2293
|
+
if (Node7.isDecorator(node)) {
|
|
2294
|
+
const name = node.getName();
|
|
2295
|
+
if (name === "ApplyContract") {
|
|
2296
|
+
roots.push(...node.getArguments());
|
|
2297
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2298
|
+
for (const arg of node.getArguments()) {
|
|
2299
|
+
if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
|
|
2300
|
+
roots.push(...arg.getArguments());
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
} else if (Node7.isCallExpression(node)) {
|
|
2305
|
+
const callee = node.getExpression();
|
|
2306
|
+
const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2307
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
return roots;
|
|
2311
|
+
}
|
|
2312
|
+
function topLevelDeclarations(sf) {
|
|
2313
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2314
|
+
const add = (name, node) => {
|
|
2315
|
+
if (!name) return;
|
|
2316
|
+
const list = byName.get(name);
|
|
2317
|
+
if (list) list.push(node);
|
|
2318
|
+
else byName.set(name, [node]);
|
|
2319
|
+
};
|
|
2320
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2321
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2322
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2323
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2324
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2325
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2326
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2327
|
+
return byName;
|
|
2328
|
+
}
|
|
2329
|
+
function defaultExport(sf) {
|
|
2330
|
+
const nodes = [];
|
|
2331
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2332
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2333
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2334
|
+
return nodes;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// src/discovery/schema-type-inference.ts
|
|
2338
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2339
|
+
function programFor(discovery) {
|
|
2340
|
+
let program = programs.get(discovery);
|
|
2341
|
+
if (!program) {
|
|
2342
|
+
program = new SchemaTypeProgram(discovery);
|
|
2343
|
+
programs.set(discovery, program);
|
|
2344
|
+
}
|
|
2345
|
+
return program;
|
|
2346
|
+
}
|
|
2347
|
+
function clearSchemaTypeProject(discovery) {
|
|
2348
|
+
programs.delete(discovery);
|
|
2349
|
+
}
|
|
2350
|
+
function counterpart(typedSf, node) {
|
|
2351
|
+
const start = node.getStart();
|
|
2352
|
+
const end = node.getEnd();
|
|
2353
|
+
const kind = node.getKind();
|
|
2354
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2355
|
+
while (candidate) {
|
|
2356
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2357
|
+
return candidate;
|
|
2358
|
+
}
|
|
2359
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2360
|
+
candidate = candidate.getParent();
|
|
2361
|
+
}
|
|
2362
|
+
return void 0;
|
|
2363
|
+
}
|
|
2364
|
+
function propType(type, name, at) {
|
|
2365
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2366
|
+
}
|
|
2367
|
+
function isAnyOrUnknown(type) {
|
|
2368
|
+
return type.isAny() || type.isUnknown();
|
|
2369
|
+
}
|
|
2370
|
+
function inferSchema(expr, discovery) {
|
|
2371
|
+
try {
|
|
2372
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2373
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2374
|
+
if (!at) return { kind: "unresolved" };
|
|
2375
|
+
const schemaType = at.getType();
|
|
2376
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2377
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2378
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2379
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2380
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2381
|
+
if (stdInput && stdOutput) {
|
|
2382
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2383
|
+
}
|
|
2384
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2385
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2386
|
+
if (zodInput && zodOutput) {
|
|
2387
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2388
|
+
}
|
|
2389
|
+
return { kind: "not-schema" };
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
dbg("schema inference failed:", err);
|
|
2392
|
+
return { kind: "unresolved" };
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2396
|
+
"Date",
|
|
2397
|
+
"File",
|
|
2398
|
+
"Blob",
|
|
2399
|
+
"FormData",
|
|
2400
|
+
"URL",
|
|
2401
|
+
"RegExp",
|
|
2402
|
+
"Map",
|
|
2403
|
+
"Set",
|
|
2404
|
+
"ArrayBuffer",
|
|
2405
|
+
"Uint8Array"
|
|
2406
|
+
]);
|
|
2407
|
+
var MAX_DEPTH = 12;
|
|
2408
|
+
function objectKey(name) {
|
|
2409
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2410
|
+
}
|
|
2411
|
+
function isOptionalSymbol(sym) {
|
|
2412
|
+
return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
|
|
2413
|
+
}
|
|
2414
|
+
function isSymbolKeyed(sym) {
|
|
2415
|
+
return sym.getName().startsWith("__@");
|
|
2416
|
+
}
|
|
2417
|
+
function orderedProperties(type) {
|
|
2418
|
+
const position = (sym) => {
|
|
2419
|
+
const decl = sym.getDeclarations()[0];
|
|
2420
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2421
|
+
};
|
|
2422
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2423
|
+
if (a.at && b.at) {
|
|
2424
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2425
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2426
|
+
} else if (a.at || b.at) {
|
|
2427
|
+
return a.at ? -1 : 1;
|
|
2428
|
+
}
|
|
2429
|
+
return a.index - b.index;
|
|
2430
|
+
}).map((entry) => entry.sym);
|
|
2431
|
+
}
|
|
2432
|
+
function isFromDeclarationFile(sym) {
|
|
2433
|
+
const decl = sym?.getDeclarations()[0];
|
|
2434
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2435
|
+
}
|
|
2436
|
+
function render(type, state, paired) {
|
|
2437
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2438
|
+
if (isAnyOrUnknown(type)) {
|
|
2439
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2440
|
+
}
|
|
2441
|
+
if (type.isNever()) return "never";
|
|
2442
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2443
|
+
if (type.isNumber()) return "number";
|
|
2444
|
+
if (type.isBoolean()) return "boolean";
|
|
2445
|
+
if (type.isBigInt()) return "bigint";
|
|
2446
|
+
if (type.isNull()) return "null";
|
|
2447
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2448
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2449
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2450
|
+
const value = type.getLiteralValue();
|
|
2451
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2452
|
+
}
|
|
2453
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2454
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2455
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2456
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2457
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2458
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2459
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2460
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2461
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2462
|
+
}
|
|
2463
|
+
if (type.isTuple()) {
|
|
2464
|
+
const elements = type.getTupleElements();
|
|
2465
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2466
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2467
|
+
}
|
|
2468
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2469
|
+
return "unknown";
|
|
2470
|
+
}
|
|
2471
|
+
function renderUnion(type, state, paired) {
|
|
2472
|
+
const members = type.getUnionTypes();
|
|
2473
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2474
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2475
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2476
|
+
const arms = [];
|
|
2477
|
+
let sawBoolean = false;
|
|
2478
|
+
let hasNull = false;
|
|
2479
|
+
let hasUndefined = false;
|
|
2480
|
+
for (const member of members) {
|
|
2481
|
+
if (member.isNull()) {
|
|
2482
|
+
hasNull = true;
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2485
|
+
if (member.isUndefined()) {
|
|
2486
|
+
hasUndefined = true;
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2490
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2491
|
+
sawBoolean = true;
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2495
|
+
}
|
|
2496
|
+
if (hasNull) arms.push("null");
|
|
2497
|
+
if (hasUndefined) arms.push("undefined");
|
|
2498
|
+
const unique = [...new Set(arms)];
|
|
2499
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2500
|
+
return unique.join(" | ");
|
|
2501
|
+
}
|
|
2502
|
+
function renderIntersection(type, state) {
|
|
2503
|
+
const members = type.getIntersectionTypes();
|
|
2504
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2505
|
+
if (primitive) return render(primitive, state);
|
|
2506
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2507
|
+
}
|
|
2508
|
+
function renderObject(type, state, paired) {
|
|
2509
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2510
|
+
const name = symbol?.getName();
|
|
2511
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2512
|
+
const args = type.getTypeArguments();
|
|
2513
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2514
|
+
}
|
|
2515
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2516
|
+
return "unknown";
|
|
2517
|
+
}
|
|
2518
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2519
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2520
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2521
|
+
const inner = { ...state, stack };
|
|
2522
|
+
const fields = [];
|
|
2523
|
+
for (const prop of orderedProperties(type)) {
|
|
2524
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2525
|
+
const propName = prop.getName();
|
|
2526
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2527
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2528
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2529
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2530
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2531
|
+
}
|
|
2532
|
+
const stringIndex = type.getStringIndexType();
|
|
2533
|
+
if (stringIndex) {
|
|
2534
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2535
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2536
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2537
|
+
fields.push(`[key: string]: ${value}`);
|
|
2538
|
+
}
|
|
2539
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2540
|
+
}
|
|
2541
|
+
function stateAt(at) {
|
|
2542
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2543
|
+
}
|
|
2544
|
+
function requestTypeOf(types) {
|
|
2545
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2546
|
+
}
|
|
2547
|
+
function responseTypeOf(types) {
|
|
2548
|
+
return render(types.output, stateAt(types.at));
|
|
2549
|
+
}
|
|
2550
|
+
function requestFieldsOf(types) {
|
|
2551
|
+
const { input, output, at } = types;
|
|
2552
|
+
const objectInput = input.getNonNullableType();
|
|
2553
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2554
|
+
const state = stateAt(at);
|
|
2555
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2556
|
+
name: prop.getName(),
|
|
2557
|
+
type: render(
|
|
2558
|
+
prop.getTypeAtLocation(at),
|
|
2559
|
+
state,
|
|
2560
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2561
|
+
),
|
|
2562
|
+
optional: isOptionalSymbol(prop)
|
|
2563
|
+
}));
|
|
2564
|
+
}
|
|
2565
|
+
function acceptsUndefined(types) {
|
|
2566
|
+
const { input, output } = types;
|
|
2567
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2568
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2569
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2573
|
+
import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
2574
|
+
function zodAstToTs(node) {
|
|
2575
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2576
|
+
const expr = node.getExpression();
|
|
2577
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2578
|
+
const methodName = expr.getName();
|
|
2579
|
+
const receiver = expr.getExpression();
|
|
2580
|
+
if (methodName === "optional") {
|
|
2581
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2582
|
+
}
|
|
2583
|
+
if (methodName === "nullable") {
|
|
2584
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2585
|
+
}
|
|
2586
|
+
const args = node.getArguments();
|
|
2587
|
+
switch (methodName) {
|
|
2588
|
+
case "string":
|
|
2589
|
+
return "string";
|
|
2590
|
+
case "number":
|
|
2591
|
+
return "number";
|
|
2592
|
+
case "boolean":
|
|
2593
|
+
return "boolean";
|
|
2594
|
+
case "unknown":
|
|
2595
|
+
return "unknown";
|
|
2596
|
+
case "any":
|
|
2597
|
+
return "unknown";
|
|
2598
|
+
case "literal": {
|
|
2599
|
+
const lit = args[0];
|
|
2600
|
+
if (!lit) return "unknown";
|
|
2601
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2602
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2603
|
+
if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
|
|
2604
|
+
if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
|
|
2605
|
+
return "unknown";
|
|
2606
|
+
}
|
|
2607
|
+
case "enum": {
|
|
2608
|
+
const arrArg = args[0];
|
|
2609
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2610
|
+
const members = arrArg.getElements().map(
|
|
2611
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2612
|
+
);
|
|
2613
|
+
return members.join(" | ");
|
|
2614
|
+
}
|
|
2615
|
+
case "array": {
|
|
2616
|
+
const inner = args[0];
|
|
2617
|
+
if (!inner) return "unknown";
|
|
2618
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2619
|
+
}
|
|
2620
|
+
case "object": {
|
|
2621
|
+
const objArg = args[0];
|
|
2622
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2623
|
+
const lines = [];
|
|
2624
|
+
for (const prop of objArg.getProperties()) {
|
|
2625
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2626
|
+
const key = prop.getName();
|
|
2627
|
+
const valNode = prop.getInitializer();
|
|
2628
|
+
if (!valNode) continue;
|
|
2629
|
+
const tsType = zodAstToTs(valNode);
|
|
2630
|
+
const isOpt = isOptionalChain(valNode);
|
|
2631
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2632
|
+
}
|
|
2633
|
+
return `{ ${lines.join("; ")} }`;
|
|
2634
|
+
}
|
|
2635
|
+
case "union": {
|
|
2636
|
+
const arrArg = args[0];
|
|
2637
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2638
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2639
|
+
}
|
|
2640
|
+
case "record": {
|
|
2641
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2642
|
+
if (!valArg) return "unknown";
|
|
2643
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2644
|
+
}
|
|
2645
|
+
case "tuple": {
|
|
2646
|
+
const arrArg = args[0];
|
|
2647
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2648
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2649
|
+
}
|
|
2650
|
+
default:
|
|
2651
|
+
return "unknown";
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
return "unknown";
|
|
2655
|
+
}
|
|
2656
|
+
function isOptionalChain(node) {
|
|
2657
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
2658
|
+
const expr = node.getExpression();
|
|
2659
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2660
|
+
}
|
|
2661
|
+
function memberType(val, position, project) {
|
|
2662
|
+
if (project) {
|
|
2663
|
+
const inferred = inferSchema(val, project);
|
|
2664
|
+
if (inferred.kind === "schema") {
|
|
2665
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return zodAstToTs(val);
|
|
2669
|
+
}
|
|
2670
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2671
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2672
|
+
const callee = callExpr.getExpression();
|
|
2673
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2674
|
+
if (calleeName !== "defineContract") return null;
|
|
2675
|
+
const args = callExpr.getArguments();
|
|
2676
|
+
const optsArg = args[0];
|
|
2677
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2678
|
+
let query = null;
|
|
2679
|
+
let body = null;
|
|
2680
|
+
let response = "unknown";
|
|
2681
|
+
let error = null;
|
|
2682
|
+
let bodyZodText = null;
|
|
2683
|
+
let queryZodText = null;
|
|
2684
|
+
for (const prop of optsArg.getProperties()) {
|
|
2685
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2686
|
+
const propName = prop.getName();
|
|
2687
|
+
const val = prop.getInitializer();
|
|
2688
|
+
if (!val) continue;
|
|
2689
|
+
if (propName === "query") {
|
|
2690
|
+
query = memberType(val, "request", project);
|
|
2691
|
+
queryZodText = val.getText();
|
|
2692
|
+
} else if (propName === "body") {
|
|
2693
|
+
body = memberType(val, "request", project);
|
|
2694
|
+
bodyZodText = val.getText();
|
|
2695
|
+
} else if (propName === "response") {
|
|
2696
|
+
response = memberType(val, "response", project);
|
|
2697
|
+
} else if (propName === "error") {
|
|
2698
|
+
error = memberType(val, "response", project);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
// src/discovery/pipe-schema.ts
|
|
2705
|
+
function pipeSchemaOf(decorator, project) {
|
|
2706
|
+
for (const pipe of decorator.getArguments()) {
|
|
2707
|
+
if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
|
|
2708
|
+
for (const arg of pipe.getArguments()) {
|
|
2709
|
+
if (!couldBeSchema(arg)) continue;
|
|
2710
|
+
const inferred = inferSchema(arg, project);
|
|
2711
|
+
if (inferred.kind === "schema") {
|
|
2712
|
+
return {
|
|
2713
|
+
type: requestTypeOf(inferred.types),
|
|
2714
|
+
optional: acceptsUndefined(inferred.types),
|
|
2715
|
+
fields: requestFieldsOf(inferred.types)
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
if (inferred.kind === "unresolved") {
|
|
2719
|
+
const fallback = syntacticZodType(arg, project);
|
|
2720
|
+
if (fallback) {
|
|
2721
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return null;
|
|
2727
|
+
}
|
|
2728
|
+
function couldBeSchema(arg) {
|
|
2729
|
+
return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
|
|
2730
|
+
}
|
|
2731
|
+
function syntacticZodType(arg, project) {
|
|
2732
|
+
let expr = arg;
|
|
2733
|
+
if (Node9.isIdentifier(arg)) {
|
|
2734
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2735
|
+
expr = resolved?.decl.getInitializer();
|
|
2736
|
+
}
|
|
2737
|
+
if (!expr || !Node9.isCallExpression(expr)) return null;
|
|
2738
|
+
const type = zodAstToTs(expr);
|
|
2739
|
+
return type === "unknown" ? null : type;
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2074
2742
|
// src/discovery/dto-type-resolver.ts
|
|
2075
2743
|
var WRAPPER_TYPES = {
|
|
2076
2744
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2102,22 +2770,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2102
2770
|
]);
|
|
2103
2771
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2104
2772
|
if (depth <= 0) return "unknown";
|
|
2105
|
-
if (
|
|
2773
|
+
if (Node10.isArrayTypeNode(typeNode)) {
|
|
2106
2774
|
const elementType = typeNode.getElementTypeNode();
|
|
2107
2775
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2108
2776
|
}
|
|
2109
|
-
if (
|
|
2777
|
+
if (Node10.isUnionTypeNode(typeNode)) {
|
|
2110
2778
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2111
2779
|
}
|
|
2112
|
-
if (
|
|
2780
|
+
if (Node10.isIntersectionTypeNode(typeNode)) {
|
|
2113
2781
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2114
2782
|
}
|
|
2115
|
-
if (
|
|
2783
|
+
if (Node10.isParenthesizedTypeNode(typeNode)) {
|
|
2116
2784
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2117
2785
|
}
|
|
2118
|
-
if (
|
|
2786
|
+
if (Node10.isTypeReference(typeNode)) {
|
|
2119
2787
|
const typeName = typeNode.getTypeName();
|
|
2120
|
-
const name =
|
|
2788
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2121
2789
|
const bound = subst.get(name);
|
|
2122
2790
|
if (bound !== void 0) return bound;
|
|
2123
2791
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2140,13 +2808,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2140
2808
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2141
2809
|
return "unknown";
|
|
2142
2810
|
}
|
|
2143
|
-
if (
|
|
2811
|
+
if (Node10.isTypeLiteral(typeNode)) {
|
|
2144
2812
|
const members = [];
|
|
2145
2813
|
for (const member of typeNode.getMembers()) {
|
|
2146
|
-
if (
|
|
2814
|
+
if (Node10.isPropertySignature(member)) {
|
|
2147
2815
|
const memberTypeNode = member.getTypeNode();
|
|
2148
|
-
const
|
|
2149
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2816
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2817
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2150
2818
|
} else {
|
|
2151
2819
|
members.push(member.getText());
|
|
2152
2820
|
}
|
|
@@ -2154,11 +2822,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2154
2822
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2155
2823
|
}
|
|
2156
2824
|
const kind = typeNode.getKind();
|
|
2157
|
-
if (kind ===
|
|
2158
|
-
if (kind ===
|
|
2159
|
-
if (kind ===
|
|
2160
|
-
if (kind ===
|
|
2161
|
-
if (kind ===
|
|
2825
|
+
if (kind === SyntaxKind4.StringKeyword) return "string";
|
|
2826
|
+
if (kind === SyntaxKind4.NumberKeyword) return "number";
|
|
2827
|
+
if (kind === SyntaxKind4.BooleanKeyword) return "boolean";
|
|
2828
|
+
if (kind === SyntaxKind4.UnknownKeyword) return "unknown";
|
|
2829
|
+
if (kind === SyntaxKind4.AnyKeyword) return "unknown";
|
|
2162
2830
|
return typeNode.getText();
|
|
2163
2831
|
}
|
|
2164
2832
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2206,20 +2874,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2206
2874
|
const propName = prop.getName();
|
|
2207
2875
|
const isOptional = prop.hasQuestionToken();
|
|
2208
2876
|
const propTypeNode = prop.getTypeNode();
|
|
2209
|
-
let
|
|
2877
|
+
let propType2 = "unknown";
|
|
2210
2878
|
if (propTypeNode) {
|
|
2211
|
-
|
|
2879
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2212
2880
|
}
|
|
2213
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
2881
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2214
2882
|
}
|
|
2215
2883
|
return `{ ${lines.join("; ")} }`;
|
|
2216
2884
|
}
|
|
2885
|
+
function namedField(decorator) {
|
|
2886
|
+
const first = decorator.getArguments()[0];
|
|
2887
|
+
return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
2888
|
+
}
|
|
2217
2889
|
function extractBodyType(method, sourceFile, project) {
|
|
2218
2890
|
for (const param of method.getParameters()) {
|
|
2219
2891
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2220
|
-
if (!bodyDecorator) continue;
|
|
2221
|
-
const
|
|
2222
|
-
if (
|
|
2892
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
2893
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
2894
|
+
if (piped) return piped.type;
|
|
2223
2895
|
const typeNode = param.getTypeNode();
|
|
2224
2896
|
if (typeNode) {
|
|
2225
2897
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2227,11 +2899,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2227
2899
|
}
|
|
2228
2900
|
return null;
|
|
2229
2901
|
}
|
|
2902
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
2903
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
2904
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
2905
|
+
}
|
|
2230
2906
|
function extractQueryType(method, sourceFile, project) {
|
|
2231
2907
|
for (const param of method.getParameters()) {
|
|
2232
2908
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2233
|
-
if (!queryDecorator) continue;
|
|
2234
|
-
|
|
2909
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
2910
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
2911
|
+
if (piped) return piped.type;
|
|
2235
2912
|
const typeNode = param.getTypeNode();
|
|
2236
2913
|
if (typeNode) {
|
|
2237
2914
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2241,10 +2918,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2241
2918
|
for (const param of method.getParameters()) {
|
|
2242
2919
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2243
2920
|
if (!queryDecorator) continue;
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2921
|
+
const queryName = namedField(queryDecorator);
|
|
2922
|
+
if (queryName === null) continue;
|
|
2923
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
2924
|
+
if (piped) {
|
|
2925
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2248
2928
|
const typeNode = param.getTypeNode();
|
|
2249
2929
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2250
2930
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2254,8 +2934,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2254
2934
|
function isParamOptional(param) {
|
|
2255
2935
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2256
2936
|
const typeNode = param.getTypeNode();
|
|
2257
|
-
if (typeNode &&
|
|
2258
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
2937
|
+
if (typeNode && Node10.isUnionTypeNode(typeNode)) {
|
|
2938
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
|
|
2259
2939
|
}
|
|
2260
2940
|
return false;
|
|
2261
2941
|
}
|
|
@@ -2267,7 +2947,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2267
2947
|
const paramArgs = paramDecorator.getArguments();
|
|
2268
2948
|
if (paramArgs.length === 0) continue;
|
|
2269
2949
|
const nameArg = paramArgs[0];
|
|
2270
|
-
if (!
|
|
2950
|
+
if (!Node10.isStringLiteral(nameArg)) continue;
|
|
2271
2951
|
const paramName = nameArg.getLiteralValue();
|
|
2272
2952
|
const typeNode = param.getTypeNode();
|
|
2273
2953
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2275,6 +2955,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2275
2955
|
}
|
|
2276
2956
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2277
2957
|
}
|
|
2958
|
+
function extractPipedParamTypes(method, project) {
|
|
2959
|
+
const types = {};
|
|
2960
|
+
for (const param of method.getParameters()) {
|
|
2961
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
2962
|
+
if (!paramDecorator) continue;
|
|
2963
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
2964
|
+
if (!piped) continue;
|
|
2965
|
+
const name = namedField(paramDecorator);
|
|
2966
|
+
if (name !== null) {
|
|
2967
|
+
types[name] = piped.type;
|
|
2968
|
+
} else {
|
|
2969
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
2973
|
+
}
|
|
2278
2974
|
function extractUploadedFiles(method) {
|
|
2279
2975
|
const FILE = "File | Blob";
|
|
2280
2976
|
const entries = [];
|
|
@@ -2288,28 +2984,28 @@ function extractUploadedFiles(method) {
|
|
|
2288
2984
|
for (const decorator of method.getDecorators()) {
|
|
2289
2985
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2290
2986
|
for (const arg of decorator.getArguments()) {
|
|
2291
|
-
if (!
|
|
2987
|
+
if (!Node10.isCallExpression(arg)) continue;
|
|
2292
2988
|
const interceptor = arg.getExpression().getText();
|
|
2293
2989
|
const callArgs = arg.getArguments();
|
|
2294
2990
|
const firstArg2 = callArgs[0];
|
|
2295
2991
|
if (interceptor === "FileInterceptor") {
|
|
2296
|
-
if (firstArg2 &&
|
|
2992
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2297
2993
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2298
2994
|
multipart = true;
|
|
2299
2995
|
}
|
|
2300
2996
|
} else if (interceptor === "FilesInterceptor") {
|
|
2301
|
-
if (firstArg2 &&
|
|
2997
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2302
2998
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2303
2999
|
multipart = true;
|
|
2304
3000
|
}
|
|
2305
3001
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2306
|
-
if (firstArg2 &&
|
|
3002
|
+
if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
|
|
2307
3003
|
for (const el of firstArg2.getElements()) {
|
|
2308
|
-
if (!
|
|
3004
|
+
if (!Node10.isObjectLiteralExpression(el)) continue;
|
|
2309
3005
|
const nameProp = el.getProperty("name");
|
|
2310
|
-
if (nameProp &&
|
|
3006
|
+
if (nameProp && Node10.isPropertyAssignment(nameProp)) {
|
|
2311
3007
|
const init = nameProp.getInitializer();
|
|
2312
|
-
if (init &&
|
|
3008
|
+
if (init && Node10.isStringLiteral(init)) {
|
|
2313
3009
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2314
3010
|
}
|
|
2315
3011
|
}
|
|
@@ -2329,13 +3025,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2329
3025
|
if (apiResponseDecorator) {
|
|
2330
3026
|
const args = apiResponseDecorator.getArguments();
|
|
2331
3027
|
const optsArg = args[0];
|
|
2332
|
-
if (optsArg &&
|
|
3028
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2333
3029
|
for (const prop of optsArg.getProperties()) {
|
|
2334
|
-
if (!
|
|
3030
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2335
3031
|
if (prop.getName() !== "type") continue;
|
|
2336
3032
|
const val = prop.getInitializer();
|
|
2337
3033
|
if (!val) continue;
|
|
2338
|
-
if (
|
|
3034
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2339
3035
|
const elements = val.getElements();
|
|
2340
3036
|
const firstEl = elements[0];
|
|
2341
3037
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2356,24 +3052,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2356
3052
|
}
|
|
2357
3053
|
function apiResponseStatus(decorator) {
|
|
2358
3054
|
const optsArg = decorator.getArguments()[0];
|
|
2359
|
-
if (!optsArg || !
|
|
3055
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2360
3056
|
for (const prop of optsArg.getProperties()) {
|
|
2361
|
-
if (!
|
|
3057
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2362
3058
|
if (prop.getName() !== "status") continue;
|
|
2363
3059
|
const val = prop.getInitializer();
|
|
2364
|
-
if (val &&
|
|
3060
|
+
if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2365
3061
|
}
|
|
2366
3062
|
return null;
|
|
2367
3063
|
}
|
|
2368
3064
|
function apiResponseTypeNode(decorator) {
|
|
2369
3065
|
const optsArg = decorator.getArguments()[0];
|
|
2370
|
-
if (!optsArg || !
|
|
3066
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2371
3067
|
for (const prop of optsArg.getProperties()) {
|
|
2372
|
-
if (!
|
|
3068
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2373
3069
|
if (prop.getName() !== "type") continue;
|
|
2374
3070
|
const val = prop.getInitializer();
|
|
2375
3071
|
if (!val) return null;
|
|
2376
|
-
if (
|
|
3072
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2377
3073
|
const first = val.getElements()[0];
|
|
2378
3074
|
return first ? { node: first, isArray: true } : null;
|
|
2379
3075
|
}
|
|
@@ -2391,7 +3087,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2391
3087
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2392
3088
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2393
3089
|
let ref = null;
|
|
2394
|
-
if (
|
|
3090
|
+
if (Node10.isIdentifier(typeInfo.node)) {
|
|
2395
3091
|
const name = typeInfo.node.getText();
|
|
2396
3092
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2397
3093
|
if (localDecl?.isExported()) {
|
|
@@ -2408,7 +3104,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2408
3104
|
return null;
|
|
2409
3105
|
}
|
|
2410
3106
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2411
|
-
if (!
|
|
3107
|
+
if (!Node10.isIdentifier(node)) return "unknown";
|
|
2412
3108
|
const name = node.getText();
|
|
2413
3109
|
const resolved = findType(name, sourceFile, project);
|
|
2414
3110
|
if (resolved) {
|
|
@@ -2428,9 +3124,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2428
3124
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2429
3125
|
function detectBinaryResponse(method) {
|
|
2430
3126
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2431
|
-
if (!node || !
|
|
3127
|
+
if (!node || !Node10.isTypeReference(node)) return false;
|
|
2432
3128
|
const typeName = node.getTypeName();
|
|
2433
|
-
const name =
|
|
3129
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2434
3130
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2435
3131
|
}
|
|
2436
3132
|
function detectStreamElement(method) {
|
|
@@ -2445,9 +3141,9 @@ function detectStreamElement(method) {
|
|
|
2445
3141
|
return null;
|
|
2446
3142
|
}
|
|
2447
3143
|
function streamContainerElement(node) {
|
|
2448
|
-
if (!node || !
|
|
3144
|
+
if (!node || !Node10.isTypeReference(node)) return null;
|
|
2449
3145
|
const typeName = node.getTypeName();
|
|
2450
|
-
const name =
|
|
3146
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2451
3147
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2452
3148
|
return node.getTypeArguments()[0] ?? null;
|
|
2453
3149
|
}
|
|
@@ -2457,9 +3153,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2457
3153
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2458
3154
|
}
|
|
2459
3155
|
function unwrapNamedContainer(node, names) {
|
|
2460
|
-
if (!node || !
|
|
3156
|
+
if (!node || !Node10.isTypeReference(node)) return node;
|
|
2461
3157
|
const typeName = node.getTypeName();
|
|
2462
|
-
const name =
|
|
3158
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2463
3159
|
if (names.has(name)) {
|
|
2464
3160
|
return node.getTypeArguments()[0] ?? node;
|
|
2465
3161
|
}
|
|
@@ -2480,19 +3176,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2480
3176
|
body = body ?? bodyType;
|
|
2481
3177
|
}
|
|
2482
3178
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3179
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2483
3180
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2484
3181
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
2485
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3182
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2486
3183
|
return null;
|
|
2487
3184
|
}
|
|
2488
3185
|
let bodyRef = null;
|
|
2489
3186
|
let queryRef = null;
|
|
2490
3187
|
let responseRef = null;
|
|
2491
3188
|
for (const param of method.getParameters()) {
|
|
2492
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3189
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2493
3190
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2494
3191
|
}
|
|
2495
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3192
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2496
3193
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2497
3194
|
}
|
|
2498
3195
|
}
|
|
@@ -2505,11 +3202,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2505
3202
|
if (apiResp) {
|
|
2506
3203
|
const args = apiResp.getArguments();
|
|
2507
3204
|
const optsArg = args[0];
|
|
2508
|
-
if (optsArg &&
|
|
3205
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2509
3206
|
for (const prop of optsArg.getProperties()) {
|
|
2510
|
-
if (
|
|
3207
|
+
if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2511
3208
|
const val = prop.getInitializer();
|
|
2512
|
-
if (val &&
|
|
3209
|
+
if (val && Node10.isIdentifier(val)) {
|
|
2513
3210
|
const name = val.getText();
|
|
2514
3211
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2515
3212
|
if (localDecl?.isExported()) {
|
|
@@ -2545,6 +3242,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2545
3242
|
response,
|
|
2546
3243
|
error: errorInfo?.type ?? null,
|
|
2547
3244
|
params: paramsType,
|
|
3245
|
+
paramTypes,
|
|
2548
3246
|
queryRef,
|
|
2549
3247
|
bodyRef,
|
|
2550
3248
|
responseRef,
|
|
@@ -2565,6 +3263,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2565
3263
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2566
3264
|
for (const param of method.getParameters()) {
|
|
2567
3265
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3266
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2568
3267
|
const typeNode = param.getTypeNode();
|
|
2569
3268
|
if (!typeNode) continue;
|
|
2570
3269
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2577,129 +3276,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2577
3276
|
return null;
|
|
2578
3277
|
}
|
|
2579
3278
|
|
|
2580
|
-
// src/discovery/zod-ast-to-ts.ts
|
|
2581
|
-
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
2582
|
-
function zodAstToTs(node) {
|
|
2583
|
-
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2584
|
-
const expr = node.getExpression();
|
|
2585
|
-
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2586
|
-
const methodName = expr.getName();
|
|
2587
|
-
const receiver = expr.getExpression();
|
|
2588
|
-
if (methodName === "optional") {
|
|
2589
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
2590
|
-
}
|
|
2591
|
-
if (methodName === "nullable") {
|
|
2592
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
2593
|
-
}
|
|
2594
|
-
const args = node.getArguments();
|
|
2595
|
-
switch (methodName) {
|
|
2596
|
-
case "string":
|
|
2597
|
-
return "string";
|
|
2598
|
-
case "number":
|
|
2599
|
-
return "number";
|
|
2600
|
-
case "boolean":
|
|
2601
|
-
return "boolean";
|
|
2602
|
-
case "unknown":
|
|
2603
|
-
return "unknown";
|
|
2604
|
-
case "any":
|
|
2605
|
-
return "unknown";
|
|
2606
|
-
case "literal": {
|
|
2607
|
-
const lit = args[0];
|
|
2608
|
-
if (!lit) return "unknown";
|
|
2609
|
-
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2610
|
-
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2611
|
-
if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
|
|
2612
|
-
if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
|
|
2613
|
-
return "unknown";
|
|
2614
|
-
}
|
|
2615
|
-
case "enum": {
|
|
2616
|
-
const arrArg = args[0];
|
|
2617
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2618
|
-
const members = arrArg.getElements().map(
|
|
2619
|
-
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2620
|
-
);
|
|
2621
|
-
return members.join(" | ");
|
|
2622
|
-
}
|
|
2623
|
-
case "array": {
|
|
2624
|
-
const inner = args[0];
|
|
2625
|
-
if (!inner) return "unknown";
|
|
2626
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
2627
|
-
}
|
|
2628
|
-
case "object": {
|
|
2629
|
-
const objArg = args[0];
|
|
2630
|
-
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2631
|
-
const lines = [];
|
|
2632
|
-
for (const prop of objArg.getProperties()) {
|
|
2633
|
-
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2634
|
-
const key = prop.getName();
|
|
2635
|
-
const valNode = prop.getInitializer();
|
|
2636
|
-
if (!valNode) continue;
|
|
2637
|
-
const tsType = zodAstToTs(valNode);
|
|
2638
|
-
const isOpt = isOptionalChain(valNode);
|
|
2639
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2640
|
-
}
|
|
2641
|
-
return `{ ${lines.join("; ")} }`;
|
|
2642
|
-
}
|
|
2643
|
-
case "union": {
|
|
2644
|
-
const arrArg = args[0];
|
|
2645
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2646
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2647
|
-
}
|
|
2648
|
-
case "record": {
|
|
2649
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2650
|
-
if (!valArg) return "unknown";
|
|
2651
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2652
|
-
}
|
|
2653
|
-
case "tuple": {
|
|
2654
|
-
const arrArg = args[0];
|
|
2655
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2656
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2657
|
-
}
|
|
2658
|
-
default:
|
|
2659
|
-
return "unknown";
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
return "unknown";
|
|
2663
|
-
}
|
|
2664
|
-
function isOptionalChain(node) {
|
|
2665
|
-
if (!Node8.isCallExpression(node)) return false;
|
|
2666
|
-
const expr = node.getExpression();
|
|
2667
|
-
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2668
|
-
}
|
|
2669
|
-
function parseDefineContractCall(callExpr) {
|
|
2670
|
-
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2671
|
-
const callee = callExpr.getExpression();
|
|
2672
|
-
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2673
|
-
if (calleeName !== "defineContract") return null;
|
|
2674
|
-
const args = callExpr.getArguments();
|
|
2675
|
-
const optsArg = args[0];
|
|
2676
|
-
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2677
|
-
let query = null;
|
|
2678
|
-
let body = null;
|
|
2679
|
-
let response = "unknown";
|
|
2680
|
-
let error = null;
|
|
2681
|
-
let bodyZodText = null;
|
|
2682
|
-
let queryZodText = null;
|
|
2683
|
-
for (const prop of optsArg.getProperties()) {
|
|
2684
|
-
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2685
|
-
const propName = prop.getName();
|
|
2686
|
-
const val = prop.getInitializer();
|
|
2687
|
-
if (!val) continue;
|
|
2688
|
-
if (propName === "query") {
|
|
2689
|
-
query = zodAstToTs(val);
|
|
2690
|
-
queryZodText = val.getText();
|
|
2691
|
-
} else if (propName === "body") {
|
|
2692
|
-
body = zodAstToTs(val);
|
|
2693
|
-
bodyZodText = val.getText();
|
|
2694
|
-
} else if (propName === "response") {
|
|
2695
|
-
response = zodAstToTs(val);
|
|
2696
|
-
} else if (propName === "error") {
|
|
2697
|
-
error = zodAstToTs(val);
|
|
2698
|
-
}
|
|
2699
|
-
}
|
|
2700
|
-
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2701
|
-
}
|
|
2702
|
-
|
|
2703
3279
|
// src/discovery/contracts-fast.ts
|
|
2704
3280
|
async function discoverContractsFast(opts) {
|
|
2705
3281
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2712,7 +3288,9 @@ async function discoverContractsFast(opts) {
|
|
|
2712
3288
|
}
|
|
2713
3289
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2714
3290
|
clearMixinTypeProject();
|
|
2715
|
-
|
|
3291
|
+
const routes = extractAllRoutes(project);
|
|
3292
|
+
clearSchemaTypeProject(project);
|
|
3293
|
+
return routes;
|
|
2716
3294
|
}
|
|
2717
3295
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2718
3296
|
return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
|
|
@@ -2724,25 +3302,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2724
3302
|
};
|
|
2725
3303
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2726
3304
|
if (!existsSync(tsconfigPath)) return { options: null, files: [] };
|
|
2727
|
-
const read =
|
|
3305
|
+
const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
2728
3306
|
if (read.error) {
|
|
2729
3307
|
return {
|
|
2730
3308
|
options: null,
|
|
2731
3309
|
files: [tsconfigPath],
|
|
2732
|
-
error:
|
|
3310
|
+
error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2733
3311
|
};
|
|
2734
3312
|
}
|
|
2735
3313
|
const host = {
|
|
2736
|
-
useCaseSensitiveFileNames:
|
|
3314
|
+
useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
|
|
2737
3315
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2738
3316
|
readDirectory: () => [],
|
|
2739
|
-
fileExists: (path) =>
|
|
3317
|
+
fileExists: (path) => ts3.sys.fileExists(path),
|
|
2740
3318
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2741
3319
|
// in a base tsconfig keeps working.
|
|
2742
|
-
readFile: (path) =>
|
|
3320
|
+
readFile: (path) => ts3.sys.readFile(path)
|
|
2743
3321
|
};
|
|
2744
|
-
const sourceFile =
|
|
2745
|
-
const parsed =
|
|
3322
|
+
const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
3323
|
+
const parsed = ts3.parseJsonSourceFileConfigFileContent(
|
|
2746
3324
|
sourceFile,
|
|
2747
3325
|
host,
|
|
2748
3326
|
dirname2(tsconfigPath),
|
|
@@ -2765,7 +3343,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2765
3343
|
`[nestjs-codegen/fast] Could not load ${tsconfigPath}: ${tsconfig.error} \u2014 continuing WITHOUT its compiler options, so path alias imports (e.g. '@/...') will not resolve and controllers extending an alias-imported factory will contribute NO routes to the generated client.`
|
|
2766
3344
|
);
|
|
2767
3345
|
}
|
|
2768
|
-
return new
|
|
3346
|
+
return new Project4({
|
|
2769
3347
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2770
3348
|
skipAddingFilesFromTsConfig: true,
|
|
2771
3349
|
skipLoadingLibFiles: true,
|
|
@@ -2877,15 +3455,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2877
3455
|
clearTypeResolutionCaches(this.project);
|
|
2878
3456
|
clearEnumCache(this.project);
|
|
2879
3457
|
clearMixinTypeProject();
|
|
3458
|
+
clearSchemaTypeProject(this.project);
|
|
2880
3459
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2881
3460
|
}
|
|
2882
3461
|
};
|
|
2883
3462
|
function decoratorStringArg(decoratorExpr) {
|
|
2884
3463
|
if (!decoratorExpr) return void 0;
|
|
2885
|
-
if (
|
|
2886
|
-
if (
|
|
3464
|
+
if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3465
|
+
if (Node11.isArrayLiteralExpression(decoratorExpr)) {
|
|
2887
3466
|
const first = decoratorExpr.getElements()[0];
|
|
2888
|
-
if (first &&
|
|
3467
|
+
if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
|
|
2889
3468
|
}
|
|
2890
3469
|
return void 0;
|
|
2891
3470
|
}
|
|
@@ -3006,9 +3585,9 @@ function extractContractRoute(args) {
|
|
|
3006
3585
|
let contractDef = null;
|
|
3007
3586
|
let bodyZodRef = null;
|
|
3008
3587
|
let queryZodRef = null;
|
|
3009
|
-
if (
|
|
3010
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3011
|
-
} else if (
|
|
3588
|
+
if (Node11.isCallExpression(firstDecoratorArg)) {
|
|
3589
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3590
|
+
} else if (Node11.isIdentifier(firstDecoratorArg)) {
|
|
3012
3591
|
const identName = firstDecoratorArg.getText();
|
|
3013
3592
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3014
3593
|
if (!resolvedVar) {
|
|
@@ -3020,7 +3599,7 @@ function extractContractRoute(args) {
|
|
|
3020
3599
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3021
3600
|
const initializer = varDecl.getInitializer();
|
|
3022
3601
|
if (!initializer) return null;
|
|
3023
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3602
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3024
3603
|
if (contractDef && varDecl.isExported()) {
|
|
3025
3604
|
const filePath = declFile.getFilePath();
|
|
3026
3605
|
if (contractDef.body !== null) {
|
|
@@ -3091,6 +3670,7 @@ function extractDtoRoute(args) {
|
|
|
3091
3670
|
body: dtoContract?.body ?? null,
|
|
3092
3671
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3093
3672
|
error: dtoContract?.error ?? null,
|
|
3673
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3094
3674
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3095
3675
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3096
3676
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3213,7 +3793,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3213
3793
|
},
|
|
3214
3794
|
project() {
|
|
3215
3795
|
if (!project) {
|
|
3216
|
-
project = new
|
|
3796
|
+
project = new Project5({
|
|
3217
3797
|
skipAddingFilesFromTsConfig: true,
|
|
3218
3798
|
skipLoadingLibFiles: true,
|
|
3219
3799
|
skipFileDependencyResolution: true,
|
|
@@ -3331,10 +3911,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3331
3911
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3332
3912
|
}
|
|
3333
3913
|
}
|
|
3334
|
-
function buildParamsType(params) {
|
|
3914
|
+
function buildParamsType(params, paramTypes) {
|
|
3335
3915
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3336
3916
|
if (pathParams.length === 0) return "never";
|
|
3337
|
-
|
|
3917
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
3918
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3338
3919
|
}
|
|
3339
3920
|
function hasPathParams(params) {
|
|
3340
3921
|
return params.some((p) => p.source === "path");
|
|
@@ -3428,7 +4009,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3428
4009
|
}
|
|
3429
4010
|
const response = buildResponseType(c, outDir, serialization);
|
|
3430
4011
|
const error = buildErrorType(c);
|
|
3431
|
-
const params = buildParamsType(c.params);
|
|
4012
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3432
4013
|
const safeMethod = JSON.stringify(method);
|
|
3433
4014
|
const safeUrl = JSON.stringify(c.path);
|
|
3434
4015
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3697,6 +4278,9 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3697
4278
|
"}",
|
|
3698
4279
|
""
|
|
3699
4280
|
];
|
|
4281
|
+
function isClientCallable(route) {
|
|
4282
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4283
|
+
}
|
|
3700
4284
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3701
4285
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3702
4286
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3704,7 +4288,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3704
4288
|
const { layer } = resolveApiSlots(extensions);
|
|
3705
4289
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3706
4290
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3707
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4291
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3708
4292
|
const ctx = opts.ctx ?? {
|
|
3709
4293
|
cwd: outDir ?? "",
|
|
3710
4294
|
outDir: outDir ?? "",
|
|
@@ -4622,6 +5206,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4622
5206
|
const paths = {};
|
|
4623
5207
|
for (const route of routes) {
|
|
4624
5208
|
if (!route.contract) continue;
|
|
5209
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4625
5210
|
const oaPath = toOpenApiPath(route.path);
|
|
4626
5211
|
const method = route.method.toLowerCase();
|
|
4627
5212
|
let pathItem = paths[oaPath];
|
|
@@ -5281,7 +5866,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
5281
5866
|
}
|
|
5282
5867
|
|
|
5283
5868
|
// src/index.ts
|
|
5284
|
-
var VERSION = "0.
|
|
5869
|
+
var VERSION = "0.26.0";
|
|
5285
5870
|
|
|
5286
5871
|
// src/cli/codegen.ts
|
|
5287
5872
|
async function runCodegen(opts = {}) {
|