@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/index.js
CHANGED
|
@@ -88,12 +88,33 @@ function validateUserConfig(userConfig) {
|
|
|
88
88
|
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
|
-
if (userConfig.pages &&
|
|
91
|
+
if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
|
|
92
92
|
throw new ConfigError(
|
|
93
|
-
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
93
|
+
"Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
|
|
97
|
+
if (scope === "default") {
|
|
98
|
+
throw new ConfigError(
|
|
99
|
+
'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (!isGlobInput(scopeConfig?.glob)) {
|
|
103
|
+
throw new ConfigError(
|
|
104
|
+
`Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
|
|
109
|
+
console.warn(
|
|
110
|
+
"[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)."
|
|
94
111
|
);
|
|
95
112
|
}
|
|
96
113
|
}
|
|
114
|
+
function isGlobInput(value) {
|
|
115
|
+
if (typeof value === "string") return true;
|
|
116
|
+
return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
|
|
117
|
+
}
|
|
97
118
|
function applyDefaults(userConfig, cwd) {
|
|
98
119
|
validateUserConfig(userConfig);
|
|
99
120
|
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : join(cwd, ".nestjs-codegen");
|
|
@@ -120,7 +141,17 @@ function applyDefaults(userConfig, cwd) {
|
|
|
120
141
|
pages: userConfig.pages ? {
|
|
121
142
|
glob: userConfig.pages.glob,
|
|
122
143
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
123
|
-
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
|
|
144
|
+
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
|
|
145
|
+
scopes: Object.fromEntries(
|
|
146
|
+
Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
|
|
147
|
+
scope,
|
|
148
|
+
{
|
|
149
|
+
glob: scopeConfig.glob,
|
|
150
|
+
prefix: scopeConfig.prefix ?? `${scope}/`,
|
|
151
|
+
componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
|
|
152
|
+
}
|
|
153
|
+
])
|
|
154
|
+
)
|
|
124
155
|
} : null,
|
|
125
156
|
contracts: {
|
|
126
157
|
glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
|
|
@@ -194,26 +225,84 @@ import { dirname as dirname4, join as join14 } from "path";
|
|
|
194
225
|
|
|
195
226
|
// src/discovery/pages.ts
|
|
196
227
|
import { readFile } from "fs/promises";
|
|
197
|
-
import { join as join2, relative as relative2 } from "path";
|
|
228
|
+
import { join as join2, relative as relative2, sep as sep2 } from "path";
|
|
198
229
|
import fg from "fast-glob";
|
|
199
230
|
var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
|
|
231
|
+
var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
|
|
232
|
+
function globStaticBase(glob) {
|
|
233
|
+
const segments = glob.split("/");
|
|
234
|
+
const staticSegments = [];
|
|
235
|
+
for (const segment of segments.slice(0, -1)) {
|
|
236
|
+
if (MAGIC_SEGMENT_RE.test(segment)) break;
|
|
237
|
+
staticSegments.push(segment);
|
|
238
|
+
}
|
|
239
|
+
return staticSegments.join("/");
|
|
240
|
+
}
|
|
200
241
|
async function discoverPages(opts) {
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
const pagesBase = join2(opts.cwd, globStatic);
|
|
242
|
+
const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
|
|
243
|
+
const prefix = opts.prefix ?? "";
|
|
244
|
+
const scope = opts.scope ?? "default";
|
|
245
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
206
246
|
const out = [];
|
|
207
|
-
for (const
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
247
|
+
for (const glob of globs) {
|
|
248
|
+
const files = (await fg(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
|
|
249
|
+
const pagesBase = join2(opts.cwd, globStaticBase(glob));
|
|
250
|
+
for (const file of files) {
|
|
251
|
+
seenFiles.add(file);
|
|
252
|
+
const rel = relative2(opts.cwd, file);
|
|
253
|
+
const nameRel = relative2(pagesBase, file).split(sep2).join("/");
|
|
254
|
+
const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
|
|
255
|
+
const source = await readFile(file, "utf8");
|
|
256
|
+
const propsSource = extractPropsSource(source, opts.propsExport);
|
|
257
|
+
out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
assertUniqueNames(out);
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
async function discoverPageScopes(config) {
|
|
264
|
+
const pages = config.pages;
|
|
265
|
+
if (!pages) return [];
|
|
266
|
+
const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
|
|
267
|
+
const out = await discoverPages({
|
|
268
|
+
...common,
|
|
269
|
+
glob: pages.glob,
|
|
270
|
+
componentNameStrategy: pages.componentNameStrategy
|
|
271
|
+
});
|
|
272
|
+
for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
|
|
273
|
+
out.push(
|
|
274
|
+
...await discoverPages({
|
|
275
|
+
...common,
|
|
276
|
+
glob: scopeConfig.glob,
|
|
277
|
+
componentNameStrategy: scopeConfig.componentNameStrategy,
|
|
278
|
+
prefix: scopeConfig.prefix,
|
|
279
|
+
scope
|
|
280
|
+
})
|
|
281
|
+
);
|
|
214
282
|
}
|
|
283
|
+
assertUniqueNames(out);
|
|
215
284
|
return out;
|
|
216
285
|
}
|
|
286
|
+
function allPageGlobs(config) {
|
|
287
|
+
if (!config.pages) return [];
|
|
288
|
+
const asList = (glob) => typeof glob === "string" ? [glob] : glob;
|
|
289
|
+
return [
|
|
290
|
+
...asList(config.pages.glob),
|
|
291
|
+
...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
function assertUniqueNames(pages) {
|
|
295
|
+
const byName = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const page of pages) {
|
|
297
|
+
const previous = byName.get(page.name);
|
|
298
|
+
if (previous) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`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.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
byName.set(page.name, page);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
217
306
|
function computeName(rel, strat) {
|
|
218
307
|
if (typeof strat === "function") return strat(rel);
|
|
219
308
|
const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
|
|
@@ -523,22 +612,22 @@ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from
|
|
|
523
612
|
|
|
524
613
|
// src/extension/registry.ts
|
|
525
614
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
526
|
-
import { Project as
|
|
615
|
+
import { Project as Project5 } from "ts-morph";
|
|
527
616
|
|
|
528
617
|
// src/discovery/contracts-fast.ts
|
|
529
618
|
import { existsSync } from "fs";
|
|
530
619
|
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
531
620
|
import fg2 from "fast-glob";
|
|
532
621
|
import {
|
|
533
|
-
Node as
|
|
534
|
-
Project as
|
|
535
|
-
ts
|
|
622
|
+
Node as Node11,
|
|
623
|
+
Project as Project4,
|
|
624
|
+
ts as ts3
|
|
536
625
|
} from "ts-morph";
|
|
537
626
|
|
|
538
627
|
// src/discovery/dto-type-resolver.ts
|
|
539
628
|
import {
|
|
540
|
-
Node as
|
|
541
|
-
SyntaxKind as
|
|
629
|
+
Node as Node10,
|
|
630
|
+
SyntaxKind as SyntaxKind4
|
|
542
631
|
} from "ts-morph";
|
|
543
632
|
|
|
544
633
|
// src/discovery/dto-to-ir.ts
|
|
@@ -2076,6 +2165,674 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2076
2165
|
return fields;
|
|
2077
2166
|
}
|
|
2078
2167
|
|
|
2168
|
+
// src/discovery/pipe-schema.ts
|
|
2169
|
+
import { Node as Node9 } from "ts-morph";
|
|
2170
|
+
|
|
2171
|
+
// src/discovery/schema-type-inference.ts
|
|
2172
|
+
import {
|
|
2173
|
+
ts as ts2
|
|
2174
|
+
} from "ts-morph";
|
|
2175
|
+
|
|
2176
|
+
// src/discovery/schema-type-program.ts
|
|
2177
|
+
import { Node as Node7, Project as Project3, ts } from "ts-morph";
|
|
2178
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2179
|
+
var SchemaTypeProgram = class {
|
|
2180
|
+
constructor(discovery) {
|
|
2181
|
+
this.discovery = discovery;
|
|
2182
|
+
const base = discovery.getCompilerOptions();
|
|
2183
|
+
this.options = {
|
|
2184
|
+
...base,
|
|
2185
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2186
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2187
|
+
target: base.target ?? ts.ScriptTarget.ES2022,
|
|
2188
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2189
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2190
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
|
|
2191
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2192
|
+
strictNullChecks: true,
|
|
2193
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2194
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2195
|
+
types: [],
|
|
2196
|
+
noEmit: true
|
|
2197
|
+
};
|
|
2198
|
+
this.project = new Project3({
|
|
2199
|
+
skipAddingFilesFromTsConfig: true,
|
|
2200
|
+
compilerOptions: this.options
|
|
2201
|
+
});
|
|
2202
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2203
|
+
this.flush();
|
|
2204
|
+
}
|
|
2205
|
+
project;
|
|
2206
|
+
options;
|
|
2207
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2208
|
+
scratch = new Project3({
|
|
2209
|
+
skipAddingFilesFromTsConfig: true,
|
|
2210
|
+
skipLoadingLibFiles: true,
|
|
2211
|
+
skipFileDependencyResolution: true
|
|
2212
|
+
});
|
|
2213
|
+
needs = /* @__PURE__ */ new Map();
|
|
2214
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2215
|
+
written = /* @__PURE__ */ new Map();
|
|
2216
|
+
/**
|
|
2217
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2218
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2219
|
+
* program once.
|
|
2220
|
+
*/
|
|
2221
|
+
sourceFileFor(sf) {
|
|
2222
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2223
|
+
this.flush();
|
|
2224
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2225
|
+
}
|
|
2226
|
+
// -------------------------------------------------------------------------
|
|
2227
|
+
queue = [];
|
|
2228
|
+
request(path, want) {
|
|
2229
|
+
let needs = this.needs.get(path);
|
|
2230
|
+
const isNew = !needs;
|
|
2231
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2232
|
+
let grew = isNew;
|
|
2233
|
+
if (want.schema && !needs.schema) {
|
|
2234
|
+
needs.schema = true;
|
|
2235
|
+
grew = true;
|
|
2236
|
+
}
|
|
2237
|
+
if (want.all && !needs.all) {
|
|
2238
|
+
needs.all = true;
|
|
2239
|
+
grew = true;
|
|
2240
|
+
}
|
|
2241
|
+
for (const name of want.names ?? []) {
|
|
2242
|
+
if (!needs.names.has(name)) {
|
|
2243
|
+
needs.names.add(name);
|
|
2244
|
+
grew = true;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
this.needs.set(path, needs);
|
|
2248
|
+
if (grew) this.queue.push(path);
|
|
2249
|
+
}
|
|
2250
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2251
|
+
flush() {
|
|
2252
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2253
|
+
while (this.queue.length > 0) {
|
|
2254
|
+
const path = this.queue.pop();
|
|
2255
|
+
const original = this.original(path);
|
|
2256
|
+
if (!original) continue;
|
|
2257
|
+
const needs = this.needs.get(path);
|
|
2258
|
+
const text = this.prune(original, needs);
|
|
2259
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2260
|
+
}
|
|
2261
|
+
for (const [path, text] of changed) {
|
|
2262
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2263
|
+
this.written.set(path, text);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2267
|
+
original(path) {
|
|
2268
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2269
|
+
}
|
|
2270
|
+
/**
|
|
2271
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2272
|
+
* requesting from each kept one's target the names it brings in.
|
|
2273
|
+
*/
|
|
2274
|
+
prune(sf, needs) {
|
|
2275
|
+
const locals = topLevelDeclarations(sf);
|
|
2276
|
+
const roots = [];
|
|
2277
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2278
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2279
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2280
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2281
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2282
|
+
if (spec === void 0) {
|
|
2283
|
+
for (const named of exp.getNamedExports()) {
|
|
2284
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2285
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2286
|
+
}
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2290
|
+
for (const named of exp.getNamedExports()) {
|
|
2291
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2292
|
+
const list = reexported.get(exposed) ?? [];
|
|
2293
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2294
|
+
reexported.set(exposed, list);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
const unresolvedNames = [];
|
|
2298
|
+
for (const name of needs.names) {
|
|
2299
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2300
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2301
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2302
|
+
}
|
|
2303
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2304
|
+
const queue = [...roots];
|
|
2305
|
+
const seen = new Set(queue);
|
|
2306
|
+
while (queue.length > 0) {
|
|
2307
|
+
const node = queue.pop();
|
|
2308
|
+
const ids = Node7.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
|
|
2309
|
+
for (const id of ids) {
|
|
2310
|
+
const name = id.getText();
|
|
2311
|
+
if (referenced.has(name)) continue;
|
|
2312
|
+
referenced.add(name);
|
|
2313
|
+
for (const local of locals.get(name) ?? []) {
|
|
2314
|
+
if (seen.has(local)) continue;
|
|
2315
|
+
seen.add(local);
|
|
2316
|
+
queue.push(local);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const chars = sf.getFullText().split("");
|
|
2321
|
+
const blank = (node) => {
|
|
2322
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2323
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2327
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2328
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2329
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2330
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2331
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2332
|
+
imported: n.getName()
|
|
2333
|
+
})).filter((n) => referenced.has(n.local));
|
|
2334
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2335
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2336
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2337
|
+
blank(decl);
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
if (target) {
|
|
2341
|
+
this.request(target, {
|
|
2342
|
+
all: wantsNamespace,
|
|
2343
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2348
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2349
|
+
if (spec === void 0) continue;
|
|
2350
|
+
const target = this.resolve(spec, sf);
|
|
2351
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2352
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2353
|
+
blank(exp);
|
|
2354
|
+
} else if (target) {
|
|
2355
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2356
|
+
}
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
const wanted = exp.getNamedExports().filter(
|
|
2360
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2361
|
+
).map((n) => n.getNameNode().getText());
|
|
2362
|
+
if (wanted.length === 0) blank(exp);
|
|
2363
|
+
else if (target) this.request(target, { names: wanted });
|
|
2364
|
+
}
|
|
2365
|
+
return chars.join("");
|
|
2366
|
+
}
|
|
2367
|
+
/**
|
|
2368
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2369
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2370
|
+
*/
|
|
2371
|
+
resolve(specifier, from) {
|
|
2372
|
+
const resolved = ts.resolveModuleName(
|
|
2373
|
+
specifier,
|
|
2374
|
+
from.getFilePath(),
|
|
2375
|
+
this.options,
|
|
2376
|
+
ts.sys
|
|
2377
|
+
).resolvedModule;
|
|
2378
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2379
|
+
const file = resolved.resolvedFileName;
|
|
2380
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2381
|
+
return file;
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
function schemaPositions(sf) {
|
|
2385
|
+
const roots = [];
|
|
2386
|
+
sf.forEachDescendant((node) => {
|
|
2387
|
+
if (Node7.isDecorator(node)) {
|
|
2388
|
+
const name = node.getName();
|
|
2389
|
+
if (name === "ApplyContract") {
|
|
2390
|
+
roots.push(...node.getArguments());
|
|
2391
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2392
|
+
for (const arg of node.getArguments()) {
|
|
2393
|
+
if (Node7.isNewExpression(arg) || Node7.isCallExpression(arg)) {
|
|
2394
|
+
roots.push(...arg.getArguments());
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
} else if (Node7.isCallExpression(node)) {
|
|
2399
|
+
const callee = node.getExpression();
|
|
2400
|
+
const name = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2401
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
return roots;
|
|
2405
|
+
}
|
|
2406
|
+
function topLevelDeclarations(sf) {
|
|
2407
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2408
|
+
const add = (name, node) => {
|
|
2409
|
+
if (!name) return;
|
|
2410
|
+
const list = byName.get(name);
|
|
2411
|
+
if (list) list.push(node);
|
|
2412
|
+
else byName.set(name, [node]);
|
|
2413
|
+
};
|
|
2414
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2415
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2416
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2417
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2418
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2419
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2420
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2421
|
+
return byName;
|
|
2422
|
+
}
|
|
2423
|
+
function defaultExport(sf) {
|
|
2424
|
+
const nodes = [];
|
|
2425
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2426
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2427
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2428
|
+
return nodes;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/discovery/schema-type-inference.ts
|
|
2432
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2433
|
+
function programFor(discovery) {
|
|
2434
|
+
let program = programs.get(discovery);
|
|
2435
|
+
if (!program) {
|
|
2436
|
+
program = new SchemaTypeProgram(discovery);
|
|
2437
|
+
programs.set(discovery, program);
|
|
2438
|
+
}
|
|
2439
|
+
return program;
|
|
2440
|
+
}
|
|
2441
|
+
function clearSchemaTypeProject(discovery) {
|
|
2442
|
+
programs.delete(discovery);
|
|
2443
|
+
}
|
|
2444
|
+
function counterpart(typedSf, node) {
|
|
2445
|
+
const start = node.getStart();
|
|
2446
|
+
const end = node.getEnd();
|
|
2447
|
+
const kind = node.getKind();
|
|
2448
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2449
|
+
while (candidate) {
|
|
2450
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2451
|
+
return candidate;
|
|
2452
|
+
}
|
|
2453
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2454
|
+
candidate = candidate.getParent();
|
|
2455
|
+
}
|
|
2456
|
+
return void 0;
|
|
2457
|
+
}
|
|
2458
|
+
function propType(type, name, at) {
|
|
2459
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2460
|
+
}
|
|
2461
|
+
function isAnyOrUnknown(type) {
|
|
2462
|
+
return type.isAny() || type.isUnknown();
|
|
2463
|
+
}
|
|
2464
|
+
function inferSchema(expr, discovery) {
|
|
2465
|
+
try {
|
|
2466
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2467
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2468
|
+
if (!at) return { kind: "unresolved" };
|
|
2469
|
+
const schemaType = at.getType();
|
|
2470
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2471
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2472
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2473
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2474
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2475
|
+
if (stdInput && stdOutput) {
|
|
2476
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2477
|
+
}
|
|
2478
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2479
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2480
|
+
if (zodInput && zodOutput) {
|
|
2481
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2482
|
+
}
|
|
2483
|
+
return { kind: "not-schema" };
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
dbg("schema inference failed:", err);
|
|
2486
|
+
return { kind: "unresolved" };
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2490
|
+
"Date",
|
|
2491
|
+
"File",
|
|
2492
|
+
"Blob",
|
|
2493
|
+
"FormData",
|
|
2494
|
+
"URL",
|
|
2495
|
+
"RegExp",
|
|
2496
|
+
"Map",
|
|
2497
|
+
"Set",
|
|
2498
|
+
"ArrayBuffer",
|
|
2499
|
+
"Uint8Array"
|
|
2500
|
+
]);
|
|
2501
|
+
var MAX_DEPTH = 12;
|
|
2502
|
+
function objectKey(name) {
|
|
2503
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2504
|
+
}
|
|
2505
|
+
function isOptionalSymbol(sym) {
|
|
2506
|
+
return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
|
|
2507
|
+
}
|
|
2508
|
+
function isSymbolKeyed(sym) {
|
|
2509
|
+
return sym.getName().startsWith("__@");
|
|
2510
|
+
}
|
|
2511
|
+
function orderedProperties(type) {
|
|
2512
|
+
const position = (sym) => {
|
|
2513
|
+
const decl = sym.getDeclarations()[0];
|
|
2514
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2515
|
+
};
|
|
2516
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2517
|
+
if (a.at && b.at) {
|
|
2518
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2519
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2520
|
+
} else if (a.at || b.at) {
|
|
2521
|
+
return a.at ? -1 : 1;
|
|
2522
|
+
}
|
|
2523
|
+
return a.index - b.index;
|
|
2524
|
+
}).map((entry) => entry.sym);
|
|
2525
|
+
}
|
|
2526
|
+
function isFromDeclarationFile(sym) {
|
|
2527
|
+
const decl = sym?.getDeclarations()[0];
|
|
2528
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2529
|
+
}
|
|
2530
|
+
function render(type, state, paired) {
|
|
2531
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2532
|
+
if (isAnyOrUnknown(type)) {
|
|
2533
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2534
|
+
}
|
|
2535
|
+
if (type.isNever()) return "never";
|
|
2536
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2537
|
+
if (type.isNumber()) return "number";
|
|
2538
|
+
if (type.isBoolean()) return "boolean";
|
|
2539
|
+
if (type.isBigInt()) return "bigint";
|
|
2540
|
+
if (type.isNull()) return "null";
|
|
2541
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2542
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2543
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2544
|
+
const value = type.getLiteralValue();
|
|
2545
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2546
|
+
}
|
|
2547
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2548
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2549
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2550
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2551
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2552
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2553
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2554
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2555
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2556
|
+
}
|
|
2557
|
+
if (type.isTuple()) {
|
|
2558
|
+
const elements = type.getTupleElements();
|
|
2559
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2560
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2561
|
+
}
|
|
2562
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2563
|
+
return "unknown";
|
|
2564
|
+
}
|
|
2565
|
+
function renderUnion(type, state, paired) {
|
|
2566
|
+
const members = type.getUnionTypes();
|
|
2567
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2568
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2569
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2570
|
+
const arms = [];
|
|
2571
|
+
let sawBoolean = false;
|
|
2572
|
+
let hasNull = false;
|
|
2573
|
+
let hasUndefined = false;
|
|
2574
|
+
for (const member of members) {
|
|
2575
|
+
if (member.isNull()) {
|
|
2576
|
+
hasNull = true;
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
if (member.isUndefined()) {
|
|
2580
|
+
hasUndefined = true;
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2584
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2585
|
+
sawBoolean = true;
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2589
|
+
}
|
|
2590
|
+
if (hasNull) arms.push("null");
|
|
2591
|
+
if (hasUndefined) arms.push("undefined");
|
|
2592
|
+
const unique = [...new Set(arms)];
|
|
2593
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2594
|
+
return unique.join(" | ");
|
|
2595
|
+
}
|
|
2596
|
+
function renderIntersection(type, state) {
|
|
2597
|
+
const members = type.getIntersectionTypes();
|
|
2598
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2599
|
+
if (primitive) return render(primitive, state);
|
|
2600
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2601
|
+
}
|
|
2602
|
+
function renderObject(type, state, paired) {
|
|
2603
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2604
|
+
const name = symbol?.getName();
|
|
2605
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2606
|
+
const args = type.getTypeArguments();
|
|
2607
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2608
|
+
}
|
|
2609
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2610
|
+
return "unknown";
|
|
2611
|
+
}
|
|
2612
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2613
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2614
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2615
|
+
const inner = { ...state, stack };
|
|
2616
|
+
const fields = [];
|
|
2617
|
+
for (const prop of orderedProperties(type)) {
|
|
2618
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2619
|
+
const propName = prop.getName();
|
|
2620
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2621
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2622
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2623
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2624
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2625
|
+
}
|
|
2626
|
+
const stringIndex = type.getStringIndexType();
|
|
2627
|
+
if (stringIndex) {
|
|
2628
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2629
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2630
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2631
|
+
fields.push(`[key: string]: ${value}`);
|
|
2632
|
+
}
|
|
2633
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2634
|
+
}
|
|
2635
|
+
function stateAt(at) {
|
|
2636
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2637
|
+
}
|
|
2638
|
+
function requestTypeOf(types) {
|
|
2639
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2640
|
+
}
|
|
2641
|
+
function responseTypeOf(types) {
|
|
2642
|
+
return render(types.output, stateAt(types.at));
|
|
2643
|
+
}
|
|
2644
|
+
function requestFieldsOf(types) {
|
|
2645
|
+
const { input, output, at } = types;
|
|
2646
|
+
const objectInput = input.getNonNullableType();
|
|
2647
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2648
|
+
const state = stateAt(at);
|
|
2649
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2650
|
+
name: prop.getName(),
|
|
2651
|
+
type: render(
|
|
2652
|
+
prop.getTypeAtLocation(at),
|
|
2653
|
+
state,
|
|
2654
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2655
|
+
),
|
|
2656
|
+
optional: isOptionalSymbol(prop)
|
|
2657
|
+
}));
|
|
2658
|
+
}
|
|
2659
|
+
function acceptsUndefined(types) {
|
|
2660
|
+
const { input, output } = types;
|
|
2661
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2662
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2663
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2667
|
+
import { Node as Node8, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
2668
|
+
function zodAstToTs(node) {
|
|
2669
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
2670
|
+
const expr = node.getExpression();
|
|
2671
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
2672
|
+
const methodName = expr.getName();
|
|
2673
|
+
const receiver = expr.getExpression();
|
|
2674
|
+
if (methodName === "optional") {
|
|
2675
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2676
|
+
}
|
|
2677
|
+
if (methodName === "nullable") {
|
|
2678
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2679
|
+
}
|
|
2680
|
+
const args = node.getArguments();
|
|
2681
|
+
switch (methodName) {
|
|
2682
|
+
case "string":
|
|
2683
|
+
return "string";
|
|
2684
|
+
case "number":
|
|
2685
|
+
return "number";
|
|
2686
|
+
case "boolean":
|
|
2687
|
+
return "boolean";
|
|
2688
|
+
case "unknown":
|
|
2689
|
+
return "unknown";
|
|
2690
|
+
case "any":
|
|
2691
|
+
return "unknown";
|
|
2692
|
+
case "literal": {
|
|
2693
|
+
const lit = args[0];
|
|
2694
|
+
if (!lit) return "unknown";
|
|
2695
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2696
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2697
|
+
if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
|
|
2698
|
+
if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
|
|
2699
|
+
return "unknown";
|
|
2700
|
+
}
|
|
2701
|
+
case "enum": {
|
|
2702
|
+
const arrArg = args[0];
|
|
2703
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2704
|
+
const members = arrArg.getElements().map(
|
|
2705
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2706
|
+
);
|
|
2707
|
+
return members.join(" | ");
|
|
2708
|
+
}
|
|
2709
|
+
case "array": {
|
|
2710
|
+
const inner = args[0];
|
|
2711
|
+
if (!inner) return "unknown";
|
|
2712
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2713
|
+
}
|
|
2714
|
+
case "object": {
|
|
2715
|
+
const objArg = args[0];
|
|
2716
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2717
|
+
const lines = [];
|
|
2718
|
+
for (const prop of objArg.getProperties()) {
|
|
2719
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2720
|
+
const key = prop.getName();
|
|
2721
|
+
const valNode = prop.getInitializer();
|
|
2722
|
+
if (!valNode) continue;
|
|
2723
|
+
const tsType = zodAstToTs(valNode);
|
|
2724
|
+
const isOpt = isOptionalChain(valNode);
|
|
2725
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2726
|
+
}
|
|
2727
|
+
return `{ ${lines.join("; ")} }`;
|
|
2728
|
+
}
|
|
2729
|
+
case "union": {
|
|
2730
|
+
const arrArg = args[0];
|
|
2731
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2732
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2733
|
+
}
|
|
2734
|
+
case "record": {
|
|
2735
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2736
|
+
if (!valArg) return "unknown";
|
|
2737
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2738
|
+
}
|
|
2739
|
+
case "tuple": {
|
|
2740
|
+
const arrArg = args[0];
|
|
2741
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2742
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2743
|
+
}
|
|
2744
|
+
default:
|
|
2745
|
+
return "unknown";
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
return "unknown";
|
|
2749
|
+
}
|
|
2750
|
+
function isOptionalChain(node) {
|
|
2751
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
2752
|
+
const expr = node.getExpression();
|
|
2753
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2754
|
+
}
|
|
2755
|
+
function memberType(val, position, project) {
|
|
2756
|
+
if (project) {
|
|
2757
|
+
const inferred = inferSchema(val, project);
|
|
2758
|
+
if (inferred.kind === "schema") {
|
|
2759
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return zodAstToTs(val);
|
|
2763
|
+
}
|
|
2764
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2765
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
2766
|
+
const callee = callExpr.getExpression();
|
|
2767
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2768
|
+
if (calleeName !== "defineContract") return null;
|
|
2769
|
+
const args = callExpr.getArguments();
|
|
2770
|
+
const optsArg = args[0];
|
|
2771
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
2772
|
+
let query = null;
|
|
2773
|
+
let body = null;
|
|
2774
|
+
let response = "unknown";
|
|
2775
|
+
let error = null;
|
|
2776
|
+
let bodyZodText = null;
|
|
2777
|
+
let queryZodText = null;
|
|
2778
|
+
for (const prop of optsArg.getProperties()) {
|
|
2779
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
2780
|
+
const propName = prop.getName();
|
|
2781
|
+
const val = prop.getInitializer();
|
|
2782
|
+
if (!val) continue;
|
|
2783
|
+
if (propName === "query") {
|
|
2784
|
+
query = memberType(val, "request", project);
|
|
2785
|
+
queryZodText = val.getText();
|
|
2786
|
+
} else if (propName === "body") {
|
|
2787
|
+
body = memberType(val, "request", project);
|
|
2788
|
+
bodyZodText = val.getText();
|
|
2789
|
+
} else if (propName === "response") {
|
|
2790
|
+
response = memberType(val, "response", project);
|
|
2791
|
+
} else if (propName === "error") {
|
|
2792
|
+
error = memberType(val, "response", project);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
// src/discovery/pipe-schema.ts
|
|
2799
|
+
function pipeSchemaOf(decorator, project) {
|
|
2800
|
+
for (const pipe of decorator.getArguments()) {
|
|
2801
|
+
if (!Node9.isNewExpression(pipe) && !Node9.isCallExpression(pipe)) continue;
|
|
2802
|
+
for (const arg of pipe.getArguments()) {
|
|
2803
|
+
if (!couldBeSchema(arg)) continue;
|
|
2804
|
+
const inferred = inferSchema(arg, project);
|
|
2805
|
+
if (inferred.kind === "schema") {
|
|
2806
|
+
return {
|
|
2807
|
+
type: requestTypeOf(inferred.types),
|
|
2808
|
+
optional: acceptsUndefined(inferred.types),
|
|
2809
|
+
fields: requestFieldsOf(inferred.types)
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
if (inferred.kind === "unresolved") {
|
|
2813
|
+
const fallback = syntacticZodType(arg, project);
|
|
2814
|
+
if (fallback) {
|
|
2815
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
return null;
|
|
2821
|
+
}
|
|
2822
|
+
function couldBeSchema(arg) {
|
|
2823
|
+
return Node9.isIdentifier(arg) || Node9.isPropertyAccessExpression(arg) || Node9.isCallExpression(arg) || Node9.isAsExpression(arg) || Node9.isSatisfiesExpression(arg) || Node9.isParenthesizedExpression(arg);
|
|
2824
|
+
}
|
|
2825
|
+
function syntacticZodType(arg, project) {
|
|
2826
|
+
let expr = arg;
|
|
2827
|
+
if (Node9.isIdentifier(arg)) {
|
|
2828
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2829
|
+
expr = resolved?.decl.getInitializer();
|
|
2830
|
+
}
|
|
2831
|
+
if (!expr || !Node9.isCallExpression(expr)) return null;
|
|
2832
|
+
const type = zodAstToTs(expr);
|
|
2833
|
+
return type === "unknown" ? null : type;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2079
2836
|
// src/discovery/dto-type-resolver.ts
|
|
2080
2837
|
var WRAPPER_TYPES = {
|
|
2081
2838
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2107,22 +2864,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2107
2864
|
]);
|
|
2108
2865
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2109
2866
|
if (depth <= 0) return "unknown";
|
|
2110
|
-
if (
|
|
2867
|
+
if (Node10.isArrayTypeNode(typeNode)) {
|
|
2111
2868
|
const elementType = typeNode.getElementTypeNode();
|
|
2112
2869
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2113
2870
|
}
|
|
2114
|
-
if (
|
|
2871
|
+
if (Node10.isUnionTypeNode(typeNode)) {
|
|
2115
2872
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2116
2873
|
}
|
|
2117
|
-
if (
|
|
2874
|
+
if (Node10.isIntersectionTypeNode(typeNode)) {
|
|
2118
2875
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2119
2876
|
}
|
|
2120
|
-
if (
|
|
2877
|
+
if (Node10.isParenthesizedTypeNode(typeNode)) {
|
|
2121
2878
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2122
2879
|
}
|
|
2123
|
-
if (
|
|
2880
|
+
if (Node10.isTypeReference(typeNode)) {
|
|
2124
2881
|
const typeName = typeNode.getTypeName();
|
|
2125
|
-
const name =
|
|
2882
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2126
2883
|
const bound = subst.get(name);
|
|
2127
2884
|
if (bound !== void 0) return bound;
|
|
2128
2885
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2145,13 +2902,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2145
2902
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2146
2903
|
return "unknown";
|
|
2147
2904
|
}
|
|
2148
|
-
if (
|
|
2905
|
+
if (Node10.isTypeLiteral(typeNode)) {
|
|
2149
2906
|
const members = [];
|
|
2150
2907
|
for (const member of typeNode.getMembers()) {
|
|
2151
|
-
if (
|
|
2908
|
+
if (Node10.isPropertySignature(member)) {
|
|
2152
2909
|
const memberTypeNode = member.getTypeNode();
|
|
2153
|
-
const
|
|
2154
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2910
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2911
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2155
2912
|
} else {
|
|
2156
2913
|
members.push(member.getText());
|
|
2157
2914
|
}
|
|
@@ -2159,11 +2916,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2159
2916
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2160
2917
|
}
|
|
2161
2918
|
const kind = typeNode.getKind();
|
|
2162
|
-
if (kind ===
|
|
2163
|
-
if (kind ===
|
|
2164
|
-
if (kind ===
|
|
2165
|
-
if (kind ===
|
|
2166
|
-
if (kind ===
|
|
2919
|
+
if (kind === SyntaxKind4.StringKeyword) return "string";
|
|
2920
|
+
if (kind === SyntaxKind4.NumberKeyword) return "number";
|
|
2921
|
+
if (kind === SyntaxKind4.BooleanKeyword) return "boolean";
|
|
2922
|
+
if (kind === SyntaxKind4.UnknownKeyword) return "unknown";
|
|
2923
|
+
if (kind === SyntaxKind4.AnyKeyword) return "unknown";
|
|
2167
2924
|
return typeNode.getText();
|
|
2168
2925
|
}
|
|
2169
2926
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2211,20 +2968,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2211
2968
|
const propName = prop.getName();
|
|
2212
2969
|
const isOptional = prop.hasQuestionToken();
|
|
2213
2970
|
const propTypeNode = prop.getTypeNode();
|
|
2214
|
-
let
|
|
2971
|
+
let propType2 = "unknown";
|
|
2215
2972
|
if (propTypeNode) {
|
|
2216
|
-
|
|
2973
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2217
2974
|
}
|
|
2218
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
2975
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2219
2976
|
}
|
|
2220
2977
|
return `{ ${lines.join("; ")} }`;
|
|
2221
2978
|
}
|
|
2979
|
+
function namedField(decorator) {
|
|
2980
|
+
const first = decorator.getArguments()[0];
|
|
2981
|
+
return first && Node10.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
2982
|
+
}
|
|
2222
2983
|
function extractBodyType(method, sourceFile, project) {
|
|
2223
2984
|
for (const param of method.getParameters()) {
|
|
2224
2985
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2225
|
-
if (!bodyDecorator) continue;
|
|
2226
|
-
const
|
|
2227
|
-
if (
|
|
2986
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
2987
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
2988
|
+
if (piped) return piped.type;
|
|
2228
2989
|
const typeNode = param.getTypeNode();
|
|
2229
2990
|
if (typeNode) {
|
|
2230
2991
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2232,11 +2993,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2232
2993
|
}
|
|
2233
2994
|
return null;
|
|
2234
2995
|
}
|
|
2996
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
2997
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
2998
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
2999
|
+
}
|
|
2235
3000
|
function extractQueryType(method, sourceFile, project) {
|
|
2236
3001
|
for (const param of method.getParameters()) {
|
|
2237
3002
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2238
|
-
if (!queryDecorator) continue;
|
|
2239
|
-
|
|
3003
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
3004
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3005
|
+
if (piped) return piped.type;
|
|
2240
3006
|
const typeNode = param.getTypeNode();
|
|
2241
3007
|
if (typeNode) {
|
|
2242
3008
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2246,10 +3012,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2246
3012
|
for (const param of method.getParameters()) {
|
|
2247
3013
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2248
3014
|
if (!queryDecorator) continue;
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
3015
|
+
const queryName = namedField(queryDecorator);
|
|
3016
|
+
if (queryName === null) continue;
|
|
3017
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3018
|
+
if (piped) {
|
|
3019
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
2253
3022
|
const typeNode = param.getTypeNode();
|
|
2254
3023
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2255
3024
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2259,8 +3028,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2259
3028
|
function isParamOptional(param) {
|
|
2260
3029
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2261
3030
|
const typeNode = param.getTypeNode();
|
|
2262
|
-
if (typeNode &&
|
|
2263
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
3031
|
+
if (typeNode && Node10.isUnionTypeNode(typeNode)) {
|
|
3032
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind4.UndefinedKeyword);
|
|
2264
3033
|
}
|
|
2265
3034
|
return false;
|
|
2266
3035
|
}
|
|
@@ -2272,7 +3041,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2272
3041
|
const paramArgs = paramDecorator.getArguments();
|
|
2273
3042
|
if (paramArgs.length === 0) continue;
|
|
2274
3043
|
const nameArg = paramArgs[0];
|
|
2275
|
-
if (!
|
|
3044
|
+
if (!Node10.isStringLiteral(nameArg)) continue;
|
|
2276
3045
|
const paramName = nameArg.getLiteralValue();
|
|
2277
3046
|
const typeNode = param.getTypeNode();
|
|
2278
3047
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2280,6 +3049,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2280
3049
|
}
|
|
2281
3050
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2282
3051
|
}
|
|
3052
|
+
function extractPipedParamTypes(method, project) {
|
|
3053
|
+
const types = {};
|
|
3054
|
+
for (const param of method.getParameters()) {
|
|
3055
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
3056
|
+
if (!paramDecorator) continue;
|
|
3057
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
3058
|
+
if (!piped) continue;
|
|
3059
|
+
const name = namedField(paramDecorator);
|
|
3060
|
+
if (name !== null) {
|
|
3061
|
+
types[name] = piped.type;
|
|
3062
|
+
} else {
|
|
3063
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
3067
|
+
}
|
|
2283
3068
|
function extractUploadedFiles(method) {
|
|
2284
3069
|
const FILE = "File | Blob";
|
|
2285
3070
|
const entries = [];
|
|
@@ -2293,28 +3078,28 @@ function extractUploadedFiles(method) {
|
|
|
2293
3078
|
for (const decorator of method.getDecorators()) {
|
|
2294
3079
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2295
3080
|
for (const arg of decorator.getArguments()) {
|
|
2296
|
-
if (!
|
|
3081
|
+
if (!Node10.isCallExpression(arg)) continue;
|
|
2297
3082
|
const interceptor = arg.getExpression().getText();
|
|
2298
3083
|
const callArgs = arg.getArguments();
|
|
2299
3084
|
const firstArg2 = callArgs[0];
|
|
2300
3085
|
if (interceptor === "FileInterceptor") {
|
|
2301
|
-
if (firstArg2 &&
|
|
3086
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2302
3087
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2303
3088
|
multipart = true;
|
|
2304
3089
|
}
|
|
2305
3090
|
} else if (interceptor === "FilesInterceptor") {
|
|
2306
|
-
if (firstArg2 &&
|
|
3091
|
+
if (firstArg2 && Node10.isStringLiteral(firstArg2)) {
|
|
2307
3092
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2308
3093
|
multipart = true;
|
|
2309
3094
|
}
|
|
2310
3095
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2311
|
-
if (firstArg2 &&
|
|
3096
|
+
if (firstArg2 && Node10.isArrayLiteralExpression(firstArg2)) {
|
|
2312
3097
|
for (const el of firstArg2.getElements()) {
|
|
2313
|
-
if (!
|
|
3098
|
+
if (!Node10.isObjectLiteralExpression(el)) continue;
|
|
2314
3099
|
const nameProp = el.getProperty("name");
|
|
2315
|
-
if (nameProp &&
|
|
3100
|
+
if (nameProp && Node10.isPropertyAssignment(nameProp)) {
|
|
2316
3101
|
const init = nameProp.getInitializer();
|
|
2317
|
-
if (init &&
|
|
3102
|
+
if (init && Node10.isStringLiteral(init)) {
|
|
2318
3103
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2319
3104
|
}
|
|
2320
3105
|
}
|
|
@@ -2334,13 +3119,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2334
3119
|
if (apiResponseDecorator) {
|
|
2335
3120
|
const args = apiResponseDecorator.getArguments();
|
|
2336
3121
|
const optsArg = args[0];
|
|
2337
|
-
if (optsArg &&
|
|
3122
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2338
3123
|
for (const prop of optsArg.getProperties()) {
|
|
2339
|
-
if (!
|
|
3124
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2340
3125
|
if (prop.getName() !== "type") continue;
|
|
2341
3126
|
const val = prop.getInitializer();
|
|
2342
3127
|
if (!val) continue;
|
|
2343
|
-
if (
|
|
3128
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2344
3129
|
const elements = val.getElements();
|
|
2345
3130
|
const firstEl = elements[0];
|
|
2346
3131
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2361,24 +3146,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2361
3146
|
}
|
|
2362
3147
|
function apiResponseStatus(decorator) {
|
|
2363
3148
|
const optsArg = decorator.getArguments()[0];
|
|
2364
|
-
if (!optsArg || !
|
|
3149
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2365
3150
|
for (const prop of optsArg.getProperties()) {
|
|
2366
|
-
if (!
|
|
3151
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2367
3152
|
if (prop.getName() !== "status") continue;
|
|
2368
3153
|
const val = prop.getInitializer();
|
|
2369
|
-
if (val &&
|
|
3154
|
+
if (val && Node10.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2370
3155
|
}
|
|
2371
3156
|
return null;
|
|
2372
3157
|
}
|
|
2373
3158
|
function apiResponseTypeNode(decorator) {
|
|
2374
3159
|
const optsArg = decorator.getArguments()[0];
|
|
2375
|
-
if (!optsArg || !
|
|
3160
|
+
if (!optsArg || !Node10.isObjectLiteralExpression(optsArg)) return null;
|
|
2376
3161
|
for (const prop of optsArg.getProperties()) {
|
|
2377
|
-
if (!
|
|
3162
|
+
if (!Node10.isPropertyAssignment(prop)) continue;
|
|
2378
3163
|
if (prop.getName() !== "type") continue;
|
|
2379
3164
|
const val = prop.getInitializer();
|
|
2380
3165
|
if (!val) return null;
|
|
2381
|
-
if (
|
|
3166
|
+
if (Node10.isArrayLiteralExpression(val)) {
|
|
2382
3167
|
const first = val.getElements()[0];
|
|
2383
3168
|
return first ? { node: first, isArray: true } : null;
|
|
2384
3169
|
}
|
|
@@ -2396,7 +3181,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2396
3181
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2397
3182
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2398
3183
|
let ref = null;
|
|
2399
|
-
if (
|
|
3184
|
+
if (Node10.isIdentifier(typeInfo.node)) {
|
|
2400
3185
|
const name = typeInfo.node.getText();
|
|
2401
3186
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2402
3187
|
if (localDecl?.isExported()) {
|
|
@@ -2413,7 +3198,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2413
3198
|
return null;
|
|
2414
3199
|
}
|
|
2415
3200
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2416
|
-
if (!
|
|
3201
|
+
if (!Node10.isIdentifier(node)) return "unknown";
|
|
2417
3202
|
const name = node.getText();
|
|
2418
3203
|
const resolved = findType(name, sourceFile, project);
|
|
2419
3204
|
if (resolved) {
|
|
@@ -2433,9 +3218,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2433
3218
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2434
3219
|
function detectBinaryResponse(method) {
|
|
2435
3220
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2436
|
-
if (!node || !
|
|
3221
|
+
if (!node || !Node10.isTypeReference(node)) return false;
|
|
2437
3222
|
const typeName = node.getTypeName();
|
|
2438
|
-
const name =
|
|
3223
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2439
3224
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2440
3225
|
}
|
|
2441
3226
|
function detectStreamElement(method) {
|
|
@@ -2450,9 +3235,9 @@ function detectStreamElement(method) {
|
|
|
2450
3235
|
return null;
|
|
2451
3236
|
}
|
|
2452
3237
|
function streamContainerElement(node) {
|
|
2453
|
-
if (!node || !
|
|
3238
|
+
if (!node || !Node10.isTypeReference(node)) return null;
|
|
2454
3239
|
const typeName = node.getTypeName();
|
|
2455
|
-
const name =
|
|
3240
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2456
3241
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2457
3242
|
return node.getTypeArguments()[0] ?? null;
|
|
2458
3243
|
}
|
|
@@ -2462,9 +3247,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2462
3247
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2463
3248
|
}
|
|
2464
3249
|
function unwrapNamedContainer(node, names) {
|
|
2465
|
-
if (!node || !
|
|
3250
|
+
if (!node || !Node10.isTypeReference(node)) return node;
|
|
2466
3251
|
const typeName = node.getTypeName();
|
|
2467
|
-
const name =
|
|
3252
|
+
const name = Node10.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2468
3253
|
if (names.has(name)) {
|
|
2469
3254
|
return node.getTypeArguments()[0] ?? node;
|
|
2470
3255
|
}
|
|
@@ -2485,19 +3270,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2485
3270
|
body = body ?? bodyType;
|
|
2486
3271
|
}
|
|
2487
3272
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3273
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2488
3274
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2489
3275
|
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) {
|
|
3276
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2491
3277
|
return null;
|
|
2492
3278
|
}
|
|
2493
3279
|
let bodyRef = null;
|
|
2494
3280
|
let queryRef = null;
|
|
2495
3281
|
let responseRef = null;
|
|
2496
3282
|
for (const param of method.getParameters()) {
|
|
2497
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3283
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2498
3284
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2499
3285
|
}
|
|
2500
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3286
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2501
3287
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2502
3288
|
}
|
|
2503
3289
|
}
|
|
@@ -2510,11 +3296,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2510
3296
|
if (apiResp) {
|
|
2511
3297
|
const args = apiResp.getArguments();
|
|
2512
3298
|
const optsArg = args[0];
|
|
2513
|
-
if (optsArg &&
|
|
3299
|
+
if (optsArg && Node10.isObjectLiteralExpression(optsArg)) {
|
|
2514
3300
|
for (const prop of optsArg.getProperties()) {
|
|
2515
|
-
if (
|
|
3301
|
+
if (Node10.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2516
3302
|
const val = prop.getInitializer();
|
|
2517
|
-
if (val &&
|
|
3303
|
+
if (val && Node10.isIdentifier(val)) {
|
|
2518
3304
|
const name = val.getText();
|
|
2519
3305
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2520
3306
|
if (localDecl?.isExported()) {
|
|
@@ -2550,6 +3336,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2550
3336
|
response,
|
|
2551
3337
|
error: errorInfo?.type ?? null,
|
|
2552
3338
|
params: paramsType,
|
|
3339
|
+
paramTypes,
|
|
2553
3340
|
queryRef,
|
|
2554
3341
|
bodyRef,
|
|
2555
3342
|
responseRef,
|
|
@@ -2570,6 +3357,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2570
3357
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2571
3358
|
for (const param of method.getParameters()) {
|
|
2572
3359
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3360
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2573
3361
|
const typeNode = param.getTypeNode();
|
|
2574
3362
|
if (!typeNode) continue;
|
|
2575
3363
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2582,129 +3370,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2582
3370
|
return null;
|
|
2583
3371
|
}
|
|
2584
3372
|
|
|
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
3373
|
// src/discovery/contracts-fast.ts
|
|
2709
3374
|
async function discoverContractsFast(opts) {
|
|
2710
3375
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2717,7 +3382,9 @@ async function discoverContractsFast(opts) {
|
|
|
2717
3382
|
}
|
|
2718
3383
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2719
3384
|
clearMixinTypeProject();
|
|
2720
|
-
|
|
3385
|
+
const routes = extractAllRoutes(project);
|
|
3386
|
+
clearSchemaTypeProject(project);
|
|
3387
|
+
return routes;
|
|
2721
3388
|
}
|
|
2722
3389
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2723
3390
|
return tsconfig ? resolve3(tsconfig) : join4(cwd, "tsconfig.json");
|
|
@@ -2729,25 +3396,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2729
3396
|
};
|
|
2730
3397
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2731
3398
|
if (!existsSync(tsconfigPath)) return { options: null, files: [] };
|
|
2732
|
-
const read =
|
|
3399
|
+
const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
2733
3400
|
if (read.error) {
|
|
2734
3401
|
return {
|
|
2735
3402
|
options: null,
|
|
2736
3403
|
files: [tsconfigPath],
|
|
2737
|
-
error:
|
|
3404
|
+
error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2738
3405
|
};
|
|
2739
3406
|
}
|
|
2740
3407
|
const host = {
|
|
2741
|
-
useCaseSensitiveFileNames:
|
|
3408
|
+
useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
|
|
2742
3409
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2743
3410
|
readDirectory: () => [],
|
|
2744
|
-
fileExists: (path) =>
|
|
3411
|
+
fileExists: (path) => ts3.sys.fileExists(path),
|
|
2745
3412
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2746
3413
|
// in a base tsconfig keeps working.
|
|
2747
|
-
readFile: (path) =>
|
|
3414
|
+
readFile: (path) => ts3.sys.readFile(path)
|
|
2748
3415
|
};
|
|
2749
|
-
const sourceFile =
|
|
2750
|
-
const parsed =
|
|
3416
|
+
const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
|
|
3417
|
+
const parsed = ts3.parseJsonSourceFileConfigFileContent(
|
|
2751
3418
|
sourceFile,
|
|
2752
3419
|
host,
|
|
2753
3420
|
dirname2(tsconfigPath),
|
|
@@ -2770,7 +3437,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2770
3437
|
`[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
3438
|
);
|
|
2772
3439
|
}
|
|
2773
|
-
return new
|
|
3440
|
+
return new Project4({
|
|
2774
3441
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2775
3442
|
skipAddingFilesFromTsConfig: true,
|
|
2776
3443
|
skipLoadingLibFiles: true,
|
|
@@ -2882,15 +3549,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2882
3549
|
clearTypeResolutionCaches(this.project);
|
|
2883
3550
|
clearEnumCache(this.project);
|
|
2884
3551
|
clearMixinTypeProject();
|
|
3552
|
+
clearSchemaTypeProject(this.project);
|
|
2885
3553
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2886
3554
|
}
|
|
2887
3555
|
};
|
|
2888
3556
|
function decoratorStringArg(decoratorExpr) {
|
|
2889
3557
|
if (!decoratorExpr) return void 0;
|
|
2890
|
-
if (
|
|
2891
|
-
if (
|
|
3558
|
+
if (Node11.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3559
|
+
if (Node11.isArrayLiteralExpression(decoratorExpr)) {
|
|
2892
3560
|
const first = decoratorExpr.getElements()[0];
|
|
2893
|
-
if (first &&
|
|
3561
|
+
if (first && Node11.isStringLiteral(first)) return first.getLiteralValue();
|
|
2894
3562
|
}
|
|
2895
3563
|
return void 0;
|
|
2896
3564
|
}
|
|
@@ -3011,9 +3679,9 @@ function extractContractRoute(args) {
|
|
|
3011
3679
|
let contractDef = null;
|
|
3012
3680
|
let bodyZodRef = null;
|
|
3013
3681
|
let queryZodRef = null;
|
|
3014
|
-
if (
|
|
3015
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3016
|
-
} else if (
|
|
3682
|
+
if (Node11.isCallExpression(firstDecoratorArg)) {
|
|
3683
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3684
|
+
} else if (Node11.isIdentifier(firstDecoratorArg)) {
|
|
3017
3685
|
const identName = firstDecoratorArg.getText();
|
|
3018
3686
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3019
3687
|
if (!resolvedVar) {
|
|
@@ -3025,7 +3693,7 @@ function extractContractRoute(args) {
|
|
|
3025
3693
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3026
3694
|
const initializer = varDecl.getInitializer();
|
|
3027
3695
|
if (!initializer) return null;
|
|
3028
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3696
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3029
3697
|
if (contractDef && varDecl.isExported()) {
|
|
3030
3698
|
const filePath = declFile.getFilePath();
|
|
3031
3699
|
if (contractDef.body !== null) {
|
|
@@ -3096,6 +3764,7 @@ function extractDtoRoute(args) {
|
|
|
3096
3764
|
body: dtoContract?.body ?? null,
|
|
3097
3765
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3098
3766
|
error: dtoContract?.error ?? null,
|
|
3767
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3099
3768
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3100
3769
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3101
3770
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3218,7 +3887,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3218
3887
|
},
|
|
3219
3888
|
project() {
|
|
3220
3889
|
if (!project) {
|
|
3221
|
-
project = new
|
|
3890
|
+
project = new Project5({
|
|
3222
3891
|
skipAddingFilesFromTsConfig: true,
|
|
3223
3892
|
skipLoadingLibFiles: true,
|
|
3224
3893
|
skipFileDependencyResolution: true,
|
|
@@ -3336,10 +4005,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3336
4005
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3337
4006
|
}
|
|
3338
4007
|
}
|
|
3339
|
-
function buildParamsType(params) {
|
|
4008
|
+
function buildParamsType(params, paramTypes) {
|
|
3340
4009
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3341
4010
|
if (pathParams.length === 0) return "never";
|
|
3342
|
-
|
|
4011
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
4012
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3343
4013
|
}
|
|
3344
4014
|
function hasPathParams(params) {
|
|
3345
4015
|
return params.some((p) => p.source === "path");
|
|
@@ -3433,7 +4103,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3433
4103
|
}
|
|
3434
4104
|
const response = buildResponseType(c, outDir, serialization);
|
|
3435
4105
|
const error = buildErrorType(c);
|
|
3436
|
-
const params = buildParamsType(c.params);
|
|
4106
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3437
4107
|
const safeMethod = JSON.stringify(method);
|
|
3438
4108
|
const safeUrl = JSON.stringify(c.path);
|
|
3439
4109
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3521,28 +4191,28 @@ function renderFetcherRequest(req, binaryResponse) {
|
|
|
3521
4191
|
}
|
|
3522
4192
|
function emitReqHelper() {
|
|
3523
4193
|
return [
|
|
3524
|
-
"/**
|
|
3525
|
-
"type
|
|
3526
|
-
"
|
|
4194
|
+
"/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
|
|
4195
|
+
"export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
|
|
4196
|
+
"class __Request<R> extends Promise<R> {",
|
|
4197
|
+
" static override get [Symbol.species](): PromiseConstructor {",
|
|
4198
|
+
" return Promise;",
|
|
4199
|
+
" }",
|
|
4200
|
+
" private __run: () => Promise<R>;",
|
|
4201
|
+
" private __p: Promise<R> | undefined;",
|
|
4202
|
+
" constructor(run: () => Promise<R>) {",
|
|
4203
|
+
" super(() => {});",
|
|
4204
|
+
" this.__run = run;",
|
|
4205
|
+
" }",
|
|
4206
|
+
" override then<T1 = R, T2 = never>(",
|
|
3527
4207
|
" onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
|
|
3528
4208
|
" onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
|
|
3529
|
-
" ): Promise<T1 | T2
|
|
3530
|
-
"
|
|
3531
|
-
"
|
|
3532
|
-
"
|
|
3533
|
-
"}
|
|
3534
|
-
"function __req<R>(run: () => Promise<R
|
|
3535
|
-
"
|
|
3536
|
-
" const __promise = () => {",
|
|
3537
|
-
" __p ??= run();",
|
|
3538
|
-
" return __p;",
|
|
3539
|
-
" };",
|
|
3540
|
-
" return {",
|
|
3541
|
-
" then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
|
|
3542
|
-
" catch: (onrejected) => __promise().catch(onrejected),",
|
|
3543
|
-
" finally: (onfinally) => __promise().finally(onfinally),",
|
|
3544
|
-
" fetch: run,",
|
|
3545
|
-
" };",
|
|
4209
|
+
" ): Promise<T1 | T2> {",
|
|
4210
|
+
" this.__p ??= this.__run();",
|
|
4211
|
+
" return this.__p.then(onfulfilled, onrejected);",
|
|
4212
|
+
" }",
|
|
4213
|
+
"}",
|
|
4214
|
+
"function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
|
|
4215
|
+
" return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
|
|
3546
4216
|
"}",
|
|
3547
4217
|
""
|
|
3548
4218
|
];
|
|
@@ -3560,18 +4230,26 @@ function emitFilterFieldGuard() {
|
|
|
3560
4230
|
];
|
|
3561
4231
|
}
|
|
3562
4232
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
3563
|
-
const
|
|
3564
|
-
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4233
|
+
const memberLines = [];
|
|
3565
4234
|
if (req.filterFieldsExpr) {
|
|
3566
|
-
|
|
4235
|
+
memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
3567
4236
|
}
|
|
3568
4237
|
if (streamExpr) {
|
|
3569
|
-
|
|
4238
|
+
memberLines.push(`${pad} stream: () => ${streamExpr},`);
|
|
3570
4239
|
}
|
|
3571
4240
|
for (const [name, value] of Object.entries(members)) {
|
|
3572
|
-
|
|
3573
|
-
}
|
|
3574
|
-
|
|
4241
|
+
memberLines.push(`${pad} ${name}: ${value},`);
|
|
4242
|
+
}
|
|
4243
|
+
const body = [requestExpr, ...memberLines].join("\n");
|
|
4244
|
+
const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
|
|
4245
|
+
const lines = [`${pad}${objKey}: (${param}) =>`];
|
|
4246
|
+
if (memberLines.length === 0) {
|
|
4247
|
+
lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4248
|
+
return lines;
|
|
4249
|
+
}
|
|
4250
|
+
lines.push(`${pad} __req(() => ${requestExpr}, {`);
|
|
4251
|
+
lines.push(...memberLines);
|
|
4252
|
+
lines.push(`${pad} }),`);
|
|
3575
4253
|
return lines;
|
|
3576
4254
|
}
|
|
3577
4255
|
function renderStreamExpr(req) {
|
|
@@ -3702,6 +4380,26 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3702
4380
|
"}",
|
|
3703
4381
|
""
|
|
3704
4382
|
];
|
|
4383
|
+
function isClientCallable(route) {
|
|
4384
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4385
|
+
}
|
|
4386
|
+
var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
|
|
4387
|
+
var ROUTES_EXPORTS = [
|
|
4388
|
+
// Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
|
|
4389
|
+
{ name: "route", typeOnly: false },
|
|
4390
|
+
{ name: "ROUTES", typeOnly: false },
|
|
4391
|
+
{ name: "RouteName", typeOnly: true },
|
|
4392
|
+
{ name: "ExtractParams", typeOnly: true },
|
|
4393
|
+
{ name: "RouteParams", typeOnly: true }
|
|
4394
|
+
];
|
|
4395
|
+
function finishRoutesImport(lines) {
|
|
4396
|
+
const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
|
|
4397
|
+
const used = ROUTES_EXPORTS.filter(
|
|
4398
|
+
({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
|
|
4399
|
+
).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
|
|
4400
|
+
const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
|
|
4401
|
+
return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
|
|
4402
|
+
}
|
|
3705
4403
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3706
4404
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3707
4405
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3709,7 +4407,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3709
4407
|
const { layer } = resolveApiSlots(extensions);
|
|
3710
4408
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3711
4409
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3712
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4410
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3713
4411
|
const ctx = opts.ctx ?? {
|
|
3714
4412
|
cwd: outDir ?? "",
|
|
3715
4413
|
outDir: outDir ?? "",
|
|
@@ -3761,9 +4459,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3761
4459
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
3762
4460
|
}
|
|
3763
4461
|
lines.push(...extImports);
|
|
3764
|
-
lines.push(
|
|
3765
|
-
"import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
|
|
3766
|
-
);
|
|
4462
|
+
lines.push(ROUTES_IMPORT_PLACEHOLDER);
|
|
3767
4463
|
const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
|
|
3768
4464
|
lines.push(`import type { Fetcher } from '${runtimeImport}';`);
|
|
3769
4465
|
if (serialization === "json" && contracted.length > 0) {
|
|
@@ -3814,7 +4510,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3814
4510
|
lines.push(...statements, "");
|
|
3815
4511
|
}
|
|
3816
4512
|
}
|
|
3817
|
-
return lines
|
|
4513
|
+
return finishRoutesImport(lines);
|
|
3818
4514
|
}
|
|
3819
4515
|
const tree = /* @__PURE__ */ new Map();
|
|
3820
4516
|
for (const r of contracted) {
|
|
@@ -3867,7 +4563,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3867
4563
|
lines.push(...statements, "");
|
|
3868
4564
|
}
|
|
3869
4565
|
}
|
|
3870
|
-
return lines
|
|
4566
|
+
return finishRoutesImport(lines);
|
|
3871
4567
|
}
|
|
3872
4568
|
|
|
3873
4569
|
// src/emit/emit-cache.ts
|
|
@@ -3880,6 +4576,7 @@ async function emitCache(pages, outDir) {
|
|
|
3880
4576
|
const s = await stat(p.absolutePath);
|
|
3881
4577
|
return {
|
|
3882
4578
|
name: p.name,
|
|
4579
|
+
scope: p.scope ?? "default",
|
|
3883
4580
|
relativePath: p.relativePath,
|
|
3884
4581
|
mtime: s.mtime.toISOString()
|
|
3885
4582
|
};
|
|
@@ -4630,6 +5327,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4630
5327
|
const paths = {};
|
|
4631
5328
|
for (const route of routes) {
|
|
4632
5329
|
if (!route.contract) continue;
|
|
5330
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4633
5331
|
const oaPath = toOpenApiPath(route.path);
|
|
4634
5332
|
const method = route.method.toLowerCase();
|
|
4635
5333
|
let pathItem = paths[oaPath];
|
|
@@ -4673,10 +5371,11 @@ async function emitPages(pages, outDir, _options = {}) {
|
|
|
4673
5371
|
}).join("\n");
|
|
4674
5372
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
4675
5373
|
const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
|
|
5374
|
+
const scopesBlock = buildScopesBlock(pages);
|
|
4676
5375
|
const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
|
|
4677
5376
|
|
|
4678
5377
|
export type InertiaPageName = ${pageNameUnion};
|
|
4679
|
-
${propsHelper}
|
|
5378
|
+
${propsHelper}${scopesBlock}
|
|
4680
5379
|
declare module '@dudousxd/nestjs-inertia' {
|
|
4681
5380
|
interface InertiaPages {
|
|
4682
5381
|
${augBody}
|
|
@@ -4685,6 +5384,31 @@ ${sharedPropsBlock}}
|
|
|
4685
5384
|
`;
|
|
4686
5385
|
await writeFile7(join11(outDir, "pages.d.ts"), content, "utf8");
|
|
4687
5386
|
}
|
|
5387
|
+
function buildScopesBlock(pages) {
|
|
5388
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
5389
|
+
for (const page of pages) {
|
|
5390
|
+
const scope = page.scope ?? "default";
|
|
5391
|
+
const names = byScope.get(scope) ?? [];
|
|
5392
|
+
names.push(JSON.stringify(page.name));
|
|
5393
|
+
byScope.set(scope, names);
|
|
5394
|
+
}
|
|
5395
|
+
if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
|
|
5396
|
+
if (!byScope.has("default")) byScope.set("default", []);
|
|
5397
|
+
const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
|
|
5398
|
+
const body = scopes.map((scope) => {
|
|
5399
|
+
const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
|
|
5400
|
+
const names = byScope.get(scope) ?? [];
|
|
5401
|
+
return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
|
|
5402
|
+
}).join("\n");
|
|
5403
|
+
return `
|
|
5404
|
+
/** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
|
|
5405
|
+
export interface InertiaScopePages {
|
|
5406
|
+
${body}
|
|
5407
|
+
}
|
|
5408
|
+
|
|
5409
|
+
export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
|
|
5410
|
+
`;
|
|
5411
|
+
}
|
|
4688
5412
|
function buildSharedPropsBlock(sharedProps) {
|
|
4689
5413
|
if (!sharedProps) return "";
|
|
4690
5414
|
if (sharedProps.isImportRef) {
|
|
@@ -4892,8 +5616,7 @@ function diffConfigKeyHashes(previous, current) {
|
|
|
4892
5616
|
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
4893
5617
|
}
|
|
4894
5618
|
async function discoverInputFiles(config) {
|
|
4895
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
4896
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
5619
|
+
const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
|
|
4897
5620
|
const cwd = config.codegen.cwd;
|
|
4898
5621
|
const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4899
5622
|
return [...new Set(matched)].sort();
|
|
@@ -5024,12 +5747,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
5024
5747
|
}
|
|
5025
5748
|
if (config.pages) {
|
|
5026
5749
|
const pagesConfig = config.pages;
|
|
5027
|
-
const pages = await
|
|
5028
|
-
glob: pagesConfig.glob,
|
|
5029
|
-
cwd: config.codegen.cwd,
|
|
5030
|
-
propsExport: pagesConfig.propsExport,
|
|
5031
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
5032
|
-
});
|
|
5750
|
+
const pages = await discoverPageScopes(config);
|
|
5033
5751
|
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
5034
5752
|
await emitPages(pages, config.codegen.outDir, {
|
|
5035
5753
|
propsExport: pagesConfig.propsExport,
|
|
@@ -5204,12 +5922,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
5204
5922
|
await runInitialPass();
|
|
5205
5923
|
}
|
|
5206
5924
|
let pagesDebounceTimer;
|
|
5207
|
-
const
|
|
5208
|
-
const pagesWatcher = chokidar.watch(
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5925
|
+
const pageGlobs = allPageGlobs(config);
|
|
5926
|
+
const pagesWatcher = chokidar.watch(
|
|
5927
|
+
(pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
|
|
5928
|
+
(glob) => join16(config.codegen.cwd, glob)
|
|
5929
|
+
),
|
|
5930
|
+
{
|
|
5931
|
+
ignoreInitial: true,
|
|
5932
|
+
persistent: true,
|
|
5933
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
5934
|
+
}
|
|
5935
|
+
);
|
|
5213
5936
|
function schedulePagesRegenerate() {
|
|
5214
5937
|
if (pagesDebounceTimer !== void 0) {
|
|
5215
5938
|
clearTimeout(pagesDebounceTimer);
|
|
@@ -5345,7 +6068,7 @@ function toObjectKey2(name) {
|
|
|
5345
6068
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
5346
6069
|
}
|
|
5347
6070
|
function createChainModuleRenderer(opts) {
|
|
5348
|
-
const { render, recursiveAnnotation } = opts;
|
|
6071
|
+
const { render: render2, recursiveAnnotation } = opts;
|
|
5349
6072
|
return (mod) => {
|
|
5350
6073
|
const ctx = { named: mod.named };
|
|
5351
6074
|
const recursive = mod.recursive ?? /* @__PURE__ */ new Set();
|
|
@@ -5354,7 +6077,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5354
6077
|
const namedTypeAliases = /* @__PURE__ */ new Map();
|
|
5355
6078
|
const namedAnnotations = /* @__PURE__ */ new Map();
|
|
5356
6079
|
for (const [name, node] of mod.named) {
|
|
5357
|
-
namedNestedSchemas.set(name,
|
|
6080
|
+
namedNestedSchemas.set(name, render2(node, ctx));
|
|
5358
6081
|
if (recursive.has(name)) {
|
|
5359
6082
|
const typeName = typeNameFor(name);
|
|
5360
6083
|
namedTypeAliases.set(name, `type ${typeName} = ${renderTsType(node, tctx)}`);
|
|
@@ -5362,7 +6085,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5362
6085
|
}
|
|
5363
6086
|
}
|
|
5364
6087
|
return {
|
|
5365
|
-
schemaText:
|
|
6088
|
+
schemaText: render2(mod.root, ctx),
|
|
5366
6089
|
namedNestedSchemas,
|
|
5367
6090
|
namedTypeAliases,
|
|
5368
6091
|
namedAnnotations,
|
|
@@ -5372,7 +6095,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5372
6095
|
}
|
|
5373
6096
|
|
|
5374
6097
|
// src/index.ts
|
|
5375
|
-
var VERSION = "0.
|
|
6098
|
+
var VERSION = "0.27.0";
|
|
5376
6099
|
export {
|
|
5377
6100
|
CodegenError,
|
|
5378
6101
|
ConfigError,
|