@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.cjs
CHANGED
|
@@ -552,16 +552,16 @@ var import_node_path7 = require("path");
|
|
|
552
552
|
|
|
553
553
|
// src/extension/registry.ts
|
|
554
554
|
var import_node_path6 = require("path");
|
|
555
|
-
var
|
|
555
|
+
var import_ts_morph13 = require("ts-morph");
|
|
556
556
|
|
|
557
557
|
// src/discovery/contracts-fast.ts
|
|
558
558
|
var import_node_fs = require("fs");
|
|
559
559
|
var import_node_path5 = require("path");
|
|
560
560
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
561
|
-
var
|
|
561
|
+
var import_ts_morph12 = require("ts-morph");
|
|
562
562
|
|
|
563
563
|
// src/discovery/dto-type-resolver.ts
|
|
564
|
-
var
|
|
564
|
+
var import_ts_morph11 = require("ts-morph");
|
|
565
565
|
|
|
566
566
|
// src/discovery/dto-to-ir.ts
|
|
567
567
|
var import_ts_morph3 = require("ts-morph");
|
|
@@ -2089,6 +2089,672 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2089
2089
|
return fields;
|
|
2090
2090
|
}
|
|
2091
2091
|
|
|
2092
|
+
// src/discovery/pipe-schema.ts
|
|
2093
|
+
var import_ts_morph10 = require("ts-morph");
|
|
2094
|
+
|
|
2095
|
+
// src/discovery/schema-type-inference.ts
|
|
2096
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2097
|
+
|
|
2098
|
+
// src/discovery/schema-type-program.ts
|
|
2099
|
+
var import_ts_morph7 = require("ts-morph");
|
|
2100
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2101
|
+
var SchemaTypeProgram = class {
|
|
2102
|
+
constructor(discovery) {
|
|
2103
|
+
this.discovery = discovery;
|
|
2104
|
+
const base = discovery.getCompilerOptions();
|
|
2105
|
+
this.options = {
|
|
2106
|
+
...base,
|
|
2107
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2108
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2109
|
+
target: base.target ?? import_ts_morph7.ts.ScriptTarget.ES2022,
|
|
2110
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2111
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2112
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: import_ts_morph7.ts.ModuleKind.ESNext, moduleResolution: import_ts_morph7.ts.ModuleResolutionKind.Bundler } : {},
|
|
2113
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2114
|
+
strictNullChecks: true,
|
|
2115
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2116
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2117
|
+
types: [],
|
|
2118
|
+
noEmit: true
|
|
2119
|
+
};
|
|
2120
|
+
this.project = new import_ts_morph7.Project({
|
|
2121
|
+
skipAddingFilesFromTsConfig: true,
|
|
2122
|
+
compilerOptions: this.options
|
|
2123
|
+
});
|
|
2124
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2125
|
+
this.flush();
|
|
2126
|
+
}
|
|
2127
|
+
project;
|
|
2128
|
+
options;
|
|
2129
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2130
|
+
scratch = new import_ts_morph7.Project({
|
|
2131
|
+
skipAddingFilesFromTsConfig: true,
|
|
2132
|
+
skipLoadingLibFiles: true,
|
|
2133
|
+
skipFileDependencyResolution: true
|
|
2134
|
+
});
|
|
2135
|
+
needs = /* @__PURE__ */ new Map();
|
|
2136
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2137
|
+
written = /* @__PURE__ */ new Map();
|
|
2138
|
+
/**
|
|
2139
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2140
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2141
|
+
* program once.
|
|
2142
|
+
*/
|
|
2143
|
+
sourceFileFor(sf) {
|
|
2144
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2145
|
+
this.flush();
|
|
2146
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2147
|
+
}
|
|
2148
|
+
// -------------------------------------------------------------------------
|
|
2149
|
+
queue = [];
|
|
2150
|
+
request(path, want) {
|
|
2151
|
+
let needs = this.needs.get(path);
|
|
2152
|
+
const isNew = !needs;
|
|
2153
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2154
|
+
let grew = isNew;
|
|
2155
|
+
if (want.schema && !needs.schema) {
|
|
2156
|
+
needs.schema = true;
|
|
2157
|
+
grew = true;
|
|
2158
|
+
}
|
|
2159
|
+
if (want.all && !needs.all) {
|
|
2160
|
+
needs.all = true;
|
|
2161
|
+
grew = true;
|
|
2162
|
+
}
|
|
2163
|
+
for (const name of want.names ?? []) {
|
|
2164
|
+
if (!needs.names.has(name)) {
|
|
2165
|
+
needs.names.add(name);
|
|
2166
|
+
grew = true;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
this.needs.set(path, needs);
|
|
2170
|
+
if (grew) this.queue.push(path);
|
|
2171
|
+
}
|
|
2172
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2173
|
+
flush() {
|
|
2174
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2175
|
+
while (this.queue.length > 0) {
|
|
2176
|
+
const path = this.queue.pop();
|
|
2177
|
+
const original = this.original(path);
|
|
2178
|
+
if (!original) continue;
|
|
2179
|
+
const needs = this.needs.get(path);
|
|
2180
|
+
const text = this.prune(original, needs);
|
|
2181
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2182
|
+
}
|
|
2183
|
+
for (const [path, text] of changed) {
|
|
2184
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2185
|
+
this.written.set(path, text);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2189
|
+
original(path) {
|
|
2190
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2194
|
+
* requesting from each kept one's target the names it brings in.
|
|
2195
|
+
*/
|
|
2196
|
+
prune(sf, needs) {
|
|
2197
|
+
const locals = topLevelDeclarations(sf);
|
|
2198
|
+
const roots = [];
|
|
2199
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2200
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2201
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2202
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2203
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2204
|
+
if (spec === void 0) {
|
|
2205
|
+
for (const named of exp.getNamedExports()) {
|
|
2206
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2207
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2208
|
+
}
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2212
|
+
for (const named of exp.getNamedExports()) {
|
|
2213
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2214
|
+
const list = reexported.get(exposed) ?? [];
|
|
2215
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2216
|
+
reexported.set(exposed, list);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
const unresolvedNames = [];
|
|
2220
|
+
for (const name of needs.names) {
|
|
2221
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2222
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2223
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2224
|
+
}
|
|
2225
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2226
|
+
const queue = [...roots];
|
|
2227
|
+
const seen = new Set(queue);
|
|
2228
|
+
while (queue.length > 0) {
|
|
2229
|
+
const node = queue.pop();
|
|
2230
|
+
const ids = import_ts_morph7.Node.isIdentifier(node) ? [node] : node.getDescendantsOfKind(import_ts_morph7.ts.SyntaxKind.Identifier);
|
|
2231
|
+
for (const id of ids) {
|
|
2232
|
+
const name = id.getText();
|
|
2233
|
+
if (referenced.has(name)) continue;
|
|
2234
|
+
referenced.add(name);
|
|
2235
|
+
for (const local of locals.get(name) ?? []) {
|
|
2236
|
+
if (seen.has(local)) continue;
|
|
2237
|
+
seen.add(local);
|
|
2238
|
+
queue.push(local);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
const chars = sf.getFullText().split("");
|
|
2243
|
+
const blank = (node) => {
|
|
2244
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2245
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2249
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2250
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2251
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2252
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2253
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2254
|
+
imported: n.getName()
|
|
2255
|
+
})).filter((n) => referenced.has(n.local));
|
|
2256
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2257
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2258
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2259
|
+
blank(decl);
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (target) {
|
|
2263
|
+
this.request(target, {
|
|
2264
|
+
all: wantsNamespace,
|
|
2265
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2270
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2271
|
+
if (spec === void 0) continue;
|
|
2272
|
+
const target = this.resolve(spec, sf);
|
|
2273
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2274
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2275
|
+
blank(exp);
|
|
2276
|
+
} else if (target) {
|
|
2277
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2278
|
+
}
|
|
2279
|
+
continue;
|
|
2280
|
+
}
|
|
2281
|
+
const wanted = exp.getNamedExports().filter(
|
|
2282
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2283
|
+
).map((n) => n.getNameNode().getText());
|
|
2284
|
+
if (wanted.length === 0) blank(exp);
|
|
2285
|
+
else if (target) this.request(target, { names: wanted });
|
|
2286
|
+
}
|
|
2287
|
+
return chars.join("");
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2291
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2292
|
+
*/
|
|
2293
|
+
resolve(specifier, from) {
|
|
2294
|
+
const resolved = import_ts_morph7.ts.resolveModuleName(
|
|
2295
|
+
specifier,
|
|
2296
|
+
from.getFilePath(),
|
|
2297
|
+
this.options,
|
|
2298
|
+
import_ts_morph7.ts.sys
|
|
2299
|
+
).resolvedModule;
|
|
2300
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2301
|
+
const file = resolved.resolvedFileName;
|
|
2302
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2303
|
+
return file;
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
function schemaPositions(sf) {
|
|
2307
|
+
const roots = [];
|
|
2308
|
+
sf.forEachDescendant((node) => {
|
|
2309
|
+
if (import_ts_morph7.Node.isDecorator(node)) {
|
|
2310
|
+
const name = node.getName();
|
|
2311
|
+
if (name === "ApplyContract") {
|
|
2312
|
+
roots.push(...node.getArguments());
|
|
2313
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2314
|
+
for (const arg of node.getArguments()) {
|
|
2315
|
+
if (import_ts_morph7.Node.isNewExpression(arg) || import_ts_morph7.Node.isCallExpression(arg)) {
|
|
2316
|
+
roots.push(...arg.getArguments());
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
} else if (import_ts_morph7.Node.isCallExpression(node)) {
|
|
2321
|
+
const callee = node.getExpression();
|
|
2322
|
+
const name = import_ts_morph7.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph7.Node.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2323
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2324
|
+
}
|
|
2325
|
+
});
|
|
2326
|
+
return roots;
|
|
2327
|
+
}
|
|
2328
|
+
function topLevelDeclarations(sf) {
|
|
2329
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2330
|
+
const add = (name, node) => {
|
|
2331
|
+
if (!name) return;
|
|
2332
|
+
const list = byName.get(name);
|
|
2333
|
+
if (list) list.push(node);
|
|
2334
|
+
else byName.set(name, [node]);
|
|
2335
|
+
};
|
|
2336
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2337
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2338
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2339
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2340
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2341
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2342
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2343
|
+
return byName;
|
|
2344
|
+
}
|
|
2345
|
+
function defaultExport(sf) {
|
|
2346
|
+
const nodes = [];
|
|
2347
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2348
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2349
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2350
|
+
return nodes;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
// src/discovery/schema-type-inference.ts
|
|
2354
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2355
|
+
function programFor(discovery) {
|
|
2356
|
+
let program = programs.get(discovery);
|
|
2357
|
+
if (!program) {
|
|
2358
|
+
program = new SchemaTypeProgram(discovery);
|
|
2359
|
+
programs.set(discovery, program);
|
|
2360
|
+
}
|
|
2361
|
+
return program;
|
|
2362
|
+
}
|
|
2363
|
+
function clearSchemaTypeProject(discovery) {
|
|
2364
|
+
programs.delete(discovery);
|
|
2365
|
+
}
|
|
2366
|
+
function counterpart(typedSf, node) {
|
|
2367
|
+
const start = node.getStart();
|
|
2368
|
+
const end = node.getEnd();
|
|
2369
|
+
const kind = node.getKind();
|
|
2370
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2371
|
+
while (candidate) {
|
|
2372
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2373
|
+
return candidate;
|
|
2374
|
+
}
|
|
2375
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2376
|
+
candidate = candidate.getParent();
|
|
2377
|
+
}
|
|
2378
|
+
return void 0;
|
|
2379
|
+
}
|
|
2380
|
+
function propType(type, name, at) {
|
|
2381
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2382
|
+
}
|
|
2383
|
+
function isAnyOrUnknown(type) {
|
|
2384
|
+
return type.isAny() || type.isUnknown();
|
|
2385
|
+
}
|
|
2386
|
+
function inferSchema(expr, discovery) {
|
|
2387
|
+
try {
|
|
2388
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2389
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2390
|
+
if (!at) return { kind: "unresolved" };
|
|
2391
|
+
const schemaType = at.getType();
|
|
2392
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2393
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2394
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2395
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2396
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2397
|
+
if (stdInput && stdOutput) {
|
|
2398
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2399
|
+
}
|
|
2400
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2401
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2402
|
+
if (zodInput && zodOutput) {
|
|
2403
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2404
|
+
}
|
|
2405
|
+
return { kind: "not-schema" };
|
|
2406
|
+
} catch (err) {
|
|
2407
|
+
dbg("schema inference failed:", err);
|
|
2408
|
+
return { kind: "unresolved" };
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2412
|
+
"Date",
|
|
2413
|
+
"File",
|
|
2414
|
+
"Blob",
|
|
2415
|
+
"FormData",
|
|
2416
|
+
"URL",
|
|
2417
|
+
"RegExp",
|
|
2418
|
+
"Map",
|
|
2419
|
+
"Set",
|
|
2420
|
+
"ArrayBuffer",
|
|
2421
|
+
"Uint8Array"
|
|
2422
|
+
]);
|
|
2423
|
+
var MAX_DEPTH = 12;
|
|
2424
|
+
function objectKey(name) {
|
|
2425
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2426
|
+
}
|
|
2427
|
+
function isOptionalSymbol(sym) {
|
|
2428
|
+
return (sym.getFlags() & import_ts_morph8.ts.SymbolFlags.Optional) !== 0;
|
|
2429
|
+
}
|
|
2430
|
+
function isSymbolKeyed(sym) {
|
|
2431
|
+
return sym.getName().startsWith("__@");
|
|
2432
|
+
}
|
|
2433
|
+
function orderedProperties(type) {
|
|
2434
|
+
const position = (sym) => {
|
|
2435
|
+
const decl = sym.getDeclarations()[0];
|
|
2436
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2437
|
+
};
|
|
2438
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2439
|
+
if (a.at && b.at) {
|
|
2440
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2441
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2442
|
+
} else if (a.at || b.at) {
|
|
2443
|
+
return a.at ? -1 : 1;
|
|
2444
|
+
}
|
|
2445
|
+
return a.index - b.index;
|
|
2446
|
+
}).map((entry) => entry.sym);
|
|
2447
|
+
}
|
|
2448
|
+
function isFromDeclarationFile(sym) {
|
|
2449
|
+
const decl = sym?.getDeclarations()[0];
|
|
2450
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2451
|
+
}
|
|
2452
|
+
function render(type, state, paired) {
|
|
2453
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2454
|
+
if (isAnyOrUnknown(type)) {
|
|
2455
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2456
|
+
}
|
|
2457
|
+
if (type.isNever()) return "never";
|
|
2458
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2459
|
+
if (type.isNumber()) return "number";
|
|
2460
|
+
if (type.isBoolean()) return "boolean";
|
|
2461
|
+
if (type.isBigInt()) return "bigint";
|
|
2462
|
+
if (type.isNull()) return "null";
|
|
2463
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2464
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2465
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2466
|
+
const value = type.getLiteralValue();
|
|
2467
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2468
|
+
}
|
|
2469
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2470
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2471
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2472
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2473
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2474
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2475
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2476
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2477
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2478
|
+
}
|
|
2479
|
+
if (type.isTuple()) {
|
|
2480
|
+
const elements = type.getTupleElements();
|
|
2481
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2482
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2483
|
+
}
|
|
2484
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2485
|
+
return "unknown";
|
|
2486
|
+
}
|
|
2487
|
+
function renderUnion(type, state, paired) {
|
|
2488
|
+
const members = type.getUnionTypes();
|
|
2489
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2490
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2491
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2492
|
+
const arms = [];
|
|
2493
|
+
let sawBoolean = false;
|
|
2494
|
+
let hasNull = false;
|
|
2495
|
+
let hasUndefined = false;
|
|
2496
|
+
for (const member of members) {
|
|
2497
|
+
if (member.isNull()) {
|
|
2498
|
+
hasNull = true;
|
|
2499
|
+
continue;
|
|
2500
|
+
}
|
|
2501
|
+
if (member.isUndefined()) {
|
|
2502
|
+
hasUndefined = true;
|
|
2503
|
+
continue;
|
|
2504
|
+
}
|
|
2505
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2506
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2507
|
+
sawBoolean = true;
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2511
|
+
}
|
|
2512
|
+
if (hasNull) arms.push("null");
|
|
2513
|
+
if (hasUndefined) arms.push("undefined");
|
|
2514
|
+
const unique = [...new Set(arms)];
|
|
2515
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2516
|
+
return unique.join(" | ");
|
|
2517
|
+
}
|
|
2518
|
+
function renderIntersection(type, state) {
|
|
2519
|
+
const members = type.getIntersectionTypes();
|
|
2520
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2521
|
+
if (primitive) return render(primitive, state);
|
|
2522
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2523
|
+
}
|
|
2524
|
+
function renderObject(type, state, paired) {
|
|
2525
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2526
|
+
const name = symbol?.getName();
|
|
2527
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2528
|
+
const args = type.getTypeArguments();
|
|
2529
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2530
|
+
}
|
|
2531
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2532
|
+
return "unknown";
|
|
2533
|
+
}
|
|
2534
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2535
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2536
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2537
|
+
const inner = { ...state, stack };
|
|
2538
|
+
const fields = [];
|
|
2539
|
+
for (const prop of orderedProperties(type)) {
|
|
2540
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2541
|
+
const propName = prop.getName();
|
|
2542
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2543
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2544
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2545
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2546
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2547
|
+
}
|
|
2548
|
+
const stringIndex = type.getStringIndexType();
|
|
2549
|
+
if (stringIndex) {
|
|
2550
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2551
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2552
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2553
|
+
fields.push(`[key: string]: ${value}`);
|
|
2554
|
+
}
|
|
2555
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2556
|
+
}
|
|
2557
|
+
function stateAt(at) {
|
|
2558
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2559
|
+
}
|
|
2560
|
+
function requestTypeOf(types) {
|
|
2561
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2562
|
+
}
|
|
2563
|
+
function responseTypeOf(types) {
|
|
2564
|
+
return render(types.output, stateAt(types.at));
|
|
2565
|
+
}
|
|
2566
|
+
function requestFieldsOf(types) {
|
|
2567
|
+
const { input, output, at } = types;
|
|
2568
|
+
const objectInput = input.getNonNullableType();
|
|
2569
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2570
|
+
const state = stateAt(at);
|
|
2571
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2572
|
+
name: prop.getName(),
|
|
2573
|
+
type: render(
|
|
2574
|
+
prop.getTypeAtLocation(at),
|
|
2575
|
+
state,
|
|
2576
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2577
|
+
),
|
|
2578
|
+
optional: isOptionalSymbol(prop)
|
|
2579
|
+
}));
|
|
2580
|
+
}
|
|
2581
|
+
function acceptsUndefined(types) {
|
|
2582
|
+
const { input, output } = types;
|
|
2583
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2584
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2585
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2589
|
+
var import_ts_morph9 = require("ts-morph");
|
|
2590
|
+
function zodAstToTs(node) {
|
|
2591
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
|
|
2592
|
+
const expr = node.getExpression();
|
|
2593
|
+
if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
|
|
2594
|
+
const methodName = expr.getName();
|
|
2595
|
+
const receiver = expr.getExpression();
|
|
2596
|
+
if (methodName === "optional") {
|
|
2597
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2598
|
+
}
|
|
2599
|
+
if (methodName === "nullable") {
|
|
2600
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2601
|
+
}
|
|
2602
|
+
const args = node.getArguments();
|
|
2603
|
+
switch (methodName) {
|
|
2604
|
+
case "string":
|
|
2605
|
+
return "string";
|
|
2606
|
+
case "number":
|
|
2607
|
+
return "number";
|
|
2608
|
+
case "boolean":
|
|
2609
|
+
return "boolean";
|
|
2610
|
+
case "unknown":
|
|
2611
|
+
return "unknown";
|
|
2612
|
+
case "any":
|
|
2613
|
+
return "unknown";
|
|
2614
|
+
case "literal": {
|
|
2615
|
+
const lit = args[0];
|
|
2616
|
+
if (!lit) return "unknown";
|
|
2617
|
+
if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2618
|
+
if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2619
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
|
|
2620
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
|
|
2621
|
+
return "unknown";
|
|
2622
|
+
}
|
|
2623
|
+
case "enum": {
|
|
2624
|
+
const arrArg = args[0];
|
|
2625
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2626
|
+
const members = arrArg.getElements().map(
|
|
2627
|
+
(el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2628
|
+
);
|
|
2629
|
+
return members.join(" | ");
|
|
2630
|
+
}
|
|
2631
|
+
case "array": {
|
|
2632
|
+
const inner = args[0];
|
|
2633
|
+
if (!inner) return "unknown";
|
|
2634
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2635
|
+
}
|
|
2636
|
+
case "object": {
|
|
2637
|
+
const objArg = args[0];
|
|
2638
|
+
if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2639
|
+
const lines = [];
|
|
2640
|
+
for (const prop of objArg.getProperties()) {
|
|
2641
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
2642
|
+
const key = prop.getName();
|
|
2643
|
+
const valNode = prop.getInitializer();
|
|
2644
|
+
if (!valNode) continue;
|
|
2645
|
+
const tsType = zodAstToTs(valNode);
|
|
2646
|
+
const isOpt = isOptionalChain(valNode);
|
|
2647
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2648
|
+
}
|
|
2649
|
+
return `{ ${lines.join("; ")} }`;
|
|
2650
|
+
}
|
|
2651
|
+
case "union": {
|
|
2652
|
+
const arrArg = args[0];
|
|
2653
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2654
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2655
|
+
}
|
|
2656
|
+
case "record": {
|
|
2657
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2658
|
+
if (!valArg) return "unknown";
|
|
2659
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2660
|
+
}
|
|
2661
|
+
case "tuple": {
|
|
2662
|
+
const arrArg = args[0];
|
|
2663
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2664
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2665
|
+
}
|
|
2666
|
+
default:
|
|
2667
|
+
return "unknown";
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
return "unknown";
|
|
2671
|
+
}
|
|
2672
|
+
function isOptionalChain(node) {
|
|
2673
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return false;
|
|
2674
|
+
const expr = node.getExpression();
|
|
2675
|
+
return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2676
|
+
}
|
|
2677
|
+
function memberType(val, position, project) {
|
|
2678
|
+
if (project) {
|
|
2679
|
+
const inferred = inferSchema(val, project);
|
|
2680
|
+
if (inferred.kind === "schema") {
|
|
2681
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return zodAstToTs(val);
|
|
2685
|
+
}
|
|
2686
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2687
|
+
if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
|
|
2688
|
+
const callee = callExpr.getExpression();
|
|
2689
|
+
const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2690
|
+
if (calleeName !== "defineContract") return null;
|
|
2691
|
+
const args = callExpr.getArguments();
|
|
2692
|
+
const optsArg = args[0];
|
|
2693
|
+
if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2694
|
+
let query = null;
|
|
2695
|
+
let body = null;
|
|
2696
|
+
let response = "unknown";
|
|
2697
|
+
let error = null;
|
|
2698
|
+
let bodyZodText = null;
|
|
2699
|
+
let queryZodText = null;
|
|
2700
|
+
for (const prop of optsArg.getProperties()) {
|
|
2701
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
2702
|
+
const propName = prop.getName();
|
|
2703
|
+
const val = prop.getInitializer();
|
|
2704
|
+
if (!val) continue;
|
|
2705
|
+
if (propName === "query") {
|
|
2706
|
+
query = memberType(val, "request", project);
|
|
2707
|
+
queryZodText = val.getText();
|
|
2708
|
+
} else if (propName === "body") {
|
|
2709
|
+
body = memberType(val, "request", project);
|
|
2710
|
+
bodyZodText = val.getText();
|
|
2711
|
+
} else if (propName === "response") {
|
|
2712
|
+
response = memberType(val, "response", project);
|
|
2713
|
+
} else if (propName === "error") {
|
|
2714
|
+
error = memberType(val, "response", project);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
// src/discovery/pipe-schema.ts
|
|
2721
|
+
function pipeSchemaOf(decorator, project) {
|
|
2722
|
+
for (const pipe of decorator.getArguments()) {
|
|
2723
|
+
if (!import_ts_morph10.Node.isNewExpression(pipe) && !import_ts_morph10.Node.isCallExpression(pipe)) continue;
|
|
2724
|
+
for (const arg of pipe.getArguments()) {
|
|
2725
|
+
if (!couldBeSchema(arg)) continue;
|
|
2726
|
+
const inferred = inferSchema(arg, project);
|
|
2727
|
+
if (inferred.kind === "schema") {
|
|
2728
|
+
return {
|
|
2729
|
+
type: requestTypeOf(inferred.types),
|
|
2730
|
+
optional: acceptsUndefined(inferred.types),
|
|
2731
|
+
fields: requestFieldsOf(inferred.types)
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
if (inferred.kind === "unresolved") {
|
|
2735
|
+
const fallback = syntacticZodType(arg, project);
|
|
2736
|
+
if (fallback) {
|
|
2737
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2744
|
+
function couldBeSchema(arg) {
|
|
2745
|
+
return import_ts_morph10.Node.isIdentifier(arg) || import_ts_morph10.Node.isPropertyAccessExpression(arg) || import_ts_morph10.Node.isCallExpression(arg) || import_ts_morph10.Node.isAsExpression(arg) || import_ts_morph10.Node.isSatisfiesExpression(arg) || import_ts_morph10.Node.isParenthesizedExpression(arg);
|
|
2746
|
+
}
|
|
2747
|
+
function syntacticZodType(arg, project) {
|
|
2748
|
+
let expr = arg;
|
|
2749
|
+
if (import_ts_morph10.Node.isIdentifier(arg)) {
|
|
2750
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2751
|
+
expr = resolved?.decl.getInitializer();
|
|
2752
|
+
}
|
|
2753
|
+
if (!expr || !import_ts_morph10.Node.isCallExpression(expr)) return null;
|
|
2754
|
+
const type = zodAstToTs(expr);
|
|
2755
|
+
return type === "unknown" ? null : type;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2092
2758
|
// src/discovery/dto-type-resolver.ts
|
|
2093
2759
|
var WRAPPER_TYPES = {
|
|
2094
2760
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2120,22 +2786,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2120
2786
|
]);
|
|
2121
2787
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2122
2788
|
if (depth <= 0) return "unknown";
|
|
2123
|
-
if (
|
|
2789
|
+
if (import_ts_morph11.Node.isArrayTypeNode(typeNode)) {
|
|
2124
2790
|
const elementType = typeNode.getElementTypeNode();
|
|
2125
2791
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2126
2792
|
}
|
|
2127
|
-
if (
|
|
2793
|
+
if (import_ts_morph11.Node.isUnionTypeNode(typeNode)) {
|
|
2128
2794
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2129
2795
|
}
|
|
2130
|
-
if (
|
|
2796
|
+
if (import_ts_morph11.Node.isIntersectionTypeNode(typeNode)) {
|
|
2131
2797
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2132
2798
|
}
|
|
2133
|
-
if (
|
|
2799
|
+
if (import_ts_morph11.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2134
2800
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2135
2801
|
}
|
|
2136
|
-
if (
|
|
2802
|
+
if (import_ts_morph11.Node.isTypeReference(typeNode)) {
|
|
2137
2803
|
const typeName = typeNode.getTypeName();
|
|
2138
|
-
const name =
|
|
2804
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2139
2805
|
const bound = subst.get(name);
|
|
2140
2806
|
if (bound !== void 0) return bound;
|
|
2141
2807
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2158,13 +2824,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2158
2824
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2159
2825
|
return "unknown";
|
|
2160
2826
|
}
|
|
2161
|
-
if (
|
|
2827
|
+
if (import_ts_morph11.Node.isTypeLiteral(typeNode)) {
|
|
2162
2828
|
const members = [];
|
|
2163
2829
|
for (const member of typeNode.getMembers()) {
|
|
2164
|
-
if (
|
|
2830
|
+
if (import_ts_morph11.Node.isPropertySignature(member)) {
|
|
2165
2831
|
const memberTypeNode = member.getTypeNode();
|
|
2166
|
-
const
|
|
2167
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2832
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2833
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2168
2834
|
} else {
|
|
2169
2835
|
members.push(member.getText());
|
|
2170
2836
|
}
|
|
@@ -2172,11 +2838,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2172
2838
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2173
2839
|
}
|
|
2174
2840
|
const kind = typeNode.getKind();
|
|
2175
|
-
if (kind ===
|
|
2176
|
-
if (kind ===
|
|
2177
|
-
if (kind ===
|
|
2178
|
-
if (kind ===
|
|
2179
|
-
if (kind ===
|
|
2841
|
+
if (kind === import_ts_morph11.SyntaxKind.StringKeyword) return "string";
|
|
2842
|
+
if (kind === import_ts_morph11.SyntaxKind.NumberKeyword) return "number";
|
|
2843
|
+
if (kind === import_ts_morph11.SyntaxKind.BooleanKeyword) return "boolean";
|
|
2844
|
+
if (kind === import_ts_morph11.SyntaxKind.UnknownKeyword) return "unknown";
|
|
2845
|
+
if (kind === import_ts_morph11.SyntaxKind.AnyKeyword) return "unknown";
|
|
2180
2846
|
return typeNode.getText();
|
|
2181
2847
|
}
|
|
2182
2848
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2224,20 +2890,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2224
2890
|
const propName = prop.getName();
|
|
2225
2891
|
const isOptional = prop.hasQuestionToken();
|
|
2226
2892
|
const propTypeNode = prop.getTypeNode();
|
|
2227
|
-
let
|
|
2893
|
+
let propType2 = "unknown";
|
|
2228
2894
|
if (propTypeNode) {
|
|
2229
|
-
|
|
2895
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2230
2896
|
}
|
|
2231
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
2897
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2232
2898
|
}
|
|
2233
2899
|
return `{ ${lines.join("; ")} }`;
|
|
2234
2900
|
}
|
|
2901
|
+
function namedField(decorator) {
|
|
2902
|
+
const first = decorator.getArguments()[0];
|
|
2903
|
+
return first && import_ts_morph11.Node.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
2904
|
+
}
|
|
2235
2905
|
function extractBodyType(method, sourceFile, project) {
|
|
2236
2906
|
for (const param of method.getParameters()) {
|
|
2237
2907
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2238
|
-
if (!bodyDecorator) continue;
|
|
2239
|
-
const
|
|
2240
|
-
if (
|
|
2908
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
2909
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
2910
|
+
if (piped) return piped.type;
|
|
2241
2911
|
const typeNode = param.getTypeNode();
|
|
2242
2912
|
if (typeNode) {
|
|
2243
2913
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2245,11 +2915,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2245
2915
|
}
|
|
2246
2916
|
return null;
|
|
2247
2917
|
}
|
|
2918
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
2919
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
2920
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
2921
|
+
}
|
|
2248
2922
|
function extractQueryType(method, sourceFile, project) {
|
|
2249
2923
|
for (const param of method.getParameters()) {
|
|
2250
2924
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2251
|
-
if (!queryDecorator) continue;
|
|
2252
|
-
|
|
2925
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
2926
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
2927
|
+
if (piped) return piped.type;
|
|
2253
2928
|
const typeNode = param.getTypeNode();
|
|
2254
2929
|
if (typeNode) {
|
|
2255
2930
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2259,10 +2934,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2259
2934
|
for (const param of method.getParameters()) {
|
|
2260
2935
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2261
2936
|
if (!queryDecorator) continue;
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2937
|
+
const queryName = namedField(queryDecorator);
|
|
2938
|
+
if (queryName === null) continue;
|
|
2939
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
2940
|
+
if (piped) {
|
|
2941
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2266
2944
|
const typeNode = param.getTypeNode();
|
|
2267
2945
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2268
2946
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2272,8 +2950,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2272
2950
|
function isParamOptional(param) {
|
|
2273
2951
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2274
2952
|
const typeNode = param.getTypeNode();
|
|
2275
|
-
if (typeNode &&
|
|
2276
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
2953
|
+
if (typeNode && import_ts_morph11.Node.isUnionTypeNode(typeNode)) {
|
|
2954
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === import_ts_morph11.SyntaxKind.UndefinedKeyword);
|
|
2277
2955
|
}
|
|
2278
2956
|
return false;
|
|
2279
2957
|
}
|
|
@@ -2285,7 +2963,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2285
2963
|
const paramArgs = paramDecorator.getArguments();
|
|
2286
2964
|
if (paramArgs.length === 0) continue;
|
|
2287
2965
|
const nameArg = paramArgs[0];
|
|
2288
|
-
if (!
|
|
2966
|
+
if (!import_ts_morph11.Node.isStringLiteral(nameArg)) continue;
|
|
2289
2967
|
const paramName = nameArg.getLiteralValue();
|
|
2290
2968
|
const typeNode = param.getTypeNode();
|
|
2291
2969
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2293,6 +2971,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2293
2971
|
}
|
|
2294
2972
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2295
2973
|
}
|
|
2974
|
+
function extractPipedParamTypes(method, project) {
|
|
2975
|
+
const types = {};
|
|
2976
|
+
for (const param of method.getParameters()) {
|
|
2977
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
2978
|
+
if (!paramDecorator) continue;
|
|
2979
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
2980
|
+
if (!piped) continue;
|
|
2981
|
+
const name = namedField(paramDecorator);
|
|
2982
|
+
if (name !== null) {
|
|
2983
|
+
types[name] = piped.type;
|
|
2984
|
+
} else {
|
|
2985
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
2989
|
+
}
|
|
2296
2990
|
function extractUploadedFiles(method) {
|
|
2297
2991
|
const FILE = "File | Blob";
|
|
2298
2992
|
const entries = [];
|
|
@@ -2306,28 +3000,28 @@ function extractUploadedFiles(method) {
|
|
|
2306
3000
|
for (const decorator of method.getDecorators()) {
|
|
2307
3001
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2308
3002
|
for (const arg of decorator.getArguments()) {
|
|
2309
|
-
if (!
|
|
3003
|
+
if (!import_ts_morph11.Node.isCallExpression(arg)) continue;
|
|
2310
3004
|
const interceptor = arg.getExpression().getText();
|
|
2311
3005
|
const callArgs = arg.getArguments();
|
|
2312
3006
|
const firstArg2 = callArgs[0];
|
|
2313
3007
|
if (interceptor === "FileInterceptor") {
|
|
2314
|
-
if (firstArg2 &&
|
|
3008
|
+
if (firstArg2 && import_ts_morph11.Node.isStringLiteral(firstArg2)) {
|
|
2315
3009
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2316
3010
|
multipart = true;
|
|
2317
3011
|
}
|
|
2318
3012
|
} else if (interceptor === "FilesInterceptor") {
|
|
2319
|
-
if (firstArg2 &&
|
|
3013
|
+
if (firstArg2 && import_ts_morph11.Node.isStringLiteral(firstArg2)) {
|
|
2320
3014
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2321
3015
|
multipart = true;
|
|
2322
3016
|
}
|
|
2323
3017
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2324
|
-
if (firstArg2 &&
|
|
3018
|
+
if (firstArg2 && import_ts_morph11.Node.isArrayLiteralExpression(firstArg2)) {
|
|
2325
3019
|
for (const el of firstArg2.getElements()) {
|
|
2326
|
-
if (!
|
|
3020
|
+
if (!import_ts_morph11.Node.isObjectLiteralExpression(el)) continue;
|
|
2327
3021
|
const nameProp = el.getProperty("name");
|
|
2328
|
-
if (nameProp &&
|
|
3022
|
+
if (nameProp && import_ts_morph11.Node.isPropertyAssignment(nameProp)) {
|
|
2329
3023
|
const init = nameProp.getInitializer();
|
|
2330
|
-
if (init &&
|
|
3024
|
+
if (init && import_ts_morph11.Node.isStringLiteral(init)) {
|
|
2331
3025
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2332
3026
|
}
|
|
2333
3027
|
}
|
|
@@ -2347,13 +3041,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2347
3041
|
if (apiResponseDecorator) {
|
|
2348
3042
|
const args = apiResponseDecorator.getArguments();
|
|
2349
3043
|
const optsArg = args[0];
|
|
2350
|
-
if (optsArg &&
|
|
3044
|
+
if (optsArg && import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) {
|
|
2351
3045
|
for (const prop of optsArg.getProperties()) {
|
|
2352
|
-
if (!
|
|
3046
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2353
3047
|
if (prop.getName() !== "type") continue;
|
|
2354
3048
|
const val = prop.getInitializer();
|
|
2355
3049
|
if (!val) continue;
|
|
2356
|
-
if (
|
|
3050
|
+
if (import_ts_morph11.Node.isArrayLiteralExpression(val)) {
|
|
2357
3051
|
const elements = val.getElements();
|
|
2358
3052
|
const firstEl = elements[0];
|
|
2359
3053
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2374,24 +3068,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2374
3068
|
}
|
|
2375
3069
|
function apiResponseStatus(decorator) {
|
|
2376
3070
|
const optsArg = decorator.getArguments()[0];
|
|
2377
|
-
if (!optsArg || !
|
|
3071
|
+
if (!optsArg || !import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2378
3072
|
for (const prop of optsArg.getProperties()) {
|
|
2379
|
-
if (!
|
|
3073
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2380
3074
|
if (prop.getName() !== "status") continue;
|
|
2381
3075
|
const val = prop.getInitializer();
|
|
2382
|
-
if (val &&
|
|
3076
|
+
if (val && import_ts_morph11.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2383
3077
|
}
|
|
2384
3078
|
return null;
|
|
2385
3079
|
}
|
|
2386
3080
|
function apiResponseTypeNode(decorator) {
|
|
2387
3081
|
const optsArg = decorator.getArguments()[0];
|
|
2388
|
-
if (!optsArg || !
|
|
3082
|
+
if (!optsArg || !import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2389
3083
|
for (const prop of optsArg.getProperties()) {
|
|
2390
|
-
if (!
|
|
3084
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2391
3085
|
if (prop.getName() !== "type") continue;
|
|
2392
3086
|
const val = prop.getInitializer();
|
|
2393
3087
|
if (!val) return null;
|
|
2394
|
-
if (
|
|
3088
|
+
if (import_ts_morph11.Node.isArrayLiteralExpression(val)) {
|
|
2395
3089
|
const first = val.getElements()[0];
|
|
2396
3090
|
return first ? { node: first, isArray: true } : null;
|
|
2397
3091
|
}
|
|
@@ -2409,7 +3103,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2409
3103
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2410
3104
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2411
3105
|
let ref = null;
|
|
2412
|
-
if (
|
|
3106
|
+
if (import_ts_morph11.Node.isIdentifier(typeInfo.node)) {
|
|
2413
3107
|
const name = typeInfo.node.getText();
|
|
2414
3108
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2415
3109
|
if (localDecl?.isExported()) {
|
|
@@ -2426,7 +3120,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2426
3120
|
return null;
|
|
2427
3121
|
}
|
|
2428
3122
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2429
|
-
if (!
|
|
3123
|
+
if (!import_ts_morph11.Node.isIdentifier(node)) return "unknown";
|
|
2430
3124
|
const name = node.getText();
|
|
2431
3125
|
const resolved = findType(name, sourceFile, project);
|
|
2432
3126
|
if (resolved) {
|
|
@@ -2446,9 +3140,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2446
3140
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2447
3141
|
function detectBinaryResponse(method) {
|
|
2448
3142
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2449
|
-
if (!node || !
|
|
3143
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return false;
|
|
2450
3144
|
const typeName = node.getTypeName();
|
|
2451
|
-
const name =
|
|
3145
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2452
3146
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2453
3147
|
}
|
|
2454
3148
|
function detectStreamElement(method) {
|
|
@@ -2463,9 +3157,9 @@ function detectStreamElement(method) {
|
|
|
2463
3157
|
return null;
|
|
2464
3158
|
}
|
|
2465
3159
|
function streamContainerElement(node) {
|
|
2466
|
-
if (!node || !
|
|
3160
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return null;
|
|
2467
3161
|
const typeName = node.getTypeName();
|
|
2468
|
-
const name =
|
|
3162
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2469
3163
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2470
3164
|
return node.getTypeArguments()[0] ?? null;
|
|
2471
3165
|
}
|
|
@@ -2475,9 +3169,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2475
3169
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2476
3170
|
}
|
|
2477
3171
|
function unwrapNamedContainer(node, names) {
|
|
2478
|
-
if (!node || !
|
|
3172
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return node;
|
|
2479
3173
|
const typeName = node.getTypeName();
|
|
2480
|
-
const name =
|
|
3174
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2481
3175
|
if (names.has(name)) {
|
|
2482
3176
|
return node.getTypeArguments()[0] ?? node;
|
|
2483
3177
|
}
|
|
@@ -2498,19 +3192,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2498
3192
|
body = body ?? bodyType;
|
|
2499
3193
|
}
|
|
2500
3194
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3195
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2501
3196
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2502
3197
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
2503
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3198
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2504
3199
|
return null;
|
|
2505
3200
|
}
|
|
2506
3201
|
let bodyRef = null;
|
|
2507
3202
|
let queryRef = null;
|
|
2508
3203
|
let responseRef = null;
|
|
2509
3204
|
for (const param of method.getParameters()) {
|
|
2510
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3205
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2511
3206
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2512
3207
|
}
|
|
2513
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3208
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2514
3209
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2515
3210
|
}
|
|
2516
3211
|
}
|
|
@@ -2523,11 +3218,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2523
3218
|
if (apiResp) {
|
|
2524
3219
|
const args = apiResp.getArguments();
|
|
2525
3220
|
const optsArg = args[0];
|
|
2526
|
-
if (optsArg &&
|
|
3221
|
+
if (optsArg && import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) {
|
|
2527
3222
|
for (const prop of optsArg.getProperties()) {
|
|
2528
|
-
if (
|
|
3223
|
+
if (import_ts_morph11.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2529
3224
|
const val = prop.getInitializer();
|
|
2530
|
-
if (val &&
|
|
3225
|
+
if (val && import_ts_morph11.Node.isIdentifier(val)) {
|
|
2531
3226
|
const name = val.getText();
|
|
2532
3227
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2533
3228
|
if (localDecl?.isExported()) {
|
|
@@ -2563,6 +3258,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2563
3258
|
response,
|
|
2564
3259
|
error: errorInfo?.type ?? null,
|
|
2565
3260
|
params: paramsType,
|
|
3261
|
+
paramTypes,
|
|
2566
3262
|
queryRef,
|
|
2567
3263
|
bodyRef,
|
|
2568
3264
|
responseRef,
|
|
@@ -2583,6 +3279,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2583
3279
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2584
3280
|
for (const param of method.getParameters()) {
|
|
2585
3281
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3282
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2586
3283
|
const typeNode = param.getTypeNode();
|
|
2587
3284
|
if (!typeNode) continue;
|
|
2588
3285
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2595,129 +3292,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2595
3292
|
return null;
|
|
2596
3293
|
}
|
|
2597
3294
|
|
|
2598
|
-
// src/discovery/zod-ast-to-ts.ts
|
|
2599
|
-
var import_ts_morph8 = require("ts-morph");
|
|
2600
|
-
function zodAstToTs(node) {
|
|
2601
|
-
if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
|
|
2602
|
-
const expr = node.getExpression();
|
|
2603
|
-
if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
|
|
2604
|
-
const methodName = expr.getName();
|
|
2605
|
-
const receiver = expr.getExpression();
|
|
2606
|
-
if (methodName === "optional") {
|
|
2607
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
2608
|
-
}
|
|
2609
|
-
if (methodName === "nullable") {
|
|
2610
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
2611
|
-
}
|
|
2612
|
-
const args = node.getArguments();
|
|
2613
|
-
switch (methodName) {
|
|
2614
|
-
case "string":
|
|
2615
|
-
return "string";
|
|
2616
|
-
case "number":
|
|
2617
|
-
return "number";
|
|
2618
|
-
case "boolean":
|
|
2619
|
-
return "boolean";
|
|
2620
|
-
case "unknown":
|
|
2621
|
-
return "unknown";
|
|
2622
|
-
case "any":
|
|
2623
|
-
return "unknown";
|
|
2624
|
-
case "literal": {
|
|
2625
|
-
const lit = args[0];
|
|
2626
|
-
if (!lit) return "unknown";
|
|
2627
|
-
if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2628
|
-
if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2629
|
-
if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
|
|
2630
|
-
if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
|
|
2631
|
-
return "unknown";
|
|
2632
|
-
}
|
|
2633
|
-
case "enum": {
|
|
2634
|
-
const arrArg = args[0];
|
|
2635
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2636
|
-
const members = arrArg.getElements().map(
|
|
2637
|
-
(el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2638
|
-
);
|
|
2639
|
-
return members.join(" | ");
|
|
2640
|
-
}
|
|
2641
|
-
case "array": {
|
|
2642
|
-
const inner = args[0];
|
|
2643
|
-
if (!inner) return "unknown";
|
|
2644
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
2645
|
-
}
|
|
2646
|
-
case "object": {
|
|
2647
|
-
const objArg = args[0];
|
|
2648
|
-
if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2649
|
-
const lines = [];
|
|
2650
|
-
for (const prop of objArg.getProperties()) {
|
|
2651
|
-
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2652
|
-
const key = prop.getName();
|
|
2653
|
-
const valNode = prop.getInitializer();
|
|
2654
|
-
if (!valNode) continue;
|
|
2655
|
-
const tsType = zodAstToTs(valNode);
|
|
2656
|
-
const isOpt = isOptionalChain(valNode);
|
|
2657
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2658
|
-
}
|
|
2659
|
-
return `{ ${lines.join("; ")} }`;
|
|
2660
|
-
}
|
|
2661
|
-
case "union": {
|
|
2662
|
-
const arrArg = args[0];
|
|
2663
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2664
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2665
|
-
}
|
|
2666
|
-
case "record": {
|
|
2667
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2668
|
-
if (!valArg) return "unknown";
|
|
2669
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2670
|
-
}
|
|
2671
|
-
case "tuple": {
|
|
2672
|
-
const arrArg = args[0];
|
|
2673
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2674
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2675
|
-
}
|
|
2676
|
-
default:
|
|
2677
|
-
return "unknown";
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
return "unknown";
|
|
2681
|
-
}
|
|
2682
|
-
function isOptionalChain(node) {
|
|
2683
|
-
if (!import_ts_morph8.Node.isCallExpression(node)) return false;
|
|
2684
|
-
const expr = node.getExpression();
|
|
2685
|
-
return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2686
|
-
}
|
|
2687
|
-
function parseDefineContractCall(callExpr) {
|
|
2688
|
-
if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
|
|
2689
|
-
const callee = callExpr.getExpression();
|
|
2690
|
-
const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2691
|
-
if (calleeName !== "defineContract") return null;
|
|
2692
|
-
const args = callExpr.getArguments();
|
|
2693
|
-
const optsArg = args[0];
|
|
2694
|
-
if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2695
|
-
let query = null;
|
|
2696
|
-
let body = null;
|
|
2697
|
-
let response = "unknown";
|
|
2698
|
-
let error = null;
|
|
2699
|
-
let bodyZodText = null;
|
|
2700
|
-
let queryZodText = null;
|
|
2701
|
-
for (const prop of optsArg.getProperties()) {
|
|
2702
|
-
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2703
|
-
const propName = prop.getName();
|
|
2704
|
-
const val = prop.getInitializer();
|
|
2705
|
-
if (!val) continue;
|
|
2706
|
-
if (propName === "query") {
|
|
2707
|
-
query = zodAstToTs(val);
|
|
2708
|
-
queryZodText = val.getText();
|
|
2709
|
-
} else if (propName === "body") {
|
|
2710
|
-
body = zodAstToTs(val);
|
|
2711
|
-
bodyZodText = val.getText();
|
|
2712
|
-
} else if (propName === "response") {
|
|
2713
|
-
response = zodAstToTs(val);
|
|
2714
|
-
} else if (propName === "error") {
|
|
2715
|
-
error = zodAstToTs(val);
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
3295
|
// src/discovery/contracts-fast.ts
|
|
2722
3296
|
async function discoverContractsFast(opts) {
|
|
2723
3297
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2730,7 +3304,9 @@ async function discoverContractsFast(opts) {
|
|
|
2730
3304
|
}
|
|
2731
3305
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2732
3306
|
clearMixinTypeProject();
|
|
2733
|
-
|
|
3307
|
+
const routes = extractAllRoutes(project);
|
|
3308
|
+
clearSchemaTypeProject(project);
|
|
3309
|
+
return routes;
|
|
2734
3310
|
}
|
|
2735
3311
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2736
3312
|
return tsconfig ? (0, import_node_path5.resolve)(tsconfig) : (0, import_node_path5.join)(cwd, "tsconfig.json");
|
|
@@ -2742,25 +3318,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2742
3318
|
};
|
|
2743
3319
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2744
3320
|
if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
|
|
2745
|
-
const read =
|
|
3321
|
+
const read = import_ts_morph12.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
|
|
2746
3322
|
if (read.error) {
|
|
2747
3323
|
return {
|
|
2748
3324
|
options: null,
|
|
2749
3325
|
files: [tsconfigPath],
|
|
2750
|
-
error:
|
|
3326
|
+
error: import_ts_morph12.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2751
3327
|
};
|
|
2752
3328
|
}
|
|
2753
3329
|
const host = {
|
|
2754
|
-
useCaseSensitiveFileNames:
|
|
3330
|
+
useCaseSensitiveFileNames: import_ts_morph12.ts.sys.useCaseSensitiveFileNames,
|
|
2755
3331
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2756
3332
|
readDirectory: () => [],
|
|
2757
|
-
fileExists: (path) =>
|
|
3333
|
+
fileExists: (path) => import_ts_morph12.ts.sys.fileExists(path),
|
|
2758
3334
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2759
3335
|
// in a base tsconfig keeps working.
|
|
2760
|
-
readFile: (path) =>
|
|
3336
|
+
readFile: (path) => import_ts_morph12.ts.sys.readFile(path)
|
|
2761
3337
|
};
|
|
2762
|
-
const sourceFile =
|
|
2763
|
-
const parsed =
|
|
3338
|
+
const sourceFile = import_ts_morph12.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
|
|
3339
|
+
const parsed = import_ts_morph12.ts.parseJsonSourceFileConfigFileContent(
|
|
2764
3340
|
sourceFile,
|
|
2765
3341
|
host,
|
|
2766
3342
|
(0, import_node_path5.dirname)(tsconfigPath),
|
|
@@ -2783,7 +3359,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2783
3359
|
`[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.`
|
|
2784
3360
|
);
|
|
2785
3361
|
}
|
|
2786
|
-
return new
|
|
3362
|
+
return new import_ts_morph12.Project({
|
|
2787
3363
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2788
3364
|
skipAddingFilesFromTsConfig: true,
|
|
2789
3365
|
skipLoadingLibFiles: true,
|
|
@@ -2895,15 +3471,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2895
3471
|
clearTypeResolutionCaches(this.project);
|
|
2896
3472
|
clearEnumCache(this.project);
|
|
2897
3473
|
clearMixinTypeProject();
|
|
3474
|
+
clearSchemaTypeProject(this.project);
|
|
2898
3475
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2899
3476
|
}
|
|
2900
3477
|
};
|
|
2901
3478
|
function decoratorStringArg(decoratorExpr) {
|
|
2902
3479
|
if (!decoratorExpr) return void 0;
|
|
2903
|
-
if (
|
|
2904
|
-
if (
|
|
3480
|
+
if (import_ts_morph12.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3481
|
+
if (import_ts_morph12.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
2905
3482
|
const first = decoratorExpr.getElements()[0];
|
|
2906
|
-
if (first &&
|
|
3483
|
+
if (first && import_ts_morph12.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
2907
3484
|
}
|
|
2908
3485
|
return void 0;
|
|
2909
3486
|
}
|
|
@@ -3024,9 +3601,9 @@ function extractContractRoute(args) {
|
|
|
3024
3601
|
let contractDef = null;
|
|
3025
3602
|
let bodyZodRef = null;
|
|
3026
3603
|
let queryZodRef = null;
|
|
3027
|
-
if (
|
|
3028
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3029
|
-
} else if (
|
|
3604
|
+
if (import_ts_morph12.Node.isCallExpression(firstDecoratorArg)) {
|
|
3605
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3606
|
+
} else if (import_ts_morph12.Node.isIdentifier(firstDecoratorArg)) {
|
|
3030
3607
|
const identName = firstDecoratorArg.getText();
|
|
3031
3608
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3032
3609
|
if (!resolvedVar) {
|
|
@@ -3038,7 +3615,7 @@ function extractContractRoute(args) {
|
|
|
3038
3615
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3039
3616
|
const initializer = varDecl.getInitializer();
|
|
3040
3617
|
if (!initializer) return null;
|
|
3041
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3618
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3042
3619
|
if (contractDef && varDecl.isExported()) {
|
|
3043
3620
|
const filePath = declFile.getFilePath();
|
|
3044
3621
|
if (contractDef.body !== null) {
|
|
@@ -3109,6 +3686,7 @@ function extractDtoRoute(args) {
|
|
|
3109
3686
|
body: dtoContract?.body ?? null,
|
|
3110
3687
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3111
3688
|
error: dtoContract?.error ?? null,
|
|
3689
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3112
3690
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3113
3691
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3114
3692
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3231,7 +3809,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3231
3809
|
},
|
|
3232
3810
|
project() {
|
|
3233
3811
|
if (!project) {
|
|
3234
|
-
project = new
|
|
3812
|
+
project = new import_ts_morph13.Project({
|
|
3235
3813
|
skipAddingFilesFromTsConfig: true,
|
|
3236
3814
|
skipLoadingLibFiles: true,
|
|
3237
3815
|
skipFileDependencyResolution: true,
|
|
@@ -3349,10 +3927,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3349
3927
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3350
3928
|
}
|
|
3351
3929
|
}
|
|
3352
|
-
function buildParamsType(params) {
|
|
3930
|
+
function buildParamsType(params, paramTypes) {
|
|
3353
3931
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3354
3932
|
if (pathParams.length === 0) return "never";
|
|
3355
|
-
|
|
3933
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
3934
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3356
3935
|
}
|
|
3357
3936
|
function hasPathParams(params) {
|
|
3358
3937
|
return params.some((p) => p.source === "path");
|
|
@@ -3446,7 +4025,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3446
4025
|
}
|
|
3447
4026
|
const response = buildResponseType(c, outDir, serialization);
|
|
3448
4027
|
const error = buildErrorType(c);
|
|
3449
|
-
const params = buildParamsType(c.params);
|
|
4028
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3450
4029
|
const safeMethod = JSON.stringify(method);
|
|
3451
4030
|
const safeUrl = JSON.stringify(c.path);
|
|
3452
4031
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3715,6 +4294,9 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3715
4294
|
"}",
|
|
3716
4295
|
""
|
|
3717
4296
|
];
|
|
4297
|
+
function isClientCallable(route) {
|
|
4298
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4299
|
+
}
|
|
3718
4300
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3719
4301
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3720
4302
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3722,7 +4304,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3722
4304
|
const { layer } = resolveApiSlots(extensions);
|
|
3723
4305
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3724
4306
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3725
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4307
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3726
4308
|
const ctx = opts.ctx ?? {
|
|
3727
4309
|
cwd: outDir ?? "",
|
|
3728
4310
|
outDir: outDir ?? "",
|
|
@@ -4640,6 +5222,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4640
5222
|
const paths = {};
|
|
4641
5223
|
for (const route of routes) {
|
|
4642
5224
|
if (!route.contract) continue;
|
|
5225
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4643
5226
|
const oaPath = toOpenApiPath(route.path);
|
|
4644
5227
|
const method = route.method.toLowerCase();
|
|
4645
5228
|
let pathItem = paths[oaPath];
|
|
@@ -5299,7 +5882,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
5299
5882
|
}
|
|
5300
5883
|
|
|
5301
5884
|
// src/index.ts
|
|
5302
|
-
var VERSION = "0.
|
|
5885
|
+
var VERSION = "0.26.0";
|
|
5303
5886
|
|
|
5304
5887
|
// src/cli/codegen.ts
|
|
5305
5888
|
async function runCodegen(opts = {}) {
|