@dudousxd/nestjs-codegen 0.25.1 → 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 +30 -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 +13 -9
package/dist/index.js
CHANGED
|
@@ -523,22 +523,22 @@ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from
|
|
|
523
523
|
|
|
524
524
|
// src/extension/registry.ts
|
|
525
525
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
526
|
-
import { Project as
|
|
526
|
+
import { Project as Project5 } from "ts-morph";
|
|
527
527
|
|
|
528
528
|
// src/discovery/contracts-fast.ts
|
|
529
529
|
import { existsSync } from "fs";
|
|
530
530
|
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
531
531
|
import fg2 from "fast-glob";
|
|
532
532
|
import {
|
|
533
|
-
Node as
|
|
534
|
-
Project as
|
|
535
|
-
ts
|
|
533
|
+
Node as Node11,
|
|
534
|
+
Project as Project4,
|
|
535
|
+
ts as ts3
|
|
536
536
|
} from "ts-morph";
|
|
537
537
|
|
|
538
538
|
// src/discovery/dto-type-resolver.ts
|
|
539
539
|
import {
|
|
540
|
-
Node as
|
|
541
|
-
SyntaxKind as
|
|
540
|
+
Node as Node10,
|
|
541
|
+
SyntaxKind as SyntaxKind4
|
|
542
542
|
} from "ts-morph";
|
|
543
543
|
|
|
544
544
|
// src/discovery/dto-to-ir.ts
|
|
@@ -2076,6 +2076,674 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2076
2076
|
return fields;
|
|
2077
2077
|
}
|
|
2078
2078
|
|
|
2079
|
+
// src/discovery/pipe-schema.ts
|
|
2080
|
+
import { Node as Node9 } from "ts-morph";
|
|
2081
|
+
|
|
2082
|
+
// src/discovery/schema-type-inference.ts
|
|
2083
|
+
import {
|
|
2084
|
+
ts as ts2
|
|
2085
|
+
} from "ts-morph";
|
|
2086
|
+
|
|
2087
|
+
// src/discovery/schema-type-program.ts
|
|
2088
|
+
import { Node as Node7, Project as Project3, ts } from "ts-morph";
|
|
2089
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2090
|
+
var SchemaTypeProgram = class {
|
|
2091
|
+
constructor(discovery) {
|
|
2092
|
+
this.discovery = discovery;
|
|
2093
|
+
const base = discovery.getCompilerOptions();
|
|
2094
|
+
this.options = {
|
|
2095
|
+
...base,
|
|
2096
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2097
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2098
|
+
target: base.target ?? ts.ScriptTarget.ES2022,
|
|
2099
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2100
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2101
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
|
|
2102
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2103
|
+
strictNullChecks: true,
|
|
2104
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2105
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2106
|
+
types: [],
|
|
2107
|
+
noEmit: true
|
|
2108
|
+
};
|
|
2109
|
+
this.project = new Project3({
|
|
2110
|
+
skipAddingFilesFromTsConfig: true,
|
|
2111
|
+
compilerOptions: this.options
|
|
2112
|
+
});
|
|
2113
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2114
|
+
this.flush();
|
|
2115
|
+
}
|
|
2116
|
+
project;
|
|
2117
|
+
options;
|
|
2118
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2119
|
+
scratch = new Project3({
|
|
2120
|
+
skipAddingFilesFromTsConfig: true,
|
|
2121
|
+
skipLoadingLibFiles: true,
|
|
2122
|
+
skipFileDependencyResolution: true
|
|
2123
|
+
});
|
|
2124
|
+
needs = /* @__PURE__ */ new Map();
|
|
2125
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2126
|
+
written = /* @__PURE__ */ new Map();
|
|
2127
|
+
/**
|
|
2128
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2129
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2130
|
+
* program once.
|
|
2131
|
+
*/
|
|
2132
|
+
sourceFileFor(sf) {
|
|
2133
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2134
|
+
this.flush();
|
|
2135
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2136
|
+
}
|
|
2137
|
+
// -------------------------------------------------------------------------
|
|
2138
|
+
queue = [];
|
|
2139
|
+
request(path, want) {
|
|
2140
|
+
let needs = this.needs.get(path);
|
|
2141
|
+
const isNew = !needs;
|
|
2142
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2143
|
+
let grew = isNew;
|
|
2144
|
+
if (want.schema && !needs.schema) {
|
|
2145
|
+
needs.schema = true;
|
|
2146
|
+
grew = true;
|
|
2147
|
+
}
|
|
2148
|
+
if (want.all && !needs.all) {
|
|
2149
|
+
needs.all = true;
|
|
2150
|
+
grew = true;
|
|
2151
|
+
}
|
|
2152
|
+
for (const name of want.names ?? []) {
|
|
2153
|
+
if (!needs.names.has(name)) {
|
|
2154
|
+
needs.names.add(name);
|
|
2155
|
+
grew = true;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
this.needs.set(path, needs);
|
|
2159
|
+
if (grew) this.queue.push(path);
|
|
2160
|
+
}
|
|
2161
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2162
|
+
flush() {
|
|
2163
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2164
|
+
while (this.queue.length > 0) {
|
|
2165
|
+
const path = this.queue.pop();
|
|
2166
|
+
const original = this.original(path);
|
|
2167
|
+
if (!original) continue;
|
|
2168
|
+
const needs = this.needs.get(path);
|
|
2169
|
+
const text = this.prune(original, needs);
|
|
2170
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2171
|
+
}
|
|
2172
|
+
for (const [path, text] of changed) {
|
|
2173
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2174
|
+
this.written.set(path, text);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2178
|
+
original(path) {
|
|
2179
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2183
|
+
* requesting from each kept one's target the names it brings in.
|
|
2184
|
+
*/
|
|
2185
|
+
prune(sf, needs) {
|
|
2186
|
+
const locals = topLevelDeclarations(sf);
|
|
2187
|
+
const roots = [];
|
|
2188
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2189
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2190
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2191
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2192
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2193
|
+
if (spec === void 0) {
|
|
2194
|
+
for (const named of exp.getNamedExports()) {
|
|
2195
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2196
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2197
|
+
}
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2201
|
+
for (const named of exp.getNamedExports()) {
|
|
2202
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2203
|
+
const list = reexported.get(exposed) ?? [];
|
|
2204
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2205
|
+
reexported.set(exposed, list);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
const unresolvedNames = [];
|
|
2209
|
+
for (const name of needs.names) {
|
|
2210
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2211
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2212
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2213
|
+
}
|
|
2214
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2215
|
+
const queue = [...roots];
|
|
2216
|
+
const seen = new Set(queue);
|
|
2217
|
+
while (queue.length > 0) {
|
|
2218
|
+
const node = queue.pop();
|
|
2219
|
+
const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
|
|
2220
|
+
for (const id of ids) {
|
|
2221
|
+
const name = id.getText();
|
|
2222
|
+
if (referenced.has(name)) continue;
|
|
2223
|
+
referenced.add(name);
|
|
2224
|
+
for (const local of locals.get(name) ?? []) {
|
|
2225
|
+
if (seen.has(local)) continue;
|
|
2226
|
+
seen.add(local);
|
|
2227
|
+
queue.push(local);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
const chars = sf.getFullText().split("");
|
|
2232
|
+
const blank = (node) => {
|
|
2233
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2234
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2238
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2239
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2240
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2241
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2242
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2243
|
+
imported: n.getName()
|
|
2244
|
+
})).filter((n) => referenced.has(n.local));
|
|
2245
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2246
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2247
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2248
|
+
blank(decl);
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
if (target) {
|
|
2252
|
+
this.request(target, {
|
|
2253
|
+
all: wantsNamespace,
|
|
2254
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2259
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2260
|
+
if (spec === void 0) continue;
|
|
2261
|
+
const target = this.resolve(spec, sf);
|
|
2262
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2263
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2264
|
+
blank(exp);
|
|
2265
|
+
} else if (target) {
|
|
2266
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2267
|
+
}
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
const wanted = exp.getNamedExports().filter(
|
|
2271
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2272
|
+
).map((n) => n.getNameNode().getText());
|
|
2273
|
+
if (wanted.length === 0) blank(exp);
|
|
2274
|
+
else if (target) this.request(target, { names: wanted });
|
|
2275
|
+
}
|
|
2276
|
+
return chars.join("");
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2280
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2281
|
+
*/
|
|
2282
|
+
resolve(specifier, from) {
|
|
2283
|
+
const resolved = ts.resolveModuleName(
|
|
2284
|
+
specifier,
|
|
2285
|
+
from.getFilePath(),
|
|
2286
|
+
this.options,
|
|
2287
|
+
ts.sys
|
|
2288
|
+
).resolvedModule;
|
|
2289
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2290
|
+
const file = resolved.resolvedFileName;
|
|
2291
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2292
|
+
return file;
|
|
2293
|
+
}
|
|
2294
|
+
};
|
|
2295
|
+
function schemaPositions(sf) {
|
|
2296
|
+
const roots = [];
|
|
2297
|
+
sf.forEachDescendant((node) => {
|
|
2298
|
+
if (Node7.isDecorator(node)) {
|
|
2299
|
+
const name = node.getName();
|
|
2300
|
+
if (name === "ApplyContract") {
|
|
2301
|
+
roots.push(...node.getArguments());
|
|
2302
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2303
|
+
for (const arg of node.getArguments()) {
|
|
2304
|
+
if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
|
|
2305
|
+
roots.push(...arg.getArguments());
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
} else if (Node7.isCallExpression(node)) {
|
|
2310
|
+
const callee = node.getExpression();
|
|
2311
|
+
const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2312
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
return roots;
|
|
2316
|
+
}
|
|
2317
|
+
function topLevelDeclarations(sf) {
|
|
2318
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2319
|
+
const add = (name, node) => {
|
|
2320
|
+
if (!name) return;
|
|
2321
|
+
const list = byName.get(name);
|
|
2322
|
+
if (list) list.push(node);
|
|
2323
|
+
else byName.set(name, [node]);
|
|
2324
|
+
};
|
|
2325
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2326
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2327
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2328
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2329
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2330
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2331
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2332
|
+
return byName;
|
|
2333
|
+
}
|
|
2334
|
+
function defaultExport(sf) {
|
|
2335
|
+
const nodes = [];
|
|
2336
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2337
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2338
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2339
|
+
return nodes;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// src/discovery/schema-type-inference.ts
|
|
2343
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2344
|
+
function programFor(discovery) {
|
|
2345
|
+
let program = programs.get(discovery);
|
|
2346
|
+
if (!program) {
|
|
2347
|
+
program = new SchemaTypeProgram(discovery);
|
|
2348
|
+
programs.set(discovery, program);
|
|
2349
|
+
}
|
|
2350
|
+
return program;
|
|
2351
|
+
}
|
|
2352
|
+
function clearSchemaTypeProject(discovery) {
|
|
2353
|
+
programs.delete(discovery);
|
|
2354
|
+
}
|
|
2355
|
+
function counterpart(typedSf, node) {
|
|
2356
|
+
const start = node.getStart();
|
|
2357
|
+
const end = node.getEnd();
|
|
2358
|
+
const kind = node.getKind();
|
|
2359
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2360
|
+
while (candidate) {
|
|
2361
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2362
|
+
return candidate;
|
|
2363
|
+
}
|
|
2364
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2365
|
+
candidate = candidate.getParent();
|
|
2366
|
+
}
|
|
2367
|
+
return void 0;
|
|
2368
|
+
}
|
|
2369
|
+
function propType(type, name, at) {
|
|
2370
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2371
|
+
}
|
|
2372
|
+
function isAnyOrUnknown(type) {
|
|
2373
|
+
return type.isAny() || type.isUnknown();
|
|
2374
|
+
}
|
|
2375
|
+
function inferSchema(expr, discovery) {
|
|
2376
|
+
try {
|
|
2377
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2378
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2379
|
+
if (!at) return { kind: "unresolved" };
|
|
2380
|
+
const schemaType = at.getType();
|
|
2381
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2382
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2383
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2384
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2385
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2386
|
+
if (stdInput && stdOutput) {
|
|
2387
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2388
|
+
}
|
|
2389
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2390
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2391
|
+
if (zodInput && zodOutput) {
|
|
2392
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2393
|
+
}
|
|
2394
|
+
return { kind: "not-schema" };
|
|
2395
|
+
} catch (err) {
|
|
2396
|
+
dbg("schema inference failed:", err);
|
|
2397
|
+
return { kind: "unresolved" };
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2401
|
+
"Date",
|
|
2402
|
+
"File",
|
|
2403
|
+
"Blob",
|
|
2404
|
+
"FormData",
|
|
2405
|
+
"URL",
|
|
2406
|
+
"RegExp",
|
|
2407
|
+
"Map",
|
|
2408
|
+
"Set",
|
|
2409
|
+
"ArrayBuffer",
|
|
2410
|
+
"Uint8Array"
|
|
2411
|
+
]);
|
|
2412
|
+
var MAX_DEPTH = 12;
|
|
2413
|
+
function objectKey(name) {
|
|
2414
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2415
|
+
}
|
|
2416
|
+
function isOptionalSymbol(sym) {
|
|
2417
|
+
return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
|
|
2418
|
+
}
|
|
2419
|
+
function isSymbolKeyed(sym) {
|
|
2420
|
+
return sym.getName().startsWith("__@");
|
|
2421
|
+
}
|
|
2422
|
+
function orderedProperties(type) {
|
|
2423
|
+
const position = (sym) => {
|
|
2424
|
+
const decl = sym.getDeclarations()[0];
|
|
2425
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2426
|
+
};
|
|
2427
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2428
|
+
if (a.at && b.at) {
|
|
2429
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2430
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2431
|
+
} else if (a.at || b.at) {
|
|
2432
|
+
return a.at ? -1 : 1;
|
|
2433
|
+
}
|
|
2434
|
+
return a.index - b.index;
|
|
2435
|
+
}).map((entry) => entry.sym);
|
|
2436
|
+
}
|
|
2437
|
+
function isFromDeclarationFile(sym) {
|
|
2438
|
+
const decl = sym?.getDeclarations()[0];
|
|
2439
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2440
|
+
}
|
|
2441
|
+
function render(type, state, paired) {
|
|
2442
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2443
|
+
if (isAnyOrUnknown(type)) {
|
|
2444
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2445
|
+
}
|
|
2446
|
+
if (type.isNever()) return "never";
|
|
2447
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2448
|
+
if (type.isNumber()) return "number";
|
|
2449
|
+
if (type.isBoolean()) return "boolean";
|
|
2450
|
+
if (type.isBigInt()) return "bigint";
|
|
2451
|
+
if (type.isNull()) return "null";
|
|
2452
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2453
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2454
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2455
|
+
const value = type.getLiteralValue();
|
|
2456
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2457
|
+
}
|
|
2458
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2459
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2460
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2461
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2462
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2463
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2464
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2465
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2466
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2467
|
+
}
|
|
2468
|
+
if (type.isTuple()) {
|
|
2469
|
+
const elements = type.getTupleElements();
|
|
2470
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2471
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2472
|
+
}
|
|
2473
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2474
|
+
return "unknown";
|
|
2475
|
+
}
|
|
2476
|
+
function renderUnion(type, state, paired) {
|
|
2477
|
+
const members = type.getUnionTypes();
|
|
2478
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2479
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2480
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2481
|
+
const arms = [];
|
|
2482
|
+
let sawBoolean = false;
|
|
2483
|
+
let hasNull = false;
|
|
2484
|
+
let hasUndefined = false;
|
|
2485
|
+
for (const member of members) {
|
|
2486
|
+
if (member.isNull()) {
|
|
2487
|
+
hasNull = true;
|
|
2488
|
+
continue;
|
|
2489
|
+
}
|
|
2490
|
+
if (member.isUndefined()) {
|
|
2491
|
+
hasUndefined = true;
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2495
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2496
|
+
sawBoolean = true;
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2500
|
+
}
|
|
2501
|
+
if (hasNull) arms.push("null");
|
|
2502
|
+
if (hasUndefined) arms.push("undefined");
|
|
2503
|
+
const unique = [...new Set(arms)];
|
|
2504
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2505
|
+
return unique.join(" | ");
|
|
2506
|
+
}
|
|
2507
|
+
function renderIntersection(type, state) {
|
|
2508
|
+
const members = type.getIntersectionTypes();
|
|
2509
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2510
|
+
if (primitive) return render(primitive, state);
|
|
2511
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2512
|
+
}
|
|
2513
|
+
function renderObject(type, state, paired) {
|
|
2514
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2515
|
+
const name = symbol?.getName();
|
|
2516
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2517
|
+
const args = type.getTypeArguments();
|
|
2518
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2519
|
+
}
|
|
2520
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2521
|
+
return "unknown";
|
|
2522
|
+
}
|
|
2523
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2524
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2525
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2526
|
+
const inner = { ...state, stack };
|
|
2527
|
+
const fields = [];
|
|
2528
|
+
for (const prop of orderedProperties(type)) {
|
|
2529
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2530
|
+
const propName = prop.getName();
|
|
2531
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2532
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2533
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2534
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2535
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2536
|
+
}
|
|
2537
|
+
const stringIndex = type.getStringIndexType();
|
|
2538
|
+
if (stringIndex) {
|
|
2539
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2540
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2541
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2542
|
+
fields.push(`[key: string]: ${value}`);
|
|
2543
|
+
}
|
|
2544
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2545
|
+
}
|
|
2546
|
+
function stateAt(at) {
|
|
2547
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2548
|
+
}
|
|
2549
|
+
function requestTypeOf(types) {
|
|
2550
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2551
|
+
}
|
|
2552
|
+
function responseTypeOf(types) {
|
|
2553
|
+
return render(types.output, stateAt(types.at));
|
|
2554
|
+
}
|
|
2555
|
+
function requestFieldsOf(types) {
|
|
2556
|
+
const { input, output, at } = types;
|
|
2557
|
+
const objectInput = input.getNonNullableType();
|
|
2558
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2559
|
+
const state = stateAt(at);
|
|
2560
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2561
|
+
name: prop.getName(),
|
|
2562
|
+
type: render(
|
|
2563
|
+
prop.getTypeAtLocation(at),
|
|
2564
|
+
state,
|
|
2565
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2566
|
+
),
|
|
2567
|
+
optional: isOptionalSymbol(prop)
|
|
2568
|
+
}));
|
|
2569
|
+
}
|
|
2570
|
+
function acceptsUndefined(types) {
|
|
2571
|
+
const { input, output } = types;
|
|
2572
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2573
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2574
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2578
|
+
import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
2579
|
+
function zodAstToTs(node) {
|
|
2580
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2581
|
+
const expr = node.getExpression();
|
|
2582
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2583
|
+
const methodName = expr.getName();
|
|
2584
|
+
const receiver = expr.getExpression();
|
|
2585
|
+
if (methodName === "optional") {
|
|
2586
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2587
|
+
}
|
|
2588
|
+
if (methodName === "nullable") {
|
|
2589
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2590
|
+
}
|
|
2591
|
+
const args = node.getArguments();
|
|
2592
|
+
switch (methodName) {
|
|
2593
|
+
case "string":
|
|
2594
|
+
return "string";
|
|
2595
|
+
case "number":
|
|
2596
|
+
return "number";
|
|
2597
|
+
case "boolean":
|
|
2598
|
+
return "boolean";
|
|
2599
|
+
case "unknown":
|
|
2600
|
+
return "unknown";
|
|
2601
|
+
case "any":
|
|
2602
|
+
return "unknown";
|
|
2603
|
+
case "literal": {
|
|
2604
|
+
const lit = args[0];
|
|
2605
|
+
if (!lit) return "unknown";
|
|
2606
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2607
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2608
|
+
if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
|
|
2609
|
+
if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
|
|
2610
|
+
return "unknown";
|
|
2611
|
+
}
|
|
2612
|
+
case "enum": {
|
|
2613
|
+
const arrArg = args[0];
|
|
2614
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2615
|
+
const members = arrArg.getElements().map(
|
|
2616
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2617
|
+
);
|
|
2618
|
+
return members.join(" | ");
|
|
2619
|
+
}
|
|
2620
|
+
case "array": {
|
|
2621
|
+
const inner = args[0];
|
|
2622
|
+
if (!inner) return "unknown";
|
|
2623
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2624
|
+
}
|
|
2625
|
+
case "object": {
|
|
2626
|
+
const objArg = args[0];
|
|
2627
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2628
|
+
const lines = [];
|
|
2629
|
+
for (const prop of objArg.getProperties()) {
|
|
2630
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2631
|
+
const key = prop.getName();
|
|
2632
|
+
const valNode = prop.getInitializer();
|
|
2633
|
+
if (!valNode) continue;
|
|
2634
|
+
const tsType = zodAstToTs(valNode);
|
|
2635
|
+
const isOpt = isOptionalChain(valNode);
|
|
2636
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2637
|
+
}
|
|
2638
|
+
return `{ ${lines.join("; ")} }`;
|
|
2639
|
+
}
|
|
2640
|
+
case "union": {
|
|
2641
|
+
const arrArg = args[0];
|
|
2642
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2643
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2644
|
+
}
|
|
2645
|
+
case "record": {
|
|
2646
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2647
|
+
if (!valArg) return "unknown";
|
|
2648
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2649
|
+
}
|
|
2650
|
+
case "tuple": {
|
|
2651
|
+
const arrArg = args[0];
|
|
2652
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2653
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2654
|
+
}
|
|
2655
|
+
default:
|
|
2656
|
+
return "unknown";
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
return "unknown";
|
|
2660
|
+
}
|
|
2661
|
+
function isOptionalChain(node) {
|
|
2662
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
2663
|
+
const expr = node.getExpression();
|
|
2664
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2665
|
+
}
|
|
2666
|
+
function memberType(val, position, project) {
|
|
2667
|
+
if (project) {
|
|
2668
|
+
const inferred = inferSchema(val, project);
|
|
2669
|
+
if (inferred.kind === "schema") {
|
|
2670
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return zodAstToTs(val);
|
|
2674
|
+
}
|
|
2675
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2676
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2677
|
+
const callee = callExpr.getExpression();
|
|
2678
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2679
|
+
if (calleeName !== "defineContract") return null;
|
|
2680
|
+
const args = callExpr.getArguments();
|
|
2681
|
+
const optsArg = args[0];
|
|
2682
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2683
|
+
let query = null;
|
|
2684
|
+
let body = null;
|
|
2685
|
+
let response = "unknown";
|
|
2686
|
+
let error = null;
|
|
2687
|
+
let bodyZodText = null;
|
|
2688
|
+
let queryZodText = null;
|
|
2689
|
+
for (const prop of optsArg.getProperties()) {
|
|
2690
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2691
|
+
const propName = prop.getName();
|
|
2692
|
+
const val = prop.getInitializer();
|
|
2693
|
+
if (!val) continue;
|
|
2694
|
+
if (propName === "query") {
|
|
2695
|
+
query = memberType(val, "request", project);
|
|
2696
|
+
queryZodText = val.getText();
|
|
2697
|
+
} else if (propName === "body") {
|
|
2698
|
+
body = memberType(val, "request", project);
|
|
2699
|
+
bodyZodText = val.getText();
|
|
2700
|
+
} else if (propName === "response") {
|
|
2701
|
+
response = memberType(val, "response", project);
|
|
2702
|
+
} else if (propName === "error") {
|
|
2703
|
+
error = memberType(val, "response", project);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
// src/discovery/pipe-schema.ts
|
|
2710
|
+
function pipeSchemaOf(decorator, project) {
|
|
2711
|
+
for (const pipe of decorator.getArguments()) {
|
|
2712
|
+
if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
|
|
2713
|
+
for (const arg of pipe.getArguments()) {
|
|
2714
|
+
if (!couldBeSchema(arg)) continue;
|
|
2715
|
+
const inferred = inferSchema(arg, project);
|
|
2716
|
+
if (inferred.kind === "schema") {
|
|
2717
|
+
return {
|
|
2718
|
+
type: requestTypeOf(inferred.types),
|
|
2719
|
+
optional: acceptsUndefined(inferred.types),
|
|
2720
|
+
fields: requestFieldsOf(inferred.types)
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
if (inferred.kind === "unresolved") {
|
|
2724
|
+
const fallback = syntacticZodType(arg, project);
|
|
2725
|
+
if (fallback) {
|
|
2726
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
function couldBeSchema(arg) {
|
|
2734
|
+
return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
|
|
2735
|
+
}
|
|
2736
|
+
function syntacticZodType(arg, project) {
|
|
2737
|
+
let expr = arg;
|
|
2738
|
+
if (Node9.isIdentifier(arg)) {
|
|
2739
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2740
|
+
expr = resolved?.decl.getInitializer();
|
|
2741
|
+
}
|
|
2742
|
+
if (!expr || !Node9.isCallExpression(expr)) return null;
|
|
2743
|
+
const type = zodAstToTs(expr);
|
|
2744
|
+
return type === "unknown" ? null : type;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2079
2747
|
// src/discovery/dto-type-resolver.ts
|
|
2080
2748
|
var WRAPPER_TYPES = {
|
|
2081
2749
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2107,22 +2775,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2107
2775
|
]);
|
|
2108
2776
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2109
2777
|
if (depth <= 0) return "unknown";
|
|
2110
|
-
if (
|
|
2778
|
+
if (Node10.isArrayTypeNode(typeNode)) {
|
|
2111
2779
|
const elementType = typeNode.getElementTypeNode();
|
|
2112
2780
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2113
2781
|
}
|
|
2114
|
-
if (
|
|
2782
|
+
if (Node10.isUnionTypeNode(typeNode)) {
|
|
2115
2783
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2116
2784
|
}
|
|
2117
|
-
if (
|
|
2785
|
+
if (Node10.isIntersectionTypeNode(typeNode)) {
|
|
2118
2786
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2119
2787
|
}
|
|
2120
|
-
if (
|
|
2788
|
+
if (Node10.isParenthesizedTypeNode(typeNode)) {
|
|
2121
2789
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2122
2790
|
}
|
|
2123
|
-
if (
|
|
2791
|
+
if (Node10.isTypeReference(typeNode)) {
|
|
2124
2792
|
const typeName = typeNode.getTypeName();
|
|
2125
|
-
const name =
|
|
2793
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2126
2794
|
const bound = subst.get(name);
|
|
2127
2795
|
if (bound !== void 0) return bound;
|
|
2128
2796
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2145,13 +2813,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2145
2813
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2146
2814
|
return "unknown";
|
|
2147
2815
|
}
|
|
2148
|
-
if (
|
|
2816
|
+
if (Node10.isTypeLiteral(typeNode)) {
|
|
2149
2817
|
const members = [];
|
|
2150
2818
|
for (const member of typeNode.getMembers()) {
|
|
2151
|
-
if (
|
|
2819
|
+
if (Node10.isPropertySignature(member)) {
|
|
2152
2820
|
const memberTypeNode = member.getTypeNode();
|
|
2153
|
-
const
|
|
2154
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2821
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2822
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2155
2823
|
} else {
|
|
2156
2824
|
members.push(member.getText());
|
|
2157
2825
|
}
|
|
@@ -2159,11 +2827,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2159
2827
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2160
2828
|
}
|
|
2161
2829
|
const kind = typeNode.getKind();
|
|
2162
|
-
if (kind ===
|
|
2163
|
-
if (kind ===
|
|
2164
|
-
if (kind ===
|
|
2165
|
-
if (kind ===
|
|
2166
|
-
if (kind ===
|
|
2830
|
+
if (kind === SyntaxKind4.StringKeyword) return "string";
|
|
2831
|
+
if (kind === SyntaxKind4.NumberKeyword) return "number";
|
|
2832
|
+
if (kind === SyntaxKind4.BooleanKeyword) return "boolean";
|
|
2833
|
+
if (kind === SyntaxKind4.UnknownKeyword) return "unknown";
|
|
2834
|
+
if (kind === SyntaxKind4.AnyKeyword) return "unknown";
|
|
2167
2835
|
return typeNode.getText();
|
|
2168
2836
|
}
|
|
2169
2837
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2211,20 +2879,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2211
2879
|
const propName = prop.getName();
|
|
2212
2880
|
const isOptional = prop.hasQuestionToken();
|
|
2213
2881
|
const propTypeNode = prop.getTypeNode();
|
|
2214
|
-
let
|
|
2882
|
+
let propType2 = "unknown";
|
|
2215
2883
|
if (propTypeNode) {
|
|
2216
|
-
|
|
2884
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2217
2885
|
}
|
|
2218
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
2886
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2219
2887
|
}
|
|
2220
2888
|
return `{ ${lines.join("; ")} }`;
|
|
2221
2889
|
}
|
|
2890
|
+
function namedField(decorator) {
|
|
2891
|
+
const first = decorator.getArguments()[0];
|
|
2892
|
+
return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
2893
|
+
}
|
|
2222
2894
|
function extractBodyType(method, sourceFile, project) {
|
|
2223
2895
|
for (const param of method.getParameters()) {
|
|
2224
2896
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2225
|
-
if (!bodyDecorator) continue;
|
|
2226
|
-
const
|
|
2227
|
-
if (
|
|
2897
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
2898
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
2899
|
+
if (piped) return piped.type;
|
|
2228
2900
|
const typeNode = param.getTypeNode();
|
|
2229
2901
|
if (typeNode) {
|
|
2230
2902
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2232,11 +2904,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2232
2904
|
}
|
|
2233
2905
|
return null;
|
|
2234
2906
|
}
|
|
2907
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
2908
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
2909
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
2910
|
+
}
|
|
2235
2911
|
function extractQueryType(method, sourceFile, project) {
|
|
2236
2912
|
for (const param of method.getParameters()) {
|
|
2237
2913
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2238
|
-
if (!queryDecorator) continue;
|
|
2239
|
-
|
|
2914
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
2915
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
2916
|
+
if (piped) return piped.type;
|
|
2240
2917
|
const typeNode = param.getTypeNode();
|
|
2241
2918
|
if (typeNode) {
|
|
2242
2919
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2246,10 +2923,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2246
2923
|
for (const param of method.getParameters()) {
|
|
2247
2924
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2248
2925
|
if (!queryDecorator) continue;
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2926
|
+
const queryName = namedField(queryDecorator);
|
|
2927
|
+
if (queryName === null) continue;
|
|
2928
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
2929
|
+
if (piped) {
|
|
2930
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
2931
|
+
continue;
|
|
2932
|
+
}
|
|
2253
2933
|
const typeNode = param.getTypeNode();
|
|
2254
2934
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2255
2935
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2259,8 +2939,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2259
2939
|
function isParamOptional(param) {
|
|
2260
2940
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2261
2941
|
const typeNode = param.getTypeNode();
|
|
2262
|
-
if (typeNode &&
|
|
2263
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
2942
|
+
if (typeNode && Node10.isUnionTypeNode(typeNode)) {
|
|
2943
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
|
|
2264
2944
|
}
|
|
2265
2945
|
return false;
|
|
2266
2946
|
}
|
|
@@ -2272,7 +2952,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2272
2952
|
const paramArgs = paramDecorator.getArguments();
|
|
2273
2953
|
if (paramArgs.length === 0) continue;
|
|
2274
2954
|
const nameArg = paramArgs[0];
|
|
2275
|
-
if (!
|
|
2955
|
+
if (!Node10.isStringLiteral(nameArg)) continue;
|
|
2276
2956
|
const paramName = nameArg.getLiteralValue();
|
|
2277
2957
|
const typeNode = param.getTypeNode();
|
|
2278
2958
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2280,6 +2960,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2280
2960
|
}
|
|
2281
2961
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2282
2962
|
}
|
|
2963
|
+
function extractPipedParamTypes(method, project) {
|
|
2964
|
+
const types = {};
|
|
2965
|
+
for (const param of method.getParameters()) {
|
|
2966
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
2967
|
+
if (!paramDecorator) continue;
|
|
2968
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
2969
|
+
if (!piped) continue;
|
|
2970
|
+
const name = namedField(paramDecorator);
|
|
2971
|
+
if (name !== null) {
|
|
2972
|
+
types[name] = piped.type;
|
|
2973
|
+
} else {
|
|
2974
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
2978
|
+
}
|
|
2283
2979
|
function extractUploadedFiles(method) {
|
|
2284
2980
|
const FILE = "File | Blob";
|
|
2285
2981
|
const entries = [];
|
|
@@ -2293,28 +2989,28 @@ function extractUploadedFiles(method) {
|
|
|
2293
2989
|
for (const decorator of method.getDecorators()) {
|
|
2294
2990
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2295
2991
|
for (const arg of decorator.getArguments()) {
|
|
2296
|
-
if (!
|
|
2992
|
+
if (!Node10.isCallExpression(arg)) continue;
|
|
2297
2993
|
const interceptor = arg.getExpression().getText();
|
|
2298
2994
|
const callArgs = arg.getArguments();
|
|
2299
2995
|
const firstArg2 = callArgs[0];
|
|
2300
2996
|
if (interceptor === "FileInterceptor") {
|
|
2301
|
-
if (firstArg2 &&
|
|
2997
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2302
2998
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2303
2999
|
multipart = true;
|
|
2304
3000
|
}
|
|
2305
3001
|
} else if (interceptor === "FilesInterceptor") {
|
|
2306
|
-
if (firstArg2 &&
|
|
3002
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2307
3003
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2308
3004
|
multipart = true;
|
|
2309
3005
|
}
|
|
2310
3006
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2311
|
-
if (firstArg2 &&
|
|
3007
|
+
if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
|
|
2312
3008
|
for (const el of firstArg2.getElements()) {
|
|
2313
|
-
if (!
|
|
3009
|
+
if (!Node10.isObjectLiteralExpression(el)) continue;
|
|
2314
3010
|
const nameProp = el.getProperty("name");
|
|
2315
|
-
if (nameProp &&
|
|
3011
|
+
if (nameProp && Node10.isPropertyAssignment(nameProp)) {
|
|
2316
3012
|
const init = nameProp.getInitializer();
|
|
2317
|
-
if (init &&
|
|
3013
|
+
if (init && Node10.isStringLiteral(init)) {
|
|
2318
3014
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2319
3015
|
}
|
|
2320
3016
|
}
|
|
@@ -2334,13 +3030,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2334
3030
|
if (apiResponseDecorator) {
|
|
2335
3031
|
const args = apiResponseDecorator.getArguments();
|
|
2336
3032
|
const optsArg = args[0];
|
|
2337
|
-
if (optsArg &&
|
|
3033
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2338
3034
|
for (const prop of optsArg.getProperties()) {
|
|
2339
|
-
if (!
|
|
3035
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2340
3036
|
if (prop.getName() !== "type") continue;
|
|
2341
3037
|
const val = prop.getInitializer();
|
|
2342
3038
|
if (!val) continue;
|
|
2343
|
-
if (
|
|
3039
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2344
3040
|
const elements = val.getElements();
|
|
2345
3041
|
const firstEl = elements[0];
|
|
2346
3042
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2361,24 +3057,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2361
3057
|
}
|
|
2362
3058
|
function apiResponseStatus(decorator) {
|
|
2363
3059
|
const optsArg = decorator.getArguments()[0];
|
|
2364
|
-
if (!optsArg || !
|
|
3060
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2365
3061
|
for (const prop of optsArg.getProperties()) {
|
|
2366
|
-
if (!
|
|
3062
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2367
3063
|
if (prop.getName() !== "status") continue;
|
|
2368
3064
|
const val = prop.getInitializer();
|
|
2369
|
-
if (val &&
|
|
3065
|
+
if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2370
3066
|
}
|
|
2371
3067
|
return null;
|
|
2372
3068
|
}
|
|
2373
3069
|
function apiResponseTypeNode(decorator) {
|
|
2374
3070
|
const optsArg = decorator.getArguments()[0];
|
|
2375
|
-
if (!optsArg || !
|
|
3071
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2376
3072
|
for (const prop of optsArg.getProperties()) {
|
|
2377
|
-
if (!
|
|
3073
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2378
3074
|
if (prop.getName() !== "type") continue;
|
|
2379
3075
|
const val = prop.getInitializer();
|
|
2380
3076
|
if (!val) return null;
|
|
2381
|
-
if (
|
|
3077
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2382
3078
|
const first = val.getElements()[0];
|
|
2383
3079
|
return first ? { node: first, isArray: true } : null;
|
|
2384
3080
|
}
|
|
@@ -2396,7 +3092,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2396
3092
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2397
3093
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2398
3094
|
let ref = null;
|
|
2399
|
-
if (
|
|
3095
|
+
if (Node10.isIdentifier(typeInfo.node)) {
|
|
2400
3096
|
const name = typeInfo.node.getText();
|
|
2401
3097
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2402
3098
|
if (localDecl?.isExported()) {
|
|
@@ -2413,7 +3109,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2413
3109
|
return null;
|
|
2414
3110
|
}
|
|
2415
3111
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2416
|
-
if (!
|
|
3112
|
+
if (!Node10.isIdentifier(node)) return "unknown";
|
|
2417
3113
|
const name = node.getText();
|
|
2418
3114
|
const resolved = findType(name, sourceFile, project);
|
|
2419
3115
|
if (resolved) {
|
|
@@ -2433,9 +3129,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2433
3129
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2434
3130
|
function detectBinaryResponse(method) {
|
|
2435
3131
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2436
|
-
if (!node || !
|
|
3132
|
+
if (!node || !Node10.isTypeReference(node)) return false;
|
|
2437
3133
|
const typeName = node.getTypeName();
|
|
2438
|
-
const name =
|
|
3134
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2439
3135
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2440
3136
|
}
|
|
2441
3137
|
function detectStreamElement(method) {
|
|
@@ -2450,9 +3146,9 @@ function detectStreamElement(method) {
|
|
|
2450
3146
|
return null;
|
|
2451
3147
|
}
|
|
2452
3148
|
function streamContainerElement(node) {
|
|
2453
|
-
if (!node || !
|
|
3149
|
+
if (!node || !Node10.isTypeReference(node)) return null;
|
|
2454
3150
|
const typeName = node.getTypeName();
|
|
2455
|
-
const name =
|
|
3151
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2456
3152
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2457
3153
|
return node.getTypeArguments()[0] ?? null;
|
|
2458
3154
|
}
|
|
@@ -2462,9 +3158,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2462
3158
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2463
3159
|
}
|
|
2464
3160
|
function unwrapNamedContainer(node, names) {
|
|
2465
|
-
if (!node || !
|
|
3161
|
+
if (!node || !Node10.isTypeReference(node)) return node;
|
|
2466
3162
|
const typeName = node.getTypeName();
|
|
2467
|
-
const name =
|
|
3163
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2468
3164
|
if (names.has(name)) {
|
|
2469
3165
|
return node.getTypeArguments()[0] ?? node;
|
|
2470
3166
|
}
|
|
@@ -2485,19 +3181,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2485
3181
|
body = body ?? bodyType;
|
|
2486
3182
|
}
|
|
2487
3183
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3184
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2488
3185
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2489
3186
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
2490
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3187
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2491
3188
|
return null;
|
|
2492
3189
|
}
|
|
2493
3190
|
let bodyRef = null;
|
|
2494
3191
|
let queryRef = null;
|
|
2495
3192
|
let responseRef = null;
|
|
2496
3193
|
for (const param of method.getParameters()) {
|
|
2497
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3194
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2498
3195
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2499
3196
|
}
|
|
2500
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3197
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2501
3198
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2502
3199
|
}
|
|
2503
3200
|
}
|
|
@@ -2510,11 +3207,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2510
3207
|
if (apiResp) {
|
|
2511
3208
|
const args = apiResp.getArguments();
|
|
2512
3209
|
const optsArg = args[0];
|
|
2513
|
-
if (optsArg &&
|
|
3210
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2514
3211
|
for (const prop of optsArg.getProperties()) {
|
|
2515
|
-
if (
|
|
3212
|
+
if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2516
3213
|
const val = prop.getInitializer();
|
|
2517
|
-
if (val &&
|
|
3214
|
+
if (val && Node10.isIdentifier(val)) {
|
|
2518
3215
|
const name = val.getText();
|
|
2519
3216
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2520
3217
|
if (localDecl?.isExported()) {
|
|
@@ -2550,6 +3247,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2550
3247
|
response,
|
|
2551
3248
|
error: errorInfo?.type ?? null,
|
|
2552
3249
|
params: paramsType,
|
|
3250
|
+
paramTypes,
|
|
2553
3251
|
queryRef,
|
|
2554
3252
|
bodyRef,
|
|
2555
3253
|
responseRef,
|
|
@@ -2570,6 +3268,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2570
3268
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2571
3269
|
for (const param of method.getParameters()) {
|
|
2572
3270
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3271
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2573
3272
|
const typeNode = param.getTypeNode();
|
|
2574
3273
|
if (!typeNode) continue;
|
|
2575
3274
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2582,129 +3281,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2582
3281
|
return null;
|
|
2583
3282
|
}
|
|
2584
3283
|
|
|
2585
|
-
// src/discovery/zod-ast-to-ts.ts
|
|
2586
|
-
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
2587
|
-
function zodAstToTs(node) {
|
|
2588
|
-
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2589
|
-
const expr = node.getExpression();
|
|
2590
|
-
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2591
|
-
const methodName = expr.getName();
|
|
2592
|
-
const receiver = expr.getExpression();
|
|
2593
|
-
if (methodName === "optional") {
|
|
2594
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
2595
|
-
}
|
|
2596
|
-
if (methodName === "nullable") {
|
|
2597
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
2598
|
-
}
|
|
2599
|
-
const args = node.getArguments();
|
|
2600
|
-
switch (methodName) {
|
|
2601
|
-
case "string":
|
|
2602
|
-
return "string";
|
|
2603
|
-
case "number":
|
|
2604
|
-
return "number";
|
|
2605
|
-
case "boolean":
|
|
2606
|
-
return "boolean";
|
|
2607
|
-
case "unknown":
|
|
2608
|
-
return "unknown";
|
|
2609
|
-
case "any":
|
|
2610
|
-
return "unknown";
|
|
2611
|
-
case "literal": {
|
|
2612
|
-
const lit = args[0];
|
|
2613
|
-
if (!lit) return "unknown";
|
|
2614
|
-
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2615
|
-
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2616
|
-
if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
|
|
2617
|
-
if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
|
|
2618
|
-
return "unknown";
|
|
2619
|
-
}
|
|
2620
|
-
case "enum": {
|
|
2621
|
-
const arrArg = args[0];
|
|
2622
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2623
|
-
const members = arrArg.getElements().map(
|
|
2624
|
-
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2625
|
-
);
|
|
2626
|
-
return members.join(" | ");
|
|
2627
|
-
}
|
|
2628
|
-
case "array": {
|
|
2629
|
-
const inner = args[0];
|
|
2630
|
-
if (!inner) return "unknown";
|
|
2631
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
2632
|
-
}
|
|
2633
|
-
case "object": {
|
|
2634
|
-
const objArg = args[0];
|
|
2635
|
-
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2636
|
-
const lines = [];
|
|
2637
|
-
for (const prop of objArg.getProperties()) {
|
|
2638
|
-
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2639
|
-
const key = prop.getName();
|
|
2640
|
-
const valNode = prop.getInitializer();
|
|
2641
|
-
if (!valNode) continue;
|
|
2642
|
-
const tsType = zodAstToTs(valNode);
|
|
2643
|
-
const isOpt = isOptionalChain(valNode);
|
|
2644
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2645
|
-
}
|
|
2646
|
-
return `{ ${lines.join("; ")} }`;
|
|
2647
|
-
}
|
|
2648
|
-
case "union": {
|
|
2649
|
-
const arrArg = args[0];
|
|
2650
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2651
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2652
|
-
}
|
|
2653
|
-
case "record": {
|
|
2654
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2655
|
-
if (!valArg) return "unknown";
|
|
2656
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2657
|
-
}
|
|
2658
|
-
case "tuple": {
|
|
2659
|
-
const arrArg = args[0];
|
|
2660
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2661
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2662
|
-
}
|
|
2663
|
-
default:
|
|
2664
|
-
return "unknown";
|
|
2665
|
-
}
|
|
2666
|
-
}
|
|
2667
|
-
return "unknown";
|
|
2668
|
-
}
|
|
2669
|
-
function isOptionalChain(node) {
|
|
2670
|
-
if (!Node8.isCallExpression(node)) return false;
|
|
2671
|
-
const expr = node.getExpression();
|
|
2672
|
-
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2673
|
-
}
|
|
2674
|
-
function parseDefineContractCall(callExpr) {
|
|
2675
|
-
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2676
|
-
const callee = callExpr.getExpression();
|
|
2677
|
-
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2678
|
-
if (calleeName !== "defineContract") return null;
|
|
2679
|
-
const args = callExpr.getArguments();
|
|
2680
|
-
const optsArg = args[0];
|
|
2681
|
-
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2682
|
-
let query = null;
|
|
2683
|
-
let body = null;
|
|
2684
|
-
let response = "unknown";
|
|
2685
|
-
let error = null;
|
|
2686
|
-
let bodyZodText = null;
|
|
2687
|
-
let queryZodText = null;
|
|
2688
|
-
for (const prop of optsArg.getProperties()) {
|
|
2689
|
-
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2690
|
-
const propName = prop.getName();
|
|
2691
|
-
const val = prop.getInitializer();
|
|
2692
|
-
if (!val) continue;
|
|
2693
|
-
if (propName === "query") {
|
|
2694
|
-
query = zodAstToTs(val);
|
|
2695
|
-
queryZodText = val.getText();
|
|
2696
|
-
} else if (propName === "body") {
|
|
2697
|
-
body = zodAstToTs(val);
|
|
2698
|
-
bodyZodText = val.getText();
|
|
2699
|
-
} else if (propName === "response") {
|
|
2700
|
-
response = zodAstToTs(val);
|
|
2701
|
-
} else if (propName === "error") {
|
|
2702
|
-
error = zodAstToTs(val);
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
3284
|
// src/discovery/contracts-fast.ts
|
|
2709
3285
|
async function discoverContractsFast(opts) {
|
|
2710
3286
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2717,7 +3293,9 @@ async function discoverContractsFast(opts) {
|
|
|
2717
3293
|
}
|
|
2718
3294
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2719
3295
|
clearMixinTypeProject();
|
|
2720
|
-
|
|
3296
|
+
const routes = extractAllRoutes(project);
|
|
3297
|
+
clearSchemaTypeProject(project);
|
|
3298
|
+
return routes;
|
|
2721
3299
|
}
|
|
2722
3300
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2723
3301
|
return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
|
|
@@ -2729,25 +3307,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2729
3307
|
};
|
|
2730
3308
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2731
3309
|
if (!existsSync(tsconfigPath)) return { options: null, files: [] };
|
|
2732
|
-
const read =
|
|
3310
|
+
const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
2733
3311
|
if (read.error) {
|
|
2734
3312
|
return {
|
|
2735
3313
|
options: null,
|
|
2736
3314
|
files: [tsconfigPath],
|
|
2737
|
-
error:
|
|
3315
|
+
error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2738
3316
|
};
|
|
2739
3317
|
}
|
|
2740
3318
|
const host = {
|
|
2741
|
-
useCaseSensitiveFileNames:
|
|
3319
|
+
useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
|
|
2742
3320
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2743
3321
|
readDirectory: () => [],
|
|
2744
|
-
fileExists: (path) =>
|
|
3322
|
+
fileExists: (path) => ts3.sys.fileExists(path),
|
|
2745
3323
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2746
3324
|
// in a base tsconfig keeps working.
|
|
2747
|
-
readFile: (path) =>
|
|
3325
|
+
readFile: (path) => ts3.sys.readFile(path)
|
|
2748
3326
|
};
|
|
2749
|
-
const sourceFile =
|
|
2750
|
-
const parsed =
|
|
3327
|
+
const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
3328
|
+
const parsed = ts3.parseJsonSourceFileConfigFileContent(
|
|
2751
3329
|
sourceFile,
|
|
2752
3330
|
host,
|
|
2753
3331
|
dirname2(tsconfigPath),
|
|
@@ -2770,7 +3348,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2770
3348
|
`[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.`
|
|
2771
3349
|
);
|
|
2772
3350
|
}
|
|
2773
|
-
return new
|
|
3351
|
+
return new Project4({
|
|
2774
3352
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2775
3353
|
skipAddingFilesFromTsConfig: true,
|
|
2776
3354
|
skipLoadingLibFiles: true,
|
|
@@ -2882,15 +3460,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2882
3460
|
clearTypeResolutionCaches(this.project);
|
|
2883
3461
|
clearEnumCache(this.project);
|
|
2884
3462
|
clearMixinTypeProject();
|
|
3463
|
+
clearSchemaTypeProject(this.project);
|
|
2885
3464
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2886
3465
|
}
|
|
2887
3466
|
};
|
|
2888
3467
|
function decoratorStringArg(decoratorExpr) {
|
|
2889
3468
|
if (!decoratorExpr) return void 0;
|
|
2890
|
-
if (
|
|
2891
|
-
if (
|
|
3469
|
+
if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3470
|
+
if (Node11.isArrayLiteralExpression(decoratorExpr)) {
|
|
2892
3471
|
const first = decoratorExpr.getElements()[0];
|
|
2893
|
-
if (first &&
|
|
3472
|
+
if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
|
|
2894
3473
|
}
|
|
2895
3474
|
return void 0;
|
|
2896
3475
|
}
|
|
@@ -3011,9 +3590,9 @@ function extractContractRoute(args) {
|
|
|
3011
3590
|
let contractDef = null;
|
|
3012
3591
|
let bodyZodRef = null;
|
|
3013
3592
|
let queryZodRef = null;
|
|
3014
|
-
if (
|
|
3015
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3016
|
-
} else if (
|
|
3593
|
+
if (Node11.isCallExpression(firstDecoratorArg)) {
|
|
3594
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3595
|
+
} else if (Node11.isIdentifier(firstDecoratorArg)) {
|
|
3017
3596
|
const identName = firstDecoratorArg.getText();
|
|
3018
3597
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3019
3598
|
if (!resolvedVar) {
|
|
@@ -3025,7 +3604,7 @@ function extractContractRoute(args) {
|
|
|
3025
3604
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3026
3605
|
const initializer = varDecl.getInitializer();
|
|
3027
3606
|
if (!initializer) return null;
|
|
3028
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3607
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3029
3608
|
if (contractDef && varDecl.isExported()) {
|
|
3030
3609
|
const filePath = declFile.getFilePath();
|
|
3031
3610
|
if (contractDef.body !== null) {
|
|
@@ -3096,6 +3675,7 @@ function extractDtoRoute(args) {
|
|
|
3096
3675
|
body: dtoContract?.body ?? null,
|
|
3097
3676
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3098
3677
|
error: dtoContract?.error ?? null,
|
|
3678
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3099
3679
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3100
3680
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3101
3681
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3218,7 +3798,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3218
3798
|
},
|
|
3219
3799
|
project() {
|
|
3220
3800
|
if (!project) {
|
|
3221
|
-
project = new
|
|
3801
|
+
project = new Project5({
|
|
3222
3802
|
skipAddingFilesFromTsConfig: true,
|
|
3223
3803
|
skipLoadingLibFiles: true,
|
|
3224
3804
|
skipFileDependencyResolution: true,
|
|
@@ -3336,10 +3916,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3336
3916
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3337
3917
|
}
|
|
3338
3918
|
}
|
|
3339
|
-
function buildParamsType(params) {
|
|
3919
|
+
function buildParamsType(params, paramTypes) {
|
|
3340
3920
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3341
3921
|
if (pathParams.length === 0) return "never";
|
|
3342
|
-
|
|
3922
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
3923
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3343
3924
|
}
|
|
3344
3925
|
function hasPathParams(params) {
|
|
3345
3926
|
return params.some((p) => p.source === "path");
|
|
@@ -3433,7 +4014,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3433
4014
|
}
|
|
3434
4015
|
const response = buildResponseType(c, outDir, serialization);
|
|
3435
4016
|
const error = buildErrorType(c);
|
|
3436
|
-
const params = buildParamsType(c.params);
|
|
4017
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3437
4018
|
const safeMethod = JSON.stringify(method);
|
|
3438
4019
|
const safeUrl = JSON.stringify(c.path);
|
|
3439
4020
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3702,6 +4283,9 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3702
4283
|
"}",
|
|
3703
4284
|
""
|
|
3704
4285
|
];
|
|
4286
|
+
function isClientCallable(route) {
|
|
4287
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4288
|
+
}
|
|
3705
4289
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3706
4290
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3707
4291
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3709,7 +4293,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3709
4293
|
const { layer } = resolveApiSlots(extensions);
|
|
3710
4294
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3711
4295
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3712
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4296
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3713
4297
|
const ctx = opts.ctx ?? {
|
|
3714
4298
|
cwd: outDir ?? "",
|
|
3715
4299
|
outDir: outDir ?? "",
|
|
@@ -4630,6 +5214,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4630
5214
|
const paths = {};
|
|
4631
5215
|
for (const route of routes) {
|
|
4632
5216
|
if (!route.contract) continue;
|
|
5217
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4633
5218
|
const oaPath = toOpenApiPath(route.path);
|
|
4634
5219
|
const method = route.method.toLowerCase();
|
|
4635
5220
|
let pathItem = paths[oaPath];
|
|
@@ -5345,7 +5930,7 @@ function toObjectKey2(name) {
|
|
|
5345
5930
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
5346
5931
|
}
|
|
5347
5932
|
function createChainModuleRenderer(opts) {
|
|
5348
|
-
const { render, recursiveAnnotation } = opts;
|
|
5933
|
+
const { render: render2, recursiveAnnotation } = opts;
|
|
5349
5934
|
return (mod) => {
|
|
5350
5935
|
const ctx = { named: mod.named };
|
|
5351
5936
|
const recursive = mod.recursive ?? /* @__PURE__ */ new Set();
|
|
@@ -5354,7 +5939,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5354
5939
|
const namedTypeAliases = /* @__PURE__ */ new Map();
|
|
5355
5940
|
const namedAnnotations = /* @__PURE__ */ new Map();
|
|
5356
5941
|
for (const [name, node] of mod.named) {
|
|
5357
|
-
namedNestedSchemas.set(name,
|
|
5942
|
+
namedNestedSchemas.set(name, render2(node, ctx));
|
|
5358
5943
|
if (recursive.has(name)) {
|
|
5359
5944
|
const typeName = typeNameFor(name);
|
|
5360
5945
|
namedTypeAliases.set(name, `type ${typeName} = ${renderTsType(node, tctx)}`);
|
|
@@ -5362,7 +5947,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5362
5947
|
}
|
|
5363
5948
|
}
|
|
5364
5949
|
return {
|
|
5365
|
-
schemaText:
|
|
5950
|
+
schemaText: render2(mod.root, ctx),
|
|
5366
5951
|
namedNestedSchemas,
|
|
5367
5952
|
namedTypeAliases,
|
|
5368
5953
|
namedAnnotations,
|
|
@@ -5372,7 +5957,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5372
5957
|
}
|
|
5373
5958
|
|
|
5374
5959
|
// src/index.ts
|
|
5375
|
-
var VERSION = "0.
|
|
5960
|
+
var VERSION = "0.26.0";
|
|
5376
5961
|
export {
|
|
5377
5962
|
CodegenError,
|
|
5378
5963
|
ConfigError,
|