@dudousxd/nestjs-codegen 0.25.2 → 0.27.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 +56 -0
- package/README.md +2 -2
- package/dist/cli/main.cjs +991 -270
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +997 -274
- 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-Dc4s883u.d.cts} +49 -5
- package/dist/{index-Ce3YnfwS.d.ts → index-Dc4s883u.d.ts} +49 -5
- package/dist/index.cjs +994 -273
- 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 +1000 -277
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +1037 -318
- 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 +1040 -319
- package/dist/nest/index.js.map +1 -1
- package/package.json +11 -7
package/dist/cli/main.js
CHANGED
|
@@ -83,12 +83,33 @@ function validateUserConfig(userConfig) {
|
|
|
83
83
|
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
84
84
|
);
|
|
85
85
|
}
|
|
86
|
-
if (userConfig.pages &&
|
|
86
|
+
if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
|
|
87
87
|
throw new ConfigError(
|
|
88
|
-
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
88
|
+
"Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
|
|
92
|
+
if (scope === "default") {
|
|
93
|
+
throw new ConfigError(
|
|
94
|
+
'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (!isGlobInput(scopeConfig?.glob)) {
|
|
98
|
+
throw new ConfigError(
|
|
99
|
+
`Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
|
|
104
|
+
console.warn(
|
|
105
|
+
"[nestjs-codegen] The top-level `scopes` option has no effect and is deprecated. For several Inertia apps use `pages.scopes` (one entry per InertiaModule.forFeature scope); for several controller sets use one `contracts.glob` (e.g. a brace glob)."
|
|
89
106
|
);
|
|
90
107
|
}
|
|
91
108
|
}
|
|
109
|
+
function isGlobInput(value) {
|
|
110
|
+
if (typeof value === "string") return true;
|
|
111
|
+
return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
|
|
112
|
+
}
|
|
92
113
|
function applyDefaults(userConfig, cwd) {
|
|
93
114
|
validateUserConfig(userConfig);
|
|
94
115
|
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : join(cwd, ".nestjs-codegen");
|
|
@@ -115,7 +136,17 @@ function applyDefaults(userConfig, cwd) {
|
|
|
115
136
|
pages: userConfig.pages ? {
|
|
116
137
|
glob: userConfig.pages.glob,
|
|
117
138
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
118
|
-
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
|
|
139
|
+
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
|
|
140
|
+
scopes: Object.fromEntries(
|
|
141
|
+
Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
|
|
142
|
+
scope,
|
|
143
|
+
{
|
|
144
|
+
glob: scopeConfig.glob,
|
|
145
|
+
prefix: scopeConfig.prefix ?? `${scope}/`,
|
|
146
|
+
componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
|
|
147
|
+
}
|
|
148
|
+
])
|
|
149
|
+
)
|
|
119
150
|
} : null,
|
|
120
151
|
contracts: {
|
|
121
152
|
glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
|
|
@@ -189,26 +220,84 @@ import { dirname as dirname4, join as join14 } from "path";
|
|
|
189
220
|
|
|
190
221
|
// src/discovery/pages.ts
|
|
191
222
|
import { readFile } from "fs/promises";
|
|
192
|
-
import { join as join2, relative as relative2 } from "path";
|
|
223
|
+
import { join as join2, relative as relative2, sep as sep2 } from "path";
|
|
193
224
|
import fg from "fast-glob";
|
|
194
225
|
var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
|
|
226
|
+
var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
|
|
227
|
+
function globStaticBase(glob) {
|
|
228
|
+
const segments = glob.split("/");
|
|
229
|
+
const staticSegments = [];
|
|
230
|
+
for (const segment of segments.slice(0, -1)) {
|
|
231
|
+
if (MAGIC_SEGMENT_RE.test(segment)) break;
|
|
232
|
+
staticSegments.push(segment);
|
|
233
|
+
}
|
|
234
|
+
return staticSegments.join("/");
|
|
235
|
+
}
|
|
195
236
|
async function discoverPages(opts) {
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const pagesBase = join2(opts.cwd, globStatic);
|
|
237
|
+
const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
|
|
238
|
+
const prefix = opts.prefix ?? "";
|
|
239
|
+
const scope = opts.scope ?? "default";
|
|
240
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
201
241
|
const out = [];
|
|
202
|
-
for (const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
242
|
+
for (const glob of globs) {
|
|
243
|
+
const files = (await fg(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
|
|
244
|
+
const pagesBase = join2(opts.cwd, globStaticBase(glob));
|
|
245
|
+
for (const file of files) {
|
|
246
|
+
seenFiles.add(file);
|
|
247
|
+
const rel = relative2(opts.cwd, file);
|
|
248
|
+
const nameRel = relative2(pagesBase, file).split(sep2).join("/");
|
|
249
|
+
const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
|
|
250
|
+
const source = await readFile(file, "utf8");
|
|
251
|
+
const propsSource = extractPropsSource(source, opts.propsExport);
|
|
252
|
+
out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
assertUniqueNames(out);
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
async function discoverPageScopes(config) {
|
|
259
|
+
const pages = config.pages;
|
|
260
|
+
if (!pages) return [];
|
|
261
|
+
const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
|
|
262
|
+
const out = await discoverPages({
|
|
263
|
+
...common,
|
|
264
|
+
glob: pages.glob,
|
|
265
|
+
componentNameStrategy: pages.componentNameStrategy
|
|
266
|
+
});
|
|
267
|
+
for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
|
|
268
|
+
out.push(
|
|
269
|
+
...await discoverPages({
|
|
270
|
+
...common,
|
|
271
|
+
glob: scopeConfig.glob,
|
|
272
|
+
componentNameStrategy: scopeConfig.componentNameStrategy,
|
|
273
|
+
prefix: scopeConfig.prefix,
|
|
274
|
+
scope
|
|
275
|
+
})
|
|
276
|
+
);
|
|
209
277
|
}
|
|
278
|
+
assertUniqueNames(out);
|
|
210
279
|
return out;
|
|
211
280
|
}
|
|
281
|
+
function allPageGlobs(config) {
|
|
282
|
+
if (!config.pages) return [];
|
|
283
|
+
const asList = (glob) => typeof glob === "string" ? [glob] : glob;
|
|
284
|
+
return [
|
|
285
|
+
...asList(config.pages.glob),
|
|
286
|
+
...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
function assertUniqueNames(pages) {
|
|
290
|
+
const byName = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const page of pages) {
|
|
292
|
+
const previous = byName.get(page.name);
|
|
293
|
+
if (previous) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Two Inertia pages resolve to the component name "${page.name}": ${previous.relativePath} (scope "${previous.scope}") and ${page.relativePath} (scope "${page.scope}"). Give one of them another name, directory or scope prefix.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
byName.set(page.name, page);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
212
301
|
function computeName(rel, strat) {
|
|
213
302
|
if (typeof strat === "function") return strat(rel);
|
|
214
303
|
const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
|
|
@@ -518,22 +607,22 @@ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from
|
|
|
518
607
|
|
|
519
608
|
// src/extension/registry.ts
|
|
520
609
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
521
|
-
import { Project as
|
|
610
|
+
import { Project as Project5 } from "ts-morph";
|
|
522
611
|
|
|
523
612
|
// src/discovery/contracts-fast.ts
|
|
524
613
|
import { existsSync } from "fs";
|
|
525
614
|
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
526
615
|
import fg2 from "fast-glob";
|
|
527
616
|
import {
|
|
528
|
-
Node as
|
|
529
|
-
Project as
|
|
530
|
-
ts
|
|
617
|
+
Node as Node11,
|
|
618
|
+
Project as Project4,
|
|
619
|
+
ts as ts3
|
|
531
620
|
} from "ts-morph";
|
|
532
621
|
|
|
533
622
|
// src/discovery/dto-type-resolver.ts
|
|
534
623
|
import {
|
|
535
|
-
Node as
|
|
536
|
-
SyntaxKind as
|
|
624
|
+
Node as Node10,
|
|
625
|
+
SyntaxKind as SyntaxKind4
|
|
537
626
|
} from "ts-morph";
|
|
538
627
|
|
|
539
628
|
// src/discovery/dto-to-ir.ts
|
|
@@ -2071,6 +2160,674 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2071
2160
|
return fields;
|
|
2072
2161
|
}
|
|
2073
2162
|
|
|
2163
|
+
// src/discovery/pipe-schema.ts
|
|
2164
|
+
import { Node as Node9 } from "ts-morph";
|
|
2165
|
+
|
|
2166
|
+
// src/discovery/schema-type-inference.ts
|
|
2167
|
+
import {
|
|
2168
|
+
ts as ts2
|
|
2169
|
+
} from "ts-morph";
|
|
2170
|
+
|
|
2171
|
+
// src/discovery/schema-type-program.ts
|
|
2172
|
+
import { Node as Node7, Project as Project3, ts } from "ts-morph";
|
|
2173
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2174
|
+
var SchemaTypeProgram = class {
|
|
2175
|
+
constructor(discovery) {
|
|
2176
|
+
this.discovery = discovery;
|
|
2177
|
+
const base = discovery.getCompilerOptions();
|
|
2178
|
+
this.options = {
|
|
2179
|
+
...base,
|
|
2180
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2181
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2182
|
+
target: base.target ?? ts.ScriptTarget.ES2022,
|
|
2183
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2184
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2185
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
|
|
2186
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2187
|
+
strictNullChecks: true,
|
|
2188
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2189
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2190
|
+
types: [],
|
|
2191
|
+
noEmit: true
|
|
2192
|
+
};
|
|
2193
|
+
this.project = new Project3({
|
|
2194
|
+
skipAddingFilesFromTsConfig: true,
|
|
2195
|
+
compilerOptions: this.options
|
|
2196
|
+
});
|
|
2197
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2198
|
+
this.flush();
|
|
2199
|
+
}
|
|
2200
|
+
project;
|
|
2201
|
+
options;
|
|
2202
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2203
|
+
scratch = new Project3({
|
|
2204
|
+
skipAddingFilesFromTsConfig: true,
|
|
2205
|
+
skipLoadingLibFiles: true,
|
|
2206
|
+
skipFileDependencyResolution: true
|
|
2207
|
+
});
|
|
2208
|
+
needs = /* @__PURE__ */ new Map();
|
|
2209
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2210
|
+
written = /* @__PURE__ */ new Map();
|
|
2211
|
+
/**
|
|
2212
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2213
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2214
|
+
* program once.
|
|
2215
|
+
*/
|
|
2216
|
+
sourceFileFor(sf) {
|
|
2217
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2218
|
+
this.flush();
|
|
2219
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2220
|
+
}
|
|
2221
|
+
// -------------------------------------------------------------------------
|
|
2222
|
+
queue = [];
|
|
2223
|
+
request(path, want) {
|
|
2224
|
+
let needs = this.needs.get(path);
|
|
2225
|
+
const isNew = !needs;
|
|
2226
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2227
|
+
let grew = isNew;
|
|
2228
|
+
if (want.schema && !needs.schema) {
|
|
2229
|
+
needs.schema = true;
|
|
2230
|
+
grew = true;
|
|
2231
|
+
}
|
|
2232
|
+
if (want.all && !needs.all) {
|
|
2233
|
+
needs.all = true;
|
|
2234
|
+
grew = true;
|
|
2235
|
+
}
|
|
2236
|
+
for (const name of want.names ?? []) {
|
|
2237
|
+
if (!needs.names.has(name)) {
|
|
2238
|
+
needs.names.add(name);
|
|
2239
|
+
grew = true;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
this.needs.set(path, needs);
|
|
2243
|
+
if (grew) this.queue.push(path);
|
|
2244
|
+
}
|
|
2245
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2246
|
+
flush() {
|
|
2247
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2248
|
+
while (this.queue.length > 0) {
|
|
2249
|
+
const path = this.queue.pop();
|
|
2250
|
+
const original = this.original(path);
|
|
2251
|
+
if (!original) continue;
|
|
2252
|
+
const needs = this.needs.get(path);
|
|
2253
|
+
const text = this.prune(original, needs);
|
|
2254
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2255
|
+
}
|
|
2256
|
+
for (const [path, text] of changed) {
|
|
2257
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2258
|
+
this.written.set(path, text);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2262
|
+
original(path) {
|
|
2263
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2267
|
+
* requesting from each kept one's target the names it brings in.
|
|
2268
|
+
*/
|
|
2269
|
+
prune(sf, needs) {
|
|
2270
|
+
const locals = topLevelDeclarations(sf);
|
|
2271
|
+
const roots = [];
|
|
2272
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2273
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2274
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2275
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2276
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2277
|
+
if (spec === void 0) {
|
|
2278
|
+
for (const named of exp.getNamedExports()) {
|
|
2279
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2280
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2281
|
+
}
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2285
|
+
for (const named of exp.getNamedExports()) {
|
|
2286
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2287
|
+
const list = reexported.get(exposed) ?? [];
|
|
2288
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2289
|
+
reexported.set(exposed, list);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
const unresolvedNames = [];
|
|
2293
|
+
for (const name of needs.names) {
|
|
2294
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2295
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2296
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2297
|
+
}
|
|
2298
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2299
|
+
const queue = [...roots];
|
|
2300
|
+
const seen = new Set(queue);
|
|
2301
|
+
while (queue.length > 0) {
|
|
2302
|
+
const node = queue.pop();
|
|
2303
|
+
const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
|
|
2304
|
+
for (const id of ids) {
|
|
2305
|
+
const name = id.getText();
|
|
2306
|
+
if (referenced.has(name)) continue;
|
|
2307
|
+
referenced.add(name);
|
|
2308
|
+
for (const local of locals.get(name) ?? []) {
|
|
2309
|
+
if (seen.has(local)) continue;
|
|
2310
|
+
seen.add(local);
|
|
2311
|
+
queue.push(local);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
const chars = sf.getFullText().split("");
|
|
2316
|
+
const blank = (node) => {
|
|
2317
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2318
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2322
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2323
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2324
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2325
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2326
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2327
|
+
imported: n.getName()
|
|
2328
|
+
})).filter((n) => referenced.has(n.local));
|
|
2329
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2330
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2331
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2332
|
+
blank(decl);
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
if (target) {
|
|
2336
|
+
this.request(target, {
|
|
2337
|
+
all: wantsNamespace,
|
|
2338
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2343
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2344
|
+
if (spec === void 0) continue;
|
|
2345
|
+
const target = this.resolve(spec, sf);
|
|
2346
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2347
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2348
|
+
blank(exp);
|
|
2349
|
+
} else if (target) {
|
|
2350
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2351
|
+
}
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
const wanted = exp.getNamedExports().filter(
|
|
2355
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2356
|
+
).map((n) => n.getNameNode().getText());
|
|
2357
|
+
if (wanted.length === 0) blank(exp);
|
|
2358
|
+
else if (target) this.request(target, { names: wanted });
|
|
2359
|
+
}
|
|
2360
|
+
return chars.join("");
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2364
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2365
|
+
*/
|
|
2366
|
+
resolve(specifier, from) {
|
|
2367
|
+
const resolved = ts.resolveModuleName(
|
|
2368
|
+
specifier,
|
|
2369
|
+
from.getFilePath(),
|
|
2370
|
+
this.options,
|
|
2371
|
+
ts.sys
|
|
2372
|
+
).resolvedModule;
|
|
2373
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2374
|
+
const file = resolved.resolvedFileName;
|
|
2375
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2376
|
+
return file;
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2379
|
+
function schemaPositions(sf) {
|
|
2380
|
+
const roots = [];
|
|
2381
|
+
sf.forEachDescendant((node) => {
|
|
2382
|
+
if (Node7.isDecorator(node)) {
|
|
2383
|
+
const name = node.getName();
|
|
2384
|
+
if (name === "ApplyContract") {
|
|
2385
|
+
roots.push(...node.getArguments());
|
|
2386
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2387
|
+
for (const arg of node.getArguments()) {
|
|
2388
|
+
if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
|
|
2389
|
+
roots.push(...arg.getArguments());
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
} else if (Node7.isCallExpression(node)) {
|
|
2394
|
+
const callee = node.getExpression();
|
|
2395
|
+
const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2396
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
return roots;
|
|
2400
|
+
}
|
|
2401
|
+
function topLevelDeclarations(sf) {
|
|
2402
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2403
|
+
const add = (name, node) => {
|
|
2404
|
+
if (!name) return;
|
|
2405
|
+
const list = byName.get(name);
|
|
2406
|
+
if (list) list.push(node);
|
|
2407
|
+
else byName.set(name, [node]);
|
|
2408
|
+
};
|
|
2409
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2410
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2411
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2412
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2413
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2414
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2415
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2416
|
+
return byName;
|
|
2417
|
+
}
|
|
2418
|
+
function defaultExport(sf) {
|
|
2419
|
+
const nodes = [];
|
|
2420
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2421
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2422
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2423
|
+
return nodes;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// src/discovery/schema-type-inference.ts
|
|
2427
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2428
|
+
function programFor(discovery) {
|
|
2429
|
+
let program = programs.get(discovery);
|
|
2430
|
+
if (!program) {
|
|
2431
|
+
program = new SchemaTypeProgram(discovery);
|
|
2432
|
+
programs.set(discovery, program);
|
|
2433
|
+
}
|
|
2434
|
+
return program;
|
|
2435
|
+
}
|
|
2436
|
+
function clearSchemaTypeProject(discovery) {
|
|
2437
|
+
programs.delete(discovery);
|
|
2438
|
+
}
|
|
2439
|
+
function counterpart(typedSf, node) {
|
|
2440
|
+
const start = node.getStart();
|
|
2441
|
+
const end = node.getEnd();
|
|
2442
|
+
const kind = node.getKind();
|
|
2443
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2444
|
+
while (candidate) {
|
|
2445
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2446
|
+
return candidate;
|
|
2447
|
+
}
|
|
2448
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2449
|
+
candidate = candidate.getParent();
|
|
2450
|
+
}
|
|
2451
|
+
return void 0;
|
|
2452
|
+
}
|
|
2453
|
+
function propType(type, name, at) {
|
|
2454
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2455
|
+
}
|
|
2456
|
+
function isAnyOrUnknown(type) {
|
|
2457
|
+
return type.isAny() || type.isUnknown();
|
|
2458
|
+
}
|
|
2459
|
+
function inferSchema(expr, discovery) {
|
|
2460
|
+
try {
|
|
2461
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2462
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2463
|
+
if (!at) return { kind: "unresolved" };
|
|
2464
|
+
const schemaType = at.getType();
|
|
2465
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2466
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2467
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2468
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2469
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2470
|
+
if (stdInput && stdOutput) {
|
|
2471
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2472
|
+
}
|
|
2473
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2474
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2475
|
+
if (zodInput && zodOutput) {
|
|
2476
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2477
|
+
}
|
|
2478
|
+
return { kind: "not-schema" };
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
dbg("schema inference failed:", err);
|
|
2481
|
+
return { kind: "unresolved" };
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2485
|
+
"Date",
|
|
2486
|
+
"File",
|
|
2487
|
+
"Blob",
|
|
2488
|
+
"FormData",
|
|
2489
|
+
"URL",
|
|
2490
|
+
"RegExp",
|
|
2491
|
+
"Map",
|
|
2492
|
+
"Set",
|
|
2493
|
+
"ArrayBuffer",
|
|
2494
|
+
"Uint8Array"
|
|
2495
|
+
]);
|
|
2496
|
+
var MAX_DEPTH = 12;
|
|
2497
|
+
function objectKey(name) {
|
|
2498
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2499
|
+
}
|
|
2500
|
+
function isOptionalSymbol(sym) {
|
|
2501
|
+
return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
|
|
2502
|
+
}
|
|
2503
|
+
function isSymbolKeyed(sym) {
|
|
2504
|
+
return sym.getName().startsWith("__@");
|
|
2505
|
+
}
|
|
2506
|
+
function orderedProperties(type) {
|
|
2507
|
+
const position = (sym) => {
|
|
2508
|
+
const decl = sym.getDeclarations()[0];
|
|
2509
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2510
|
+
};
|
|
2511
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2512
|
+
if (a.at && b.at) {
|
|
2513
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2514
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2515
|
+
} else if (a.at || b.at) {
|
|
2516
|
+
return a.at ? -1 : 1;
|
|
2517
|
+
}
|
|
2518
|
+
return a.index - b.index;
|
|
2519
|
+
}).map((entry) => entry.sym);
|
|
2520
|
+
}
|
|
2521
|
+
function isFromDeclarationFile(sym) {
|
|
2522
|
+
const decl = sym?.getDeclarations()[0];
|
|
2523
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2524
|
+
}
|
|
2525
|
+
function render(type, state, paired) {
|
|
2526
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2527
|
+
if (isAnyOrUnknown(type)) {
|
|
2528
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2529
|
+
}
|
|
2530
|
+
if (type.isNever()) return "never";
|
|
2531
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2532
|
+
if (type.isNumber()) return "number";
|
|
2533
|
+
if (type.isBoolean()) return "boolean";
|
|
2534
|
+
if (type.isBigInt()) return "bigint";
|
|
2535
|
+
if (type.isNull()) return "null";
|
|
2536
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2537
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2538
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2539
|
+
const value = type.getLiteralValue();
|
|
2540
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2541
|
+
}
|
|
2542
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2543
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2544
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2545
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2546
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2547
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2548
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2549
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2550
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2551
|
+
}
|
|
2552
|
+
if (type.isTuple()) {
|
|
2553
|
+
const elements = type.getTupleElements();
|
|
2554
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2555
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2556
|
+
}
|
|
2557
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2558
|
+
return "unknown";
|
|
2559
|
+
}
|
|
2560
|
+
function renderUnion(type, state, paired) {
|
|
2561
|
+
const members = type.getUnionTypes();
|
|
2562
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2563
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2564
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2565
|
+
const arms = [];
|
|
2566
|
+
let sawBoolean = false;
|
|
2567
|
+
let hasNull = false;
|
|
2568
|
+
let hasUndefined = false;
|
|
2569
|
+
for (const member of members) {
|
|
2570
|
+
if (member.isNull()) {
|
|
2571
|
+
hasNull = true;
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
if (member.isUndefined()) {
|
|
2575
|
+
hasUndefined = true;
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2579
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2580
|
+
sawBoolean = true;
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2584
|
+
}
|
|
2585
|
+
if (hasNull) arms.push("null");
|
|
2586
|
+
if (hasUndefined) arms.push("undefined");
|
|
2587
|
+
const unique = [...new Set(arms)];
|
|
2588
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2589
|
+
return unique.join(" | ");
|
|
2590
|
+
}
|
|
2591
|
+
function renderIntersection(type, state) {
|
|
2592
|
+
const members = type.getIntersectionTypes();
|
|
2593
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2594
|
+
if (primitive) return render(primitive, state);
|
|
2595
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2596
|
+
}
|
|
2597
|
+
function renderObject(type, state, paired) {
|
|
2598
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2599
|
+
const name = symbol?.getName();
|
|
2600
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2601
|
+
const args = type.getTypeArguments();
|
|
2602
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2603
|
+
}
|
|
2604
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2605
|
+
return "unknown";
|
|
2606
|
+
}
|
|
2607
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2608
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2609
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2610
|
+
const inner = { ...state, stack };
|
|
2611
|
+
const fields = [];
|
|
2612
|
+
for (const prop of orderedProperties(type)) {
|
|
2613
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2614
|
+
const propName = prop.getName();
|
|
2615
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2616
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2617
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2618
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2619
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2620
|
+
}
|
|
2621
|
+
const stringIndex = type.getStringIndexType();
|
|
2622
|
+
if (stringIndex) {
|
|
2623
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2624
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2625
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2626
|
+
fields.push(`[key: string]: ${value}`);
|
|
2627
|
+
}
|
|
2628
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2629
|
+
}
|
|
2630
|
+
function stateAt(at) {
|
|
2631
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2632
|
+
}
|
|
2633
|
+
function requestTypeOf(types) {
|
|
2634
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2635
|
+
}
|
|
2636
|
+
function responseTypeOf(types) {
|
|
2637
|
+
return render(types.output, stateAt(types.at));
|
|
2638
|
+
}
|
|
2639
|
+
function requestFieldsOf(types) {
|
|
2640
|
+
const { input, output, at } = types;
|
|
2641
|
+
const objectInput = input.getNonNullableType();
|
|
2642
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2643
|
+
const state = stateAt(at);
|
|
2644
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2645
|
+
name: prop.getName(),
|
|
2646
|
+
type: render(
|
|
2647
|
+
prop.getTypeAtLocation(at),
|
|
2648
|
+
state,
|
|
2649
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2650
|
+
),
|
|
2651
|
+
optional: isOptionalSymbol(prop)
|
|
2652
|
+
}));
|
|
2653
|
+
}
|
|
2654
|
+
function acceptsUndefined(types) {
|
|
2655
|
+
const { input, output } = types;
|
|
2656
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2657
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2658
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2662
|
+
import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
2663
|
+
function zodAstToTs(node) {
|
|
2664
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2665
|
+
const expr = node.getExpression();
|
|
2666
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2667
|
+
const methodName = expr.getName();
|
|
2668
|
+
const receiver = expr.getExpression();
|
|
2669
|
+
if (methodName === "optional") {
|
|
2670
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2671
|
+
}
|
|
2672
|
+
if (methodName === "nullable") {
|
|
2673
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2674
|
+
}
|
|
2675
|
+
const args = node.getArguments();
|
|
2676
|
+
switch (methodName) {
|
|
2677
|
+
case "string":
|
|
2678
|
+
return "string";
|
|
2679
|
+
case "number":
|
|
2680
|
+
return "number";
|
|
2681
|
+
case "boolean":
|
|
2682
|
+
return "boolean";
|
|
2683
|
+
case "unknown":
|
|
2684
|
+
return "unknown";
|
|
2685
|
+
case "any":
|
|
2686
|
+
return "unknown";
|
|
2687
|
+
case "literal": {
|
|
2688
|
+
const lit = args[0];
|
|
2689
|
+
if (!lit) return "unknown";
|
|
2690
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2691
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2692
|
+
if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
|
|
2693
|
+
if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
|
|
2694
|
+
return "unknown";
|
|
2695
|
+
}
|
|
2696
|
+
case "enum": {
|
|
2697
|
+
const arrArg = args[0];
|
|
2698
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2699
|
+
const members = arrArg.getElements().map(
|
|
2700
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2701
|
+
);
|
|
2702
|
+
return members.join(" | ");
|
|
2703
|
+
}
|
|
2704
|
+
case "array": {
|
|
2705
|
+
const inner = args[0];
|
|
2706
|
+
if (!inner) return "unknown";
|
|
2707
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2708
|
+
}
|
|
2709
|
+
case "object": {
|
|
2710
|
+
const objArg = args[0];
|
|
2711
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2712
|
+
const lines = [];
|
|
2713
|
+
for (const prop of objArg.getProperties()) {
|
|
2714
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2715
|
+
const key = prop.getName();
|
|
2716
|
+
const valNode = prop.getInitializer();
|
|
2717
|
+
if (!valNode) continue;
|
|
2718
|
+
const tsType = zodAstToTs(valNode);
|
|
2719
|
+
const isOpt = isOptionalChain(valNode);
|
|
2720
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2721
|
+
}
|
|
2722
|
+
return `{ ${lines.join("; ")} }`;
|
|
2723
|
+
}
|
|
2724
|
+
case "union": {
|
|
2725
|
+
const arrArg = args[0];
|
|
2726
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2727
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2728
|
+
}
|
|
2729
|
+
case "record": {
|
|
2730
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2731
|
+
if (!valArg) return "unknown";
|
|
2732
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2733
|
+
}
|
|
2734
|
+
case "tuple": {
|
|
2735
|
+
const arrArg = args[0];
|
|
2736
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2737
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2738
|
+
}
|
|
2739
|
+
default:
|
|
2740
|
+
return "unknown";
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
return "unknown";
|
|
2744
|
+
}
|
|
2745
|
+
function isOptionalChain(node) {
|
|
2746
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
2747
|
+
const expr = node.getExpression();
|
|
2748
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2749
|
+
}
|
|
2750
|
+
function memberType(val, position, project) {
|
|
2751
|
+
if (project) {
|
|
2752
|
+
const inferred = inferSchema(val, project);
|
|
2753
|
+
if (inferred.kind === "schema") {
|
|
2754
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
return zodAstToTs(val);
|
|
2758
|
+
}
|
|
2759
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2760
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2761
|
+
const callee = callExpr.getExpression();
|
|
2762
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2763
|
+
if (calleeName !== "defineContract") return null;
|
|
2764
|
+
const args = callExpr.getArguments();
|
|
2765
|
+
const optsArg = args[0];
|
|
2766
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2767
|
+
let query = null;
|
|
2768
|
+
let body = null;
|
|
2769
|
+
let response = "unknown";
|
|
2770
|
+
let error = null;
|
|
2771
|
+
let bodyZodText = null;
|
|
2772
|
+
let queryZodText = null;
|
|
2773
|
+
for (const prop of optsArg.getProperties()) {
|
|
2774
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2775
|
+
const propName = prop.getName();
|
|
2776
|
+
const val = prop.getInitializer();
|
|
2777
|
+
if (!val) continue;
|
|
2778
|
+
if (propName === "query") {
|
|
2779
|
+
query = memberType(val, "request", project);
|
|
2780
|
+
queryZodText = val.getText();
|
|
2781
|
+
} else if (propName === "body") {
|
|
2782
|
+
body = memberType(val, "request", project);
|
|
2783
|
+
bodyZodText = val.getText();
|
|
2784
|
+
} else if (propName === "response") {
|
|
2785
|
+
response = memberType(val, "response", project);
|
|
2786
|
+
} else if (propName === "error") {
|
|
2787
|
+
error = memberType(val, "response", project);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// src/discovery/pipe-schema.ts
|
|
2794
|
+
function pipeSchemaOf(decorator, project) {
|
|
2795
|
+
for (const pipe of decorator.getArguments()) {
|
|
2796
|
+
if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
|
|
2797
|
+
for (const arg of pipe.getArguments()) {
|
|
2798
|
+
if (!couldBeSchema(arg)) continue;
|
|
2799
|
+
const inferred = inferSchema(arg, project);
|
|
2800
|
+
if (inferred.kind === "schema") {
|
|
2801
|
+
return {
|
|
2802
|
+
type: requestTypeOf(inferred.types),
|
|
2803
|
+
optional: acceptsUndefined(inferred.types),
|
|
2804
|
+
fields: requestFieldsOf(inferred.types)
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
if (inferred.kind === "unresolved") {
|
|
2808
|
+
const fallback = syntacticZodType(arg, project);
|
|
2809
|
+
if (fallback) {
|
|
2810
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
return null;
|
|
2816
|
+
}
|
|
2817
|
+
function couldBeSchema(arg) {
|
|
2818
|
+
return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
|
|
2819
|
+
}
|
|
2820
|
+
function syntacticZodType(arg, project) {
|
|
2821
|
+
let expr = arg;
|
|
2822
|
+
if (Node9.isIdentifier(arg)) {
|
|
2823
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2824
|
+
expr = resolved?.decl.getInitializer();
|
|
2825
|
+
}
|
|
2826
|
+
if (!expr || !Node9.isCallExpression(expr)) return null;
|
|
2827
|
+
const type = zodAstToTs(expr);
|
|
2828
|
+
return type === "unknown" ? null : type;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2074
2831
|
// src/discovery/dto-type-resolver.ts
|
|
2075
2832
|
var WRAPPER_TYPES = {
|
|
2076
2833
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2102,22 +2859,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2102
2859
|
]);
|
|
2103
2860
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2104
2861
|
if (depth <= 0) return "unknown";
|
|
2105
|
-
if (
|
|
2862
|
+
if (Node10.isArrayTypeNode(typeNode)) {
|
|
2106
2863
|
const elementType = typeNode.getElementTypeNode();
|
|
2107
2864
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2108
2865
|
}
|
|
2109
|
-
if (
|
|
2866
|
+
if (Node10.isUnionTypeNode(typeNode)) {
|
|
2110
2867
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2111
2868
|
}
|
|
2112
|
-
if (
|
|
2869
|
+
if (Node10.isIntersectionTypeNode(typeNode)) {
|
|
2113
2870
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2114
2871
|
}
|
|
2115
|
-
if (
|
|
2872
|
+
if (Node10.isParenthesizedTypeNode(typeNode)) {
|
|
2116
2873
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2117
2874
|
}
|
|
2118
|
-
if (
|
|
2875
|
+
if (Node10.isTypeReference(typeNode)) {
|
|
2119
2876
|
const typeName = typeNode.getTypeName();
|
|
2120
|
-
const name =
|
|
2877
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2121
2878
|
const bound = subst.get(name);
|
|
2122
2879
|
if (bound !== void 0) return bound;
|
|
2123
2880
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2140,13 +2897,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2140
2897
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2141
2898
|
return "unknown";
|
|
2142
2899
|
}
|
|
2143
|
-
if (
|
|
2900
|
+
if (Node10.isTypeLiteral(typeNode)) {
|
|
2144
2901
|
const members = [];
|
|
2145
2902
|
for (const member of typeNode.getMembers()) {
|
|
2146
|
-
if (
|
|
2903
|
+
if (Node10.isPropertySignature(member)) {
|
|
2147
2904
|
const memberTypeNode = member.getTypeNode();
|
|
2148
|
-
const
|
|
2149
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2905
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2906
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2150
2907
|
} else {
|
|
2151
2908
|
members.push(member.getText());
|
|
2152
2909
|
}
|
|
@@ -2154,11 +2911,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2154
2911
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2155
2912
|
}
|
|
2156
2913
|
const kind = typeNode.getKind();
|
|
2157
|
-
if (kind ===
|
|
2158
|
-
if (kind ===
|
|
2159
|
-
if (kind ===
|
|
2160
|
-
if (kind ===
|
|
2161
|
-
if (kind ===
|
|
2914
|
+
if (kind === SyntaxKind4.StringKeyword) return "string";
|
|
2915
|
+
if (kind === SyntaxKind4.NumberKeyword) return "number";
|
|
2916
|
+
if (kind === SyntaxKind4.BooleanKeyword) return "boolean";
|
|
2917
|
+
if (kind === SyntaxKind4.UnknownKeyword) return "unknown";
|
|
2918
|
+
if (kind === SyntaxKind4.AnyKeyword) return "unknown";
|
|
2162
2919
|
return typeNode.getText();
|
|
2163
2920
|
}
|
|
2164
2921
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2206,20 +2963,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2206
2963
|
const propName = prop.getName();
|
|
2207
2964
|
const isOptional = prop.hasQuestionToken();
|
|
2208
2965
|
const propTypeNode = prop.getTypeNode();
|
|
2209
|
-
let
|
|
2966
|
+
let propType2 = "unknown";
|
|
2210
2967
|
if (propTypeNode) {
|
|
2211
|
-
|
|
2968
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2212
2969
|
}
|
|
2213
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
2970
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2214
2971
|
}
|
|
2215
2972
|
return `{ ${lines.join("; ")} }`;
|
|
2216
2973
|
}
|
|
2974
|
+
function namedField(decorator) {
|
|
2975
|
+
const first = decorator.getArguments()[0];
|
|
2976
|
+
return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
2977
|
+
}
|
|
2217
2978
|
function extractBodyType(method, sourceFile, project) {
|
|
2218
2979
|
for (const param of method.getParameters()) {
|
|
2219
2980
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2220
|
-
if (!bodyDecorator) continue;
|
|
2221
|
-
const
|
|
2222
|
-
if (
|
|
2981
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
2982
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
2983
|
+
if (piped) return piped.type;
|
|
2223
2984
|
const typeNode = param.getTypeNode();
|
|
2224
2985
|
if (typeNode) {
|
|
2225
2986
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2227,11 +2988,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2227
2988
|
}
|
|
2228
2989
|
return null;
|
|
2229
2990
|
}
|
|
2991
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
2992
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
2993
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
2994
|
+
}
|
|
2230
2995
|
function extractQueryType(method, sourceFile, project) {
|
|
2231
2996
|
for (const param of method.getParameters()) {
|
|
2232
2997
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2233
|
-
if (!queryDecorator) continue;
|
|
2234
|
-
|
|
2998
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
2999
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3000
|
+
if (piped) return piped.type;
|
|
2235
3001
|
const typeNode = param.getTypeNode();
|
|
2236
3002
|
if (typeNode) {
|
|
2237
3003
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2241,10 +3007,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2241
3007
|
for (const param of method.getParameters()) {
|
|
2242
3008
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2243
3009
|
if (!queryDecorator) continue;
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
3010
|
+
const queryName = namedField(queryDecorator);
|
|
3011
|
+
if (queryName === null) continue;
|
|
3012
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3013
|
+
if (piped) {
|
|
3014
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
3015
|
+
continue;
|
|
3016
|
+
}
|
|
2248
3017
|
const typeNode = param.getTypeNode();
|
|
2249
3018
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2250
3019
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2254,8 +3023,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2254
3023
|
function isParamOptional(param) {
|
|
2255
3024
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2256
3025
|
const typeNode = param.getTypeNode();
|
|
2257
|
-
if (typeNode &&
|
|
2258
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
3026
|
+
if (typeNode && Node10.isUnionTypeNode(typeNode)) {
|
|
3027
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
|
|
2259
3028
|
}
|
|
2260
3029
|
return false;
|
|
2261
3030
|
}
|
|
@@ -2267,7 +3036,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2267
3036
|
const paramArgs = paramDecorator.getArguments();
|
|
2268
3037
|
if (paramArgs.length === 0) continue;
|
|
2269
3038
|
const nameArg = paramArgs[0];
|
|
2270
|
-
if (!
|
|
3039
|
+
if (!Node10.isStringLiteral(nameArg)) continue;
|
|
2271
3040
|
const paramName = nameArg.getLiteralValue();
|
|
2272
3041
|
const typeNode = param.getTypeNode();
|
|
2273
3042
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2275,6 +3044,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2275
3044
|
}
|
|
2276
3045
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2277
3046
|
}
|
|
3047
|
+
function extractPipedParamTypes(method, project) {
|
|
3048
|
+
const types = {};
|
|
3049
|
+
for (const param of method.getParameters()) {
|
|
3050
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
3051
|
+
if (!paramDecorator) continue;
|
|
3052
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
3053
|
+
if (!piped) continue;
|
|
3054
|
+
const name = namedField(paramDecorator);
|
|
3055
|
+
if (name !== null) {
|
|
3056
|
+
types[name] = piped.type;
|
|
3057
|
+
} else {
|
|
3058
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
3062
|
+
}
|
|
2278
3063
|
function extractUploadedFiles(method) {
|
|
2279
3064
|
const FILE = "File | Blob";
|
|
2280
3065
|
const entries = [];
|
|
@@ -2288,28 +3073,28 @@ function extractUploadedFiles(method) {
|
|
|
2288
3073
|
for (const decorator of method.getDecorators()) {
|
|
2289
3074
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2290
3075
|
for (const arg of decorator.getArguments()) {
|
|
2291
|
-
if (!
|
|
3076
|
+
if (!Node10.isCallExpression(arg)) continue;
|
|
2292
3077
|
const interceptor = arg.getExpression().getText();
|
|
2293
3078
|
const callArgs = arg.getArguments();
|
|
2294
3079
|
const firstArg2 = callArgs[0];
|
|
2295
3080
|
if (interceptor === "FileInterceptor") {
|
|
2296
|
-
if (firstArg2 &&
|
|
3081
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2297
3082
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2298
3083
|
multipart = true;
|
|
2299
3084
|
}
|
|
2300
3085
|
} else if (interceptor === "FilesInterceptor") {
|
|
2301
|
-
if (firstArg2 &&
|
|
3086
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2302
3087
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2303
3088
|
multipart = true;
|
|
2304
3089
|
}
|
|
2305
3090
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2306
|
-
if (firstArg2 &&
|
|
3091
|
+
if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
|
|
2307
3092
|
for (const el of firstArg2.getElements()) {
|
|
2308
|
-
if (!
|
|
3093
|
+
if (!Node10.isObjectLiteralExpression(el)) continue;
|
|
2309
3094
|
const nameProp = el.getProperty("name");
|
|
2310
|
-
if (nameProp &&
|
|
3095
|
+
if (nameProp && Node10.isPropertyAssignment(nameProp)) {
|
|
2311
3096
|
const init = nameProp.getInitializer();
|
|
2312
|
-
if (init &&
|
|
3097
|
+
if (init && Node10.isStringLiteral(init)) {
|
|
2313
3098
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2314
3099
|
}
|
|
2315
3100
|
}
|
|
@@ -2329,13 +3114,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2329
3114
|
if (apiResponseDecorator) {
|
|
2330
3115
|
const args = apiResponseDecorator.getArguments();
|
|
2331
3116
|
const optsArg = args[0];
|
|
2332
|
-
if (optsArg &&
|
|
3117
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2333
3118
|
for (const prop of optsArg.getProperties()) {
|
|
2334
|
-
if (!
|
|
3119
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2335
3120
|
if (prop.getName() !== "type") continue;
|
|
2336
3121
|
const val = prop.getInitializer();
|
|
2337
3122
|
if (!val) continue;
|
|
2338
|
-
if (
|
|
3123
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2339
3124
|
const elements = val.getElements();
|
|
2340
3125
|
const firstEl = elements[0];
|
|
2341
3126
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2356,24 +3141,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2356
3141
|
}
|
|
2357
3142
|
function apiResponseStatus(decorator) {
|
|
2358
3143
|
const optsArg = decorator.getArguments()[0];
|
|
2359
|
-
if (!optsArg || !
|
|
3144
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2360
3145
|
for (const prop of optsArg.getProperties()) {
|
|
2361
|
-
if (!
|
|
3146
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2362
3147
|
if (prop.getName() !== "status") continue;
|
|
2363
3148
|
const val = prop.getInitializer();
|
|
2364
|
-
if (val &&
|
|
3149
|
+
if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2365
3150
|
}
|
|
2366
3151
|
return null;
|
|
2367
3152
|
}
|
|
2368
3153
|
function apiResponseTypeNode(decorator) {
|
|
2369
3154
|
const optsArg = decorator.getArguments()[0];
|
|
2370
|
-
if (!optsArg || !
|
|
3155
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2371
3156
|
for (const prop of optsArg.getProperties()) {
|
|
2372
|
-
if (!
|
|
3157
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2373
3158
|
if (prop.getName() !== "type") continue;
|
|
2374
3159
|
const val = prop.getInitializer();
|
|
2375
3160
|
if (!val) return null;
|
|
2376
|
-
if (
|
|
3161
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2377
3162
|
const first = val.getElements()[0];
|
|
2378
3163
|
return first ? { node: first, isArray: true } : null;
|
|
2379
3164
|
}
|
|
@@ -2391,7 +3176,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2391
3176
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2392
3177
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2393
3178
|
let ref = null;
|
|
2394
|
-
if (
|
|
3179
|
+
if (Node10.isIdentifier(typeInfo.node)) {
|
|
2395
3180
|
const name = typeInfo.node.getText();
|
|
2396
3181
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2397
3182
|
if (localDecl?.isExported()) {
|
|
@@ -2408,7 +3193,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2408
3193
|
return null;
|
|
2409
3194
|
}
|
|
2410
3195
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2411
|
-
if (!
|
|
3196
|
+
if (!Node10.isIdentifier(node)) return "unknown";
|
|
2412
3197
|
const name = node.getText();
|
|
2413
3198
|
const resolved = findType(name, sourceFile, project);
|
|
2414
3199
|
if (resolved) {
|
|
@@ -2428,9 +3213,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2428
3213
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2429
3214
|
function detectBinaryResponse(method) {
|
|
2430
3215
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2431
|
-
if (!node || !
|
|
3216
|
+
if (!node || !Node10.isTypeReference(node)) return false;
|
|
2432
3217
|
const typeName = node.getTypeName();
|
|
2433
|
-
const name =
|
|
3218
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2434
3219
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2435
3220
|
}
|
|
2436
3221
|
function detectStreamElement(method) {
|
|
@@ -2445,9 +3230,9 @@ function detectStreamElement(method) {
|
|
|
2445
3230
|
return null;
|
|
2446
3231
|
}
|
|
2447
3232
|
function streamContainerElement(node) {
|
|
2448
|
-
if (!node || !
|
|
3233
|
+
if (!node || !Node10.isTypeReference(node)) return null;
|
|
2449
3234
|
const typeName = node.getTypeName();
|
|
2450
|
-
const name =
|
|
3235
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2451
3236
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2452
3237
|
return node.getTypeArguments()[0] ?? null;
|
|
2453
3238
|
}
|
|
@@ -2457,9 +3242,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2457
3242
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2458
3243
|
}
|
|
2459
3244
|
function unwrapNamedContainer(node, names) {
|
|
2460
|
-
if (!node || !
|
|
3245
|
+
if (!node || !Node10.isTypeReference(node)) return node;
|
|
2461
3246
|
const typeName = node.getTypeName();
|
|
2462
|
-
const name =
|
|
3247
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2463
3248
|
if (names.has(name)) {
|
|
2464
3249
|
return node.getTypeArguments()[0] ?? node;
|
|
2465
3250
|
}
|
|
@@ -2480,19 +3265,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2480
3265
|
body = body ?? bodyType;
|
|
2481
3266
|
}
|
|
2482
3267
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3268
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2483
3269
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2484
3270
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
2485
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3271
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2486
3272
|
return null;
|
|
2487
3273
|
}
|
|
2488
3274
|
let bodyRef = null;
|
|
2489
3275
|
let queryRef = null;
|
|
2490
3276
|
let responseRef = null;
|
|
2491
3277
|
for (const param of method.getParameters()) {
|
|
2492
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3278
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2493
3279
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2494
3280
|
}
|
|
2495
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3281
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2496
3282
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2497
3283
|
}
|
|
2498
3284
|
}
|
|
@@ -2505,11 +3291,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2505
3291
|
if (apiResp) {
|
|
2506
3292
|
const args = apiResp.getArguments();
|
|
2507
3293
|
const optsArg = args[0];
|
|
2508
|
-
if (optsArg &&
|
|
3294
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2509
3295
|
for (const prop of optsArg.getProperties()) {
|
|
2510
|
-
if (
|
|
3296
|
+
if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2511
3297
|
const val = prop.getInitializer();
|
|
2512
|
-
if (val &&
|
|
3298
|
+
if (val && Node10.isIdentifier(val)) {
|
|
2513
3299
|
const name = val.getText();
|
|
2514
3300
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2515
3301
|
if (localDecl?.isExported()) {
|
|
@@ -2545,6 +3331,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2545
3331
|
response,
|
|
2546
3332
|
error: errorInfo?.type ?? null,
|
|
2547
3333
|
params: paramsType,
|
|
3334
|
+
paramTypes,
|
|
2548
3335
|
queryRef,
|
|
2549
3336
|
bodyRef,
|
|
2550
3337
|
responseRef,
|
|
@@ -2565,6 +3352,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2565
3352
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2566
3353
|
for (const param of method.getParameters()) {
|
|
2567
3354
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3355
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2568
3356
|
const typeNode = param.getTypeNode();
|
|
2569
3357
|
if (!typeNode) continue;
|
|
2570
3358
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2577,129 +3365,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2577
3365
|
return null;
|
|
2578
3366
|
}
|
|
2579
3367
|
|
|
2580
|
-
// src/discovery/zod-ast-to-ts.ts
|
|
2581
|
-
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
2582
|
-
function zodAstToTs(node) {
|
|
2583
|
-
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2584
|
-
const expr = node.getExpression();
|
|
2585
|
-
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2586
|
-
const methodName = expr.getName();
|
|
2587
|
-
const receiver = expr.getExpression();
|
|
2588
|
-
if (methodName === "optional") {
|
|
2589
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
2590
|
-
}
|
|
2591
|
-
if (methodName === "nullable") {
|
|
2592
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
2593
|
-
}
|
|
2594
|
-
const args = node.getArguments();
|
|
2595
|
-
switch (methodName) {
|
|
2596
|
-
case "string":
|
|
2597
|
-
return "string";
|
|
2598
|
-
case "number":
|
|
2599
|
-
return "number";
|
|
2600
|
-
case "boolean":
|
|
2601
|
-
return "boolean";
|
|
2602
|
-
case "unknown":
|
|
2603
|
-
return "unknown";
|
|
2604
|
-
case "any":
|
|
2605
|
-
return "unknown";
|
|
2606
|
-
case "literal": {
|
|
2607
|
-
const lit = args[0];
|
|
2608
|
-
if (!lit) return "unknown";
|
|
2609
|
-
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2610
|
-
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2611
|
-
if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
|
|
2612
|
-
if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
|
|
2613
|
-
return "unknown";
|
|
2614
|
-
}
|
|
2615
|
-
case "enum": {
|
|
2616
|
-
const arrArg = args[0];
|
|
2617
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2618
|
-
const members = arrArg.getElements().map(
|
|
2619
|
-
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2620
|
-
);
|
|
2621
|
-
return members.join(" | ");
|
|
2622
|
-
}
|
|
2623
|
-
case "array": {
|
|
2624
|
-
const inner = args[0];
|
|
2625
|
-
if (!inner) return "unknown";
|
|
2626
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
2627
|
-
}
|
|
2628
|
-
case "object": {
|
|
2629
|
-
const objArg = args[0];
|
|
2630
|
-
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2631
|
-
const lines = [];
|
|
2632
|
-
for (const prop of objArg.getProperties()) {
|
|
2633
|
-
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2634
|
-
const key = prop.getName();
|
|
2635
|
-
const valNode = prop.getInitializer();
|
|
2636
|
-
if (!valNode) continue;
|
|
2637
|
-
const tsType = zodAstToTs(valNode);
|
|
2638
|
-
const isOpt = isOptionalChain(valNode);
|
|
2639
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2640
|
-
}
|
|
2641
|
-
return `{ ${lines.join("; ")} }`;
|
|
2642
|
-
}
|
|
2643
|
-
case "union": {
|
|
2644
|
-
const arrArg = args[0];
|
|
2645
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2646
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2647
|
-
}
|
|
2648
|
-
case "record": {
|
|
2649
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2650
|
-
if (!valArg) return "unknown";
|
|
2651
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2652
|
-
}
|
|
2653
|
-
case "tuple": {
|
|
2654
|
-
const arrArg = args[0];
|
|
2655
|
-
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2656
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2657
|
-
}
|
|
2658
|
-
default:
|
|
2659
|
-
return "unknown";
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
return "unknown";
|
|
2663
|
-
}
|
|
2664
|
-
function isOptionalChain(node) {
|
|
2665
|
-
if (!Node8.isCallExpression(node)) return false;
|
|
2666
|
-
const expr = node.getExpression();
|
|
2667
|
-
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2668
|
-
}
|
|
2669
|
-
function parseDefineContractCall(callExpr) {
|
|
2670
|
-
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2671
|
-
const callee = callExpr.getExpression();
|
|
2672
|
-
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2673
|
-
if (calleeName !== "defineContract") return null;
|
|
2674
|
-
const args = callExpr.getArguments();
|
|
2675
|
-
const optsArg = args[0];
|
|
2676
|
-
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2677
|
-
let query = null;
|
|
2678
|
-
let body = null;
|
|
2679
|
-
let response = "unknown";
|
|
2680
|
-
let error = null;
|
|
2681
|
-
let bodyZodText = null;
|
|
2682
|
-
let queryZodText = null;
|
|
2683
|
-
for (const prop of optsArg.getProperties()) {
|
|
2684
|
-
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2685
|
-
const propName = prop.getName();
|
|
2686
|
-
const val = prop.getInitializer();
|
|
2687
|
-
if (!val) continue;
|
|
2688
|
-
if (propName === "query") {
|
|
2689
|
-
query = zodAstToTs(val);
|
|
2690
|
-
queryZodText = val.getText();
|
|
2691
|
-
} else if (propName === "body") {
|
|
2692
|
-
body = zodAstToTs(val);
|
|
2693
|
-
bodyZodText = val.getText();
|
|
2694
|
-
} else if (propName === "response") {
|
|
2695
|
-
response = zodAstToTs(val);
|
|
2696
|
-
} else if (propName === "error") {
|
|
2697
|
-
error = zodAstToTs(val);
|
|
2698
|
-
}
|
|
2699
|
-
}
|
|
2700
|
-
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2701
|
-
}
|
|
2702
|
-
|
|
2703
3368
|
// src/discovery/contracts-fast.ts
|
|
2704
3369
|
async function discoverContractsFast(opts) {
|
|
2705
3370
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2712,7 +3377,9 @@ async function discoverContractsFast(opts) {
|
|
|
2712
3377
|
}
|
|
2713
3378
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2714
3379
|
clearMixinTypeProject();
|
|
2715
|
-
|
|
3380
|
+
const routes = extractAllRoutes(project);
|
|
3381
|
+
clearSchemaTypeProject(project);
|
|
3382
|
+
return routes;
|
|
2716
3383
|
}
|
|
2717
3384
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2718
3385
|
return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
|
|
@@ -2724,25 +3391,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2724
3391
|
};
|
|
2725
3392
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2726
3393
|
if (!existsSync(tsconfigPath)) return { options: null, files: [] };
|
|
2727
|
-
const read =
|
|
3394
|
+
const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
2728
3395
|
if (read.error) {
|
|
2729
3396
|
return {
|
|
2730
3397
|
options: null,
|
|
2731
3398
|
files: [tsconfigPath],
|
|
2732
|
-
error:
|
|
3399
|
+
error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2733
3400
|
};
|
|
2734
3401
|
}
|
|
2735
3402
|
const host = {
|
|
2736
|
-
useCaseSensitiveFileNames:
|
|
3403
|
+
useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
|
|
2737
3404
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2738
3405
|
readDirectory: () => [],
|
|
2739
|
-
fileExists: (path) =>
|
|
3406
|
+
fileExists: (path) => ts3.sys.fileExists(path),
|
|
2740
3407
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2741
3408
|
// in a base tsconfig keeps working.
|
|
2742
|
-
readFile: (path) =>
|
|
3409
|
+
readFile: (path) => ts3.sys.readFile(path)
|
|
2743
3410
|
};
|
|
2744
|
-
const sourceFile =
|
|
2745
|
-
const parsed =
|
|
3411
|
+
const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
3412
|
+
const parsed = ts3.parseJsonSourceFileConfigFileContent(
|
|
2746
3413
|
sourceFile,
|
|
2747
3414
|
host,
|
|
2748
3415
|
dirname2(tsconfigPath),
|
|
@@ -2765,7 +3432,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2765
3432
|
`[nestjs-codegen/fast] Could not load ${tsconfigPath}: ${tsconfig.error} \u2014 continuing WITHOUT its compiler options, so path alias imports (e.g. '@/...') will not resolve and controllers extending an alias-imported factory will contribute NO routes to the generated client.`
|
|
2766
3433
|
);
|
|
2767
3434
|
}
|
|
2768
|
-
return new
|
|
3435
|
+
return new Project4({
|
|
2769
3436
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2770
3437
|
skipAddingFilesFromTsConfig: true,
|
|
2771
3438
|
skipLoadingLibFiles: true,
|
|
@@ -2877,15 +3544,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2877
3544
|
clearTypeResolutionCaches(this.project);
|
|
2878
3545
|
clearEnumCache(this.project);
|
|
2879
3546
|
clearMixinTypeProject();
|
|
3547
|
+
clearSchemaTypeProject(this.project);
|
|
2880
3548
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2881
3549
|
}
|
|
2882
3550
|
};
|
|
2883
3551
|
function decoratorStringArg(decoratorExpr) {
|
|
2884
3552
|
if (!decoratorExpr) return void 0;
|
|
2885
|
-
if (
|
|
2886
|
-
if (
|
|
3553
|
+
if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3554
|
+
if (Node11.isArrayLiteralExpression(decoratorExpr)) {
|
|
2887
3555
|
const first = decoratorExpr.getElements()[0];
|
|
2888
|
-
if (first &&
|
|
3556
|
+
if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
|
|
2889
3557
|
}
|
|
2890
3558
|
return void 0;
|
|
2891
3559
|
}
|
|
@@ -3006,9 +3674,9 @@ function extractContractRoute(args) {
|
|
|
3006
3674
|
let contractDef = null;
|
|
3007
3675
|
let bodyZodRef = null;
|
|
3008
3676
|
let queryZodRef = null;
|
|
3009
|
-
if (
|
|
3010
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3011
|
-
} else if (
|
|
3677
|
+
if (Node11.isCallExpression(firstDecoratorArg)) {
|
|
3678
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3679
|
+
} else if (Node11.isIdentifier(firstDecoratorArg)) {
|
|
3012
3680
|
const identName = firstDecoratorArg.getText();
|
|
3013
3681
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3014
3682
|
if (!resolvedVar) {
|
|
@@ -3020,7 +3688,7 @@ function extractContractRoute(args) {
|
|
|
3020
3688
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3021
3689
|
const initializer = varDecl.getInitializer();
|
|
3022
3690
|
if (!initializer) return null;
|
|
3023
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3691
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3024
3692
|
if (contractDef && varDecl.isExported()) {
|
|
3025
3693
|
const filePath = declFile.getFilePath();
|
|
3026
3694
|
if (contractDef.body !== null) {
|
|
@@ -3091,6 +3759,7 @@ function extractDtoRoute(args) {
|
|
|
3091
3759
|
body: dtoContract?.body ?? null,
|
|
3092
3760
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3093
3761
|
error: dtoContract?.error ?? null,
|
|
3762
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3094
3763
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3095
3764
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3096
3765
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3213,7 +3882,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3213
3882
|
},
|
|
3214
3883
|
project() {
|
|
3215
3884
|
if (!project) {
|
|
3216
|
-
project = new
|
|
3885
|
+
project = new Project5({
|
|
3217
3886
|
skipAddingFilesFromTsConfig: true,
|
|
3218
3887
|
skipLoadingLibFiles: true,
|
|
3219
3888
|
skipFileDependencyResolution: true,
|
|
@@ -3331,10 +4000,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3331
4000
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3332
4001
|
}
|
|
3333
4002
|
}
|
|
3334
|
-
function buildParamsType(params) {
|
|
4003
|
+
function buildParamsType(params, paramTypes) {
|
|
3335
4004
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3336
4005
|
if (pathParams.length === 0) return "never";
|
|
3337
|
-
|
|
4006
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
4007
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3338
4008
|
}
|
|
3339
4009
|
function hasPathParams(params) {
|
|
3340
4010
|
return params.some((p) => p.source === "path");
|
|
@@ -3428,7 +4098,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3428
4098
|
}
|
|
3429
4099
|
const response = buildResponseType(c, outDir, serialization);
|
|
3430
4100
|
const error = buildErrorType(c);
|
|
3431
|
-
const params = buildParamsType(c.params);
|
|
4101
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3432
4102
|
const safeMethod = JSON.stringify(method);
|
|
3433
4103
|
const safeUrl = JSON.stringify(c.path);
|
|
3434
4104
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3516,28 +4186,28 @@ function renderFetcherRequest(req, binaryResponse) {
|
|
|
3516
4186
|
}
|
|
3517
4187
|
function emitReqHelper() {
|
|
3518
4188
|
return [
|
|
3519
|
-
"/**
|
|
3520
|
-
"type
|
|
3521
|
-
"
|
|
4189
|
+
"/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
|
|
4190
|
+
"export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
|
|
4191
|
+
"class __Request<R> extends Promise<R> {",
|
|
4192
|
+
" static override get [Symbol.species](): PromiseConstructor {",
|
|
4193
|
+
" return Promise;",
|
|
4194
|
+
" }",
|
|
4195
|
+
" private __run: () => Promise<R>;",
|
|
4196
|
+
" private __p: Promise<R> | undefined;",
|
|
4197
|
+
" constructor(run: () => Promise<R>) {",
|
|
4198
|
+
" super(() => {});",
|
|
4199
|
+
" this.__run = run;",
|
|
4200
|
+
" }",
|
|
4201
|
+
" override then<T1 = R, T2 = never>(",
|
|
3522
4202
|
" onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
|
|
3523
4203
|
" onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
|
|
3524
|
-
" ): Promise<T1 | T2
|
|
3525
|
-
"
|
|
3526
|
-
"
|
|
3527
|
-
"
|
|
3528
|
-
"}
|
|
3529
|
-
"function __req<R>(run: () => Promise<R
|
|
3530
|
-
"
|
|
3531
|
-
" const __promise = () => {",
|
|
3532
|
-
" __p ??= run();",
|
|
3533
|
-
" return __p;",
|
|
3534
|
-
" };",
|
|
3535
|
-
" return {",
|
|
3536
|
-
" then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
|
|
3537
|
-
" catch: (onrejected) => __promise().catch(onrejected),",
|
|
3538
|
-
" finally: (onfinally) => __promise().finally(onfinally),",
|
|
3539
|
-
" fetch: run,",
|
|
3540
|
-
" };",
|
|
4204
|
+
" ): Promise<T1 | T2> {",
|
|
4205
|
+
" this.__p ??= this.__run();",
|
|
4206
|
+
" return this.__p.then(onfulfilled, onrejected);",
|
|
4207
|
+
" }",
|
|
4208
|
+
"}",
|
|
4209
|
+
"function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
|
|
4210
|
+
" return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
|
|
3541
4211
|
"}",
|
|
3542
4212
|
""
|
|
3543
4213
|
];
|
|
@@ -3555,18 +4225,26 @@ function emitFilterFieldGuard() {
|
|
|
3555
4225
|
];
|
|
3556
4226
|
}
|
|
3557
4227
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
3558
|
-
const
|
|
3559
|
-
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4228
|
+
const memberLines = [];
|
|
3560
4229
|
if (req.filterFieldsExpr) {
|
|
3561
|
-
|
|
4230
|
+
memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
3562
4231
|
}
|
|
3563
4232
|
if (streamExpr) {
|
|
3564
|
-
|
|
4233
|
+
memberLines.push(`${pad} stream: () => ${streamExpr},`);
|
|
3565
4234
|
}
|
|
3566
4235
|
for (const [name, value] of Object.entries(members)) {
|
|
3567
|
-
|
|
3568
|
-
}
|
|
3569
|
-
|
|
4236
|
+
memberLines.push(`${pad} ${name}: ${value},`);
|
|
4237
|
+
}
|
|
4238
|
+
const body = [requestExpr, ...memberLines].join("\n");
|
|
4239
|
+
const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
|
|
4240
|
+
const lines = [`${pad}${objKey}: (${param}) =>`];
|
|
4241
|
+
if (memberLines.length === 0) {
|
|
4242
|
+
lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4243
|
+
return lines;
|
|
4244
|
+
}
|
|
4245
|
+
lines.push(`${pad} __req(() => ${requestExpr}, {`);
|
|
4246
|
+
lines.push(...memberLines);
|
|
4247
|
+
lines.push(`${pad} }),`);
|
|
3570
4248
|
return lines;
|
|
3571
4249
|
}
|
|
3572
4250
|
function renderStreamExpr(req) {
|
|
@@ -3697,6 +4375,26 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3697
4375
|
"}",
|
|
3698
4376
|
""
|
|
3699
4377
|
];
|
|
4378
|
+
function isClientCallable(route) {
|
|
4379
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4380
|
+
}
|
|
4381
|
+
var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
|
|
4382
|
+
var ROUTES_EXPORTS = [
|
|
4383
|
+
// Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
|
|
4384
|
+
{ name: "route", typeOnly: false },
|
|
4385
|
+
{ name: "ROUTES", typeOnly: false },
|
|
4386
|
+
{ name: "RouteName", typeOnly: true },
|
|
4387
|
+
{ name: "ExtractParams", typeOnly: true },
|
|
4388
|
+
{ name: "RouteParams", typeOnly: true }
|
|
4389
|
+
];
|
|
4390
|
+
function finishRoutesImport(lines) {
|
|
4391
|
+
const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
|
|
4392
|
+
const used = ROUTES_EXPORTS.filter(
|
|
4393
|
+
({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
|
|
4394
|
+
).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
|
|
4395
|
+
const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
|
|
4396
|
+
return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
|
|
4397
|
+
}
|
|
3700
4398
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3701
4399
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3702
4400
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3704,7 +4402,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3704
4402
|
const { layer } = resolveApiSlots(extensions);
|
|
3705
4403
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3706
4404
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3707
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4405
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3708
4406
|
const ctx = opts.ctx ?? {
|
|
3709
4407
|
cwd: outDir ?? "",
|
|
3710
4408
|
outDir: outDir ?? "",
|
|
@@ -3756,9 +4454,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3756
4454
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
3757
4455
|
}
|
|
3758
4456
|
lines.push(...extImports);
|
|
3759
|
-
lines.push(
|
|
3760
|
-
"import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
|
|
3761
|
-
);
|
|
4457
|
+
lines.push(ROUTES_IMPORT_PLACEHOLDER);
|
|
3762
4458
|
const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
|
|
3763
4459
|
lines.push(`import type { Fetcher } from '${runtimeImport}';`);
|
|
3764
4460
|
if (serialization === "json" && contracted.length > 0) {
|
|
@@ -3809,7 +4505,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3809
4505
|
lines.push(...statements, "");
|
|
3810
4506
|
}
|
|
3811
4507
|
}
|
|
3812
|
-
return lines
|
|
4508
|
+
return finishRoutesImport(lines);
|
|
3813
4509
|
}
|
|
3814
4510
|
const tree = /* @__PURE__ */ new Map();
|
|
3815
4511
|
for (const r of contracted) {
|
|
@@ -3862,7 +4558,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3862
4558
|
lines.push(...statements, "");
|
|
3863
4559
|
}
|
|
3864
4560
|
}
|
|
3865
|
-
return lines
|
|
4561
|
+
return finishRoutesImport(lines);
|
|
3866
4562
|
}
|
|
3867
4563
|
|
|
3868
4564
|
// src/emit/emit-cache.ts
|
|
@@ -3875,6 +4571,7 @@ async function emitCache(pages, outDir) {
|
|
|
3875
4571
|
const s = await stat(p.absolutePath);
|
|
3876
4572
|
return {
|
|
3877
4573
|
name: p.name,
|
|
4574
|
+
scope: p.scope ?? "default",
|
|
3878
4575
|
relativePath: p.relativePath,
|
|
3879
4576
|
mtime: s.mtime.toISOString()
|
|
3880
4577
|
};
|
|
@@ -4622,6 +5319,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4622
5319
|
const paths = {};
|
|
4623
5320
|
for (const route of routes) {
|
|
4624
5321
|
if (!route.contract) continue;
|
|
5322
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4625
5323
|
const oaPath = toOpenApiPath(route.path);
|
|
4626
5324
|
const method = route.method.toLowerCase();
|
|
4627
5325
|
let pathItem = paths[oaPath];
|
|
@@ -4665,10 +5363,11 @@ async function emitPages(pages, outDir, _options = {}) {
|
|
|
4665
5363
|
}).join("\n");
|
|
4666
5364
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
4667
5365
|
const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
|
|
5366
|
+
const scopesBlock = buildScopesBlock(pages);
|
|
4668
5367
|
const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
|
|
4669
5368
|
|
|
4670
5369
|
export type InertiaPageName = ${pageNameUnion};
|
|
4671
|
-
${propsHelper}
|
|
5370
|
+
${propsHelper}${scopesBlock}
|
|
4672
5371
|
declare module '@dudousxd/nestjs-inertia' {
|
|
4673
5372
|
interface InertiaPages {
|
|
4674
5373
|
${augBody}
|
|
@@ -4677,6 +5376,31 @@ ${sharedPropsBlock}}
|
|
|
4677
5376
|
`;
|
|
4678
5377
|
await writeFile7(join11(outDir, "pages.d.ts"), content, "utf8");
|
|
4679
5378
|
}
|
|
5379
|
+
function buildScopesBlock(pages) {
|
|
5380
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
5381
|
+
for (const page of pages) {
|
|
5382
|
+
const scope = page.scope ?? "default";
|
|
5383
|
+
const names = byScope.get(scope) ?? [];
|
|
5384
|
+
names.push(JSON.stringify(page.name));
|
|
5385
|
+
byScope.set(scope, names);
|
|
5386
|
+
}
|
|
5387
|
+
if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
|
|
5388
|
+
if (!byScope.has("default")) byScope.set("default", []);
|
|
5389
|
+
const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
|
|
5390
|
+
const body = scopes.map((scope) => {
|
|
5391
|
+
const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
|
|
5392
|
+
const names = byScope.get(scope) ?? [];
|
|
5393
|
+
return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
|
|
5394
|
+
}).join("\n");
|
|
5395
|
+
return `
|
|
5396
|
+
/** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
|
|
5397
|
+
export interface InertiaScopePages {
|
|
5398
|
+
${body}
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
|
|
5402
|
+
`;
|
|
5403
|
+
}
|
|
4680
5404
|
function buildSharedPropsBlock(sharedProps) {
|
|
4681
5405
|
if (!sharedProps) return "";
|
|
4682
5406
|
if (sharedProps.isImportRef) {
|
|
@@ -4884,8 +5608,7 @@ function diffConfigKeyHashes(previous, current) {
|
|
|
4884
5608
|
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
4885
5609
|
}
|
|
4886
5610
|
async function discoverInputFiles(config) {
|
|
4887
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
4888
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
5611
|
+
const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
|
|
4889
5612
|
const cwd = config.codegen.cwd;
|
|
4890
5613
|
const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4891
5614
|
return [...new Set(matched)].sort();
|
|
@@ -5016,12 +5739,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
5016
5739
|
}
|
|
5017
5740
|
if (config.pages) {
|
|
5018
5741
|
const pagesConfig = config.pages;
|
|
5019
|
-
const pages = await
|
|
5020
|
-
glob: pagesConfig.glob,
|
|
5021
|
-
cwd: config.codegen.cwd,
|
|
5022
|
-
propsExport: pagesConfig.propsExport,
|
|
5023
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
5024
|
-
});
|
|
5742
|
+
const pages = await discoverPageScopes(config);
|
|
5025
5743
|
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
5026
5744
|
await emitPages(pages, config.codegen.outDir, {
|
|
5027
5745
|
propsExport: pagesConfig.propsExport,
|
|
@@ -5196,12 +5914,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
5196
5914
|
await runInitialPass();
|
|
5197
5915
|
}
|
|
5198
5916
|
let pagesDebounceTimer;
|
|
5199
|
-
const
|
|
5200
|
-
const pagesWatcher = chokidar.watch(
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5917
|
+
const pageGlobs = allPageGlobs(config);
|
|
5918
|
+
const pagesWatcher = chokidar.watch(
|
|
5919
|
+
(pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
|
|
5920
|
+
(glob) => join16(config.codegen.cwd, glob)
|
|
5921
|
+
),
|
|
5922
|
+
{
|
|
5923
|
+
ignoreInitial: true,
|
|
5924
|
+
persistent: true,
|
|
5925
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
5926
|
+
}
|
|
5927
|
+
);
|
|
5205
5928
|
function schedulePagesRegenerate() {
|
|
5206
5929
|
if (pagesDebounceTimer !== void 0) {
|
|
5207
5930
|
clearTimeout(pagesDebounceTimer);
|
|
@@ -5281,7 +6004,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
5281
6004
|
}
|
|
5282
6005
|
|
|
5283
6006
|
// src/index.ts
|
|
5284
|
-
var VERSION = "0.
|
|
6007
|
+
var VERSION = "0.27.0";
|
|
5285
6008
|
|
|
5286
6009
|
// src/cli/codegen.ts
|
|
5287
6010
|
async function runCodegen(opts = {}) {
|