@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.cjs
CHANGED
|
@@ -149,11 +149,32 @@ function validateUserConfig(userConfig) {
|
|
|
149
149
|
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
|
-
if (userConfig.pages &&
|
|
152
|
+
if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
|
|
153
153
|
throw new ConfigError(
|
|
154
|
-
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
154
|
+
"Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
|
+
for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
|
|
158
|
+
if (scope === "default") {
|
|
159
|
+
throw new ConfigError(
|
|
160
|
+
'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (!isGlobInput(scopeConfig?.glob)) {
|
|
164
|
+
throw new ConfigError(
|
|
165
|
+
`Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
|
|
170
|
+
console.warn(
|
|
171
|
+
"[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)."
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function isGlobInput(value) {
|
|
176
|
+
if (typeof value === "string") return true;
|
|
177
|
+
return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
|
|
157
178
|
}
|
|
158
179
|
function applyDefaults(userConfig, cwd) {
|
|
159
180
|
validateUserConfig(userConfig);
|
|
@@ -181,7 +202,17 @@ function applyDefaults(userConfig, cwd) {
|
|
|
181
202
|
pages: userConfig.pages ? {
|
|
182
203
|
glob: userConfig.pages.glob,
|
|
183
204
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
184
|
-
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
|
|
205
|
+
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
|
|
206
|
+
scopes: Object.fromEntries(
|
|
207
|
+
Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
|
|
208
|
+
scope,
|
|
209
|
+
{
|
|
210
|
+
glob: scopeConfig.glob,
|
|
211
|
+
prefix: scopeConfig.prefix ?? `${scope}/`,
|
|
212
|
+
componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
|
|
213
|
+
}
|
|
214
|
+
])
|
|
215
|
+
)
|
|
185
216
|
} : null,
|
|
186
217
|
contracts: {
|
|
187
218
|
glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
|
|
@@ -258,23 +289,81 @@ var import_promises2 = require("fs/promises");
|
|
|
258
289
|
var import_node_path2 = require("path");
|
|
259
290
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
260
291
|
var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
|
|
292
|
+
var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
|
|
293
|
+
function globStaticBase(glob) {
|
|
294
|
+
const segments = glob.split("/");
|
|
295
|
+
const staticSegments = [];
|
|
296
|
+
for (const segment of segments.slice(0, -1)) {
|
|
297
|
+
if (MAGIC_SEGMENT_RE.test(segment)) break;
|
|
298
|
+
staticSegments.push(segment);
|
|
299
|
+
}
|
|
300
|
+
return staticSegments.join("/");
|
|
301
|
+
}
|
|
261
302
|
async function discoverPages(opts) {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStatic);
|
|
303
|
+
const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
|
|
304
|
+
const prefix = opts.prefix ?? "";
|
|
305
|
+
const scope = opts.scope ?? "default";
|
|
306
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
267
307
|
const out = [];
|
|
268
|
-
for (const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
308
|
+
for (const glob of globs) {
|
|
309
|
+
const files = (await (0, import_fast_glob.default)(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
|
|
310
|
+
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStaticBase(glob));
|
|
311
|
+
for (const file of files) {
|
|
312
|
+
seenFiles.add(file);
|
|
313
|
+
const rel = (0, import_node_path2.relative)(opts.cwd, file);
|
|
314
|
+
const nameRel = (0, import_node_path2.relative)(pagesBase, file).split(import_node_path2.sep).join("/");
|
|
315
|
+
const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
|
|
316
|
+
const source = await (0, import_promises2.readFile)(file, "utf8");
|
|
317
|
+
const propsSource = extractPropsSource(source, opts.propsExport);
|
|
318
|
+
out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
assertUniqueNames(out);
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
async function discoverPageScopes(config) {
|
|
325
|
+
const pages = config.pages;
|
|
326
|
+
if (!pages) return [];
|
|
327
|
+
const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
|
|
328
|
+
const out = await discoverPages({
|
|
329
|
+
...common,
|
|
330
|
+
glob: pages.glob,
|
|
331
|
+
componentNameStrategy: pages.componentNameStrategy
|
|
332
|
+
});
|
|
333
|
+
for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
|
|
334
|
+
out.push(
|
|
335
|
+
...await discoverPages({
|
|
336
|
+
...common,
|
|
337
|
+
glob: scopeConfig.glob,
|
|
338
|
+
componentNameStrategy: scopeConfig.componentNameStrategy,
|
|
339
|
+
prefix: scopeConfig.prefix,
|
|
340
|
+
scope
|
|
341
|
+
})
|
|
342
|
+
);
|
|
275
343
|
}
|
|
344
|
+
assertUniqueNames(out);
|
|
276
345
|
return out;
|
|
277
346
|
}
|
|
347
|
+
function allPageGlobs(config) {
|
|
348
|
+
if (!config.pages) return [];
|
|
349
|
+
const asList = (glob) => typeof glob === "string" ? [glob] : glob;
|
|
350
|
+
return [
|
|
351
|
+
...asList(config.pages.glob),
|
|
352
|
+
...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
|
|
353
|
+
];
|
|
354
|
+
}
|
|
355
|
+
function assertUniqueNames(pages) {
|
|
356
|
+
const byName = /* @__PURE__ */ new Map();
|
|
357
|
+
for (const page of pages) {
|
|
358
|
+
const previous = byName.get(page.name);
|
|
359
|
+
if (previous) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`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.`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
byName.set(page.name, page);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
278
367
|
function computeName(rel, strat) {
|
|
279
368
|
if (typeof strat === "function") return strat(rel);
|
|
280
369
|
const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
|
|
@@ -584,16 +673,16 @@ var import_node_path7 = require("path");
|
|
|
584
673
|
|
|
585
674
|
// src/extension/registry.ts
|
|
586
675
|
var import_node_path6 = require("path");
|
|
587
|
-
var
|
|
676
|
+
var import_ts_morph13 = require("ts-morph");
|
|
588
677
|
|
|
589
678
|
// src/discovery/contracts-fast.ts
|
|
590
679
|
var import_node_fs = require("fs");
|
|
591
680
|
var import_node_path5 = require("path");
|
|
592
681
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
593
|
-
var
|
|
682
|
+
var import_ts_morph12 = require("ts-morph");
|
|
594
683
|
|
|
595
684
|
// src/discovery/dto-type-resolver.ts
|
|
596
|
-
var
|
|
685
|
+
var import_ts_morph11 = require("ts-morph");
|
|
597
686
|
|
|
598
687
|
// src/discovery/dto-to-ir.ts
|
|
599
688
|
var import_ts_morph3 = require("ts-morph");
|
|
@@ -2121,6 +2210,672 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2121
2210
|
return fields;
|
|
2122
2211
|
}
|
|
2123
2212
|
|
|
2213
|
+
// src/discovery/pipe-schema.ts
|
|
2214
|
+
var import_ts_morph10 = require("ts-morph");
|
|
2215
|
+
|
|
2216
|
+
// src/discovery/schema-type-inference.ts
|
|
2217
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2218
|
+
|
|
2219
|
+
// src/discovery/schema-type-program.ts
|
|
2220
|
+
var import_ts_morph7 = require("ts-morph");
|
|
2221
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2222
|
+
var SchemaTypeProgram = class {
|
|
2223
|
+
constructor(discovery) {
|
|
2224
|
+
this.discovery = discovery;
|
|
2225
|
+
const base = discovery.getCompilerOptions();
|
|
2226
|
+
this.options = {
|
|
2227
|
+
...base,
|
|
2228
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2229
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2230
|
+
target: base.target ?? import_ts_morph7.ts.ScriptTarget.ES2022,
|
|
2231
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2232
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2233
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: import_ts_morph7.ts.ModuleKind.ESNext, moduleResolution: import_ts_morph7.ts.ModuleResolutionKind.Bundler } : {},
|
|
2234
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2235
|
+
strictNullChecks: true,
|
|
2236
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2237
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2238
|
+
types: [],
|
|
2239
|
+
noEmit: true
|
|
2240
|
+
};
|
|
2241
|
+
this.project = new import_ts_morph7.Project({
|
|
2242
|
+
skipAddingFilesFromTsConfig: true,
|
|
2243
|
+
compilerOptions: this.options
|
|
2244
|
+
});
|
|
2245
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2246
|
+
this.flush();
|
|
2247
|
+
}
|
|
2248
|
+
project;
|
|
2249
|
+
options;
|
|
2250
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2251
|
+
scratch = new import_ts_morph7.Project({
|
|
2252
|
+
skipAddingFilesFromTsConfig: true,
|
|
2253
|
+
skipLoadingLibFiles: true,
|
|
2254
|
+
skipFileDependencyResolution: true
|
|
2255
|
+
});
|
|
2256
|
+
needs = /* @__PURE__ */ new Map();
|
|
2257
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2258
|
+
written = /* @__PURE__ */ new Map();
|
|
2259
|
+
/**
|
|
2260
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2261
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2262
|
+
* program once.
|
|
2263
|
+
*/
|
|
2264
|
+
sourceFileFor(sf) {
|
|
2265
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2266
|
+
this.flush();
|
|
2267
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2268
|
+
}
|
|
2269
|
+
// -------------------------------------------------------------------------
|
|
2270
|
+
queue = [];
|
|
2271
|
+
request(path, want) {
|
|
2272
|
+
let needs = this.needs.get(path);
|
|
2273
|
+
const isNew = !needs;
|
|
2274
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2275
|
+
let grew = isNew;
|
|
2276
|
+
if (want.schema && !needs.schema) {
|
|
2277
|
+
needs.schema = true;
|
|
2278
|
+
grew = true;
|
|
2279
|
+
}
|
|
2280
|
+
if (want.all && !needs.all) {
|
|
2281
|
+
needs.all = true;
|
|
2282
|
+
grew = true;
|
|
2283
|
+
}
|
|
2284
|
+
for (const name of want.names ?? []) {
|
|
2285
|
+
if (!needs.names.has(name)) {
|
|
2286
|
+
needs.names.add(name);
|
|
2287
|
+
grew = true;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
this.needs.set(path, needs);
|
|
2291
|
+
if (grew) this.queue.push(path);
|
|
2292
|
+
}
|
|
2293
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2294
|
+
flush() {
|
|
2295
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2296
|
+
while (this.queue.length > 0) {
|
|
2297
|
+
const path = this.queue.pop();
|
|
2298
|
+
const original = this.original(path);
|
|
2299
|
+
if (!original) continue;
|
|
2300
|
+
const needs = this.needs.get(path);
|
|
2301
|
+
const text = this.prune(original, needs);
|
|
2302
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2303
|
+
}
|
|
2304
|
+
for (const [path, text] of changed) {
|
|
2305
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2306
|
+
this.written.set(path, text);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2310
|
+
original(path) {
|
|
2311
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2315
|
+
* requesting from each kept one's target the names it brings in.
|
|
2316
|
+
*/
|
|
2317
|
+
prune(sf, needs) {
|
|
2318
|
+
const locals = topLevelDeclarations(sf);
|
|
2319
|
+
const roots = [];
|
|
2320
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2321
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2322
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2323
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2324
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2325
|
+
if (spec === void 0) {
|
|
2326
|
+
for (const named of exp.getNamedExports()) {
|
|
2327
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2328
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2329
|
+
}
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2333
|
+
for (const named of exp.getNamedExports()) {
|
|
2334
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2335
|
+
const list = reexported.get(exposed) ?? [];
|
|
2336
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2337
|
+
reexported.set(exposed, list);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
const unresolvedNames = [];
|
|
2341
|
+
for (const name of needs.names) {
|
|
2342
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2343
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2344
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2345
|
+
}
|
|
2346
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2347
|
+
const queue = [...roots];
|
|
2348
|
+
const seen = new Set(queue);
|
|
2349
|
+
while (queue.length > 0) {
|
|
2350
|
+
const node = queue.pop();
|
|
2351
|
+
const ids = import_ts_morph7.Node.isIdentifier(node) ? [node] : node.getDescendantsOfKind(import_ts_morph7.ts.SyntaxKind.Identifier);
|
|
2352
|
+
for (const id of ids) {
|
|
2353
|
+
const name = id.getText();
|
|
2354
|
+
if (referenced.has(name)) continue;
|
|
2355
|
+
referenced.add(name);
|
|
2356
|
+
for (const local of locals.get(name) ?? []) {
|
|
2357
|
+
if (seen.has(local)) continue;
|
|
2358
|
+
seen.add(local);
|
|
2359
|
+
queue.push(local);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
const chars = sf.getFullText().split("");
|
|
2364
|
+
const blank = (node) => {
|
|
2365
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2366
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2370
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2371
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2372
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2373
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2374
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2375
|
+
imported: n.getName()
|
|
2376
|
+
})).filter((n) => referenced.has(n.local));
|
|
2377
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2378
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2379
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2380
|
+
blank(decl);
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
if (target) {
|
|
2384
|
+
this.request(target, {
|
|
2385
|
+
all: wantsNamespace,
|
|
2386
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2391
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2392
|
+
if (spec === void 0) continue;
|
|
2393
|
+
const target = this.resolve(spec, sf);
|
|
2394
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2395
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2396
|
+
blank(exp);
|
|
2397
|
+
} else if (target) {
|
|
2398
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2399
|
+
}
|
|
2400
|
+
continue;
|
|
2401
|
+
}
|
|
2402
|
+
const wanted = exp.getNamedExports().filter(
|
|
2403
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2404
|
+
).map((n) => n.getNameNode().getText());
|
|
2405
|
+
if (wanted.length === 0) blank(exp);
|
|
2406
|
+
else if (target) this.request(target, { names: wanted });
|
|
2407
|
+
}
|
|
2408
|
+
return chars.join("");
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2412
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2413
|
+
*/
|
|
2414
|
+
resolve(specifier, from) {
|
|
2415
|
+
const resolved = import_ts_morph7.ts.resolveModuleName(
|
|
2416
|
+
specifier,
|
|
2417
|
+
from.getFilePath(),
|
|
2418
|
+
this.options,
|
|
2419
|
+
import_ts_morph7.ts.sys
|
|
2420
|
+
).resolvedModule;
|
|
2421
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2422
|
+
const file = resolved.resolvedFileName;
|
|
2423
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2424
|
+
return file;
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
function schemaPositions(sf) {
|
|
2428
|
+
const roots = [];
|
|
2429
|
+
sf.forEachDescendant((node) => {
|
|
2430
|
+
if (import_ts_morph7.Node.isDecorator(node)) {
|
|
2431
|
+
const name = node.getName();
|
|
2432
|
+
if (name === "ApplyContract") {
|
|
2433
|
+
roots.push(...node.getArguments());
|
|
2434
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2435
|
+
for (const arg of node.getArguments()) {
|
|
2436
|
+
if (import_ts_morph7.Node.isNewExpression(arg) || import_ts_morph7.Node.isCallExpression(arg)) {
|
|
2437
|
+
roots.push(...arg.getArguments());
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
} else if (import_ts_morph7.Node.isCallExpression(node)) {
|
|
2442
|
+
const callee = node.getExpression();
|
|
2443
|
+
const name = import_ts_morph7.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph7.Node.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2444
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
return roots;
|
|
2448
|
+
}
|
|
2449
|
+
function topLevelDeclarations(sf) {
|
|
2450
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2451
|
+
const add = (name, node) => {
|
|
2452
|
+
if (!name) return;
|
|
2453
|
+
const list = byName.get(name);
|
|
2454
|
+
if (list) list.push(node);
|
|
2455
|
+
else byName.set(name, [node]);
|
|
2456
|
+
};
|
|
2457
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2458
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2459
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2460
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2461
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2462
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2463
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2464
|
+
return byName;
|
|
2465
|
+
}
|
|
2466
|
+
function defaultExport(sf) {
|
|
2467
|
+
const nodes = [];
|
|
2468
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2469
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2470
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2471
|
+
return nodes;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// src/discovery/schema-type-inference.ts
|
|
2475
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2476
|
+
function programFor(discovery) {
|
|
2477
|
+
let program = programs.get(discovery);
|
|
2478
|
+
if (!program) {
|
|
2479
|
+
program = new SchemaTypeProgram(discovery);
|
|
2480
|
+
programs.set(discovery, program);
|
|
2481
|
+
}
|
|
2482
|
+
return program;
|
|
2483
|
+
}
|
|
2484
|
+
function clearSchemaTypeProject(discovery) {
|
|
2485
|
+
programs.delete(discovery);
|
|
2486
|
+
}
|
|
2487
|
+
function counterpart(typedSf, node) {
|
|
2488
|
+
const start = node.getStart();
|
|
2489
|
+
const end = node.getEnd();
|
|
2490
|
+
const kind = node.getKind();
|
|
2491
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2492
|
+
while (candidate) {
|
|
2493
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2494
|
+
return candidate;
|
|
2495
|
+
}
|
|
2496
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2497
|
+
candidate = candidate.getParent();
|
|
2498
|
+
}
|
|
2499
|
+
return void 0;
|
|
2500
|
+
}
|
|
2501
|
+
function propType(type, name, at) {
|
|
2502
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2503
|
+
}
|
|
2504
|
+
function isAnyOrUnknown(type) {
|
|
2505
|
+
return type.isAny() || type.isUnknown();
|
|
2506
|
+
}
|
|
2507
|
+
function inferSchema(expr, discovery) {
|
|
2508
|
+
try {
|
|
2509
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2510
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2511
|
+
if (!at) return { kind: "unresolved" };
|
|
2512
|
+
const schemaType = at.getType();
|
|
2513
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2514
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2515
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2516
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2517
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2518
|
+
if (stdInput && stdOutput) {
|
|
2519
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2520
|
+
}
|
|
2521
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2522
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2523
|
+
if (zodInput && zodOutput) {
|
|
2524
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2525
|
+
}
|
|
2526
|
+
return { kind: "not-schema" };
|
|
2527
|
+
} catch (err) {
|
|
2528
|
+
dbg("schema inference failed:", err);
|
|
2529
|
+
return { kind: "unresolved" };
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2533
|
+
"Date",
|
|
2534
|
+
"File",
|
|
2535
|
+
"Blob",
|
|
2536
|
+
"FormData",
|
|
2537
|
+
"URL",
|
|
2538
|
+
"RegExp",
|
|
2539
|
+
"Map",
|
|
2540
|
+
"Set",
|
|
2541
|
+
"ArrayBuffer",
|
|
2542
|
+
"Uint8Array"
|
|
2543
|
+
]);
|
|
2544
|
+
var MAX_DEPTH = 12;
|
|
2545
|
+
function objectKey(name) {
|
|
2546
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2547
|
+
}
|
|
2548
|
+
function isOptionalSymbol(sym) {
|
|
2549
|
+
return (sym.getFlags() & import_ts_morph8.ts.SymbolFlags.Optional) !== 0;
|
|
2550
|
+
}
|
|
2551
|
+
function isSymbolKeyed(sym) {
|
|
2552
|
+
return sym.getName().startsWith("__@");
|
|
2553
|
+
}
|
|
2554
|
+
function orderedProperties(type) {
|
|
2555
|
+
const position = (sym) => {
|
|
2556
|
+
const decl = sym.getDeclarations()[0];
|
|
2557
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2558
|
+
};
|
|
2559
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2560
|
+
if (a.at && b.at) {
|
|
2561
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2562
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2563
|
+
} else if (a.at || b.at) {
|
|
2564
|
+
return a.at ? -1 : 1;
|
|
2565
|
+
}
|
|
2566
|
+
return a.index - b.index;
|
|
2567
|
+
}).map((entry) => entry.sym);
|
|
2568
|
+
}
|
|
2569
|
+
function isFromDeclarationFile(sym) {
|
|
2570
|
+
const decl = sym?.getDeclarations()[0];
|
|
2571
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2572
|
+
}
|
|
2573
|
+
function render(type, state, paired) {
|
|
2574
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2575
|
+
if (isAnyOrUnknown(type)) {
|
|
2576
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2577
|
+
}
|
|
2578
|
+
if (type.isNever()) return "never";
|
|
2579
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2580
|
+
if (type.isNumber()) return "number";
|
|
2581
|
+
if (type.isBoolean()) return "boolean";
|
|
2582
|
+
if (type.isBigInt()) return "bigint";
|
|
2583
|
+
if (type.isNull()) return "null";
|
|
2584
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2585
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2586
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2587
|
+
const value = type.getLiteralValue();
|
|
2588
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2589
|
+
}
|
|
2590
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2591
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2592
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2593
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2594
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2595
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2596
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2597
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2598
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2599
|
+
}
|
|
2600
|
+
if (type.isTuple()) {
|
|
2601
|
+
const elements = type.getTupleElements();
|
|
2602
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2603
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2604
|
+
}
|
|
2605
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2606
|
+
return "unknown";
|
|
2607
|
+
}
|
|
2608
|
+
function renderUnion(type, state, paired) {
|
|
2609
|
+
const members = type.getUnionTypes();
|
|
2610
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2611
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2612
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2613
|
+
const arms = [];
|
|
2614
|
+
let sawBoolean = false;
|
|
2615
|
+
let hasNull = false;
|
|
2616
|
+
let hasUndefined = false;
|
|
2617
|
+
for (const member of members) {
|
|
2618
|
+
if (member.isNull()) {
|
|
2619
|
+
hasNull = true;
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
if (member.isUndefined()) {
|
|
2623
|
+
hasUndefined = true;
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2627
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2628
|
+
sawBoolean = true;
|
|
2629
|
+
continue;
|
|
2630
|
+
}
|
|
2631
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2632
|
+
}
|
|
2633
|
+
if (hasNull) arms.push("null");
|
|
2634
|
+
if (hasUndefined) arms.push("undefined");
|
|
2635
|
+
const unique = [...new Set(arms)];
|
|
2636
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2637
|
+
return unique.join(" | ");
|
|
2638
|
+
}
|
|
2639
|
+
function renderIntersection(type, state) {
|
|
2640
|
+
const members = type.getIntersectionTypes();
|
|
2641
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2642
|
+
if (primitive) return render(primitive, state);
|
|
2643
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2644
|
+
}
|
|
2645
|
+
function renderObject(type, state, paired) {
|
|
2646
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2647
|
+
const name = symbol?.getName();
|
|
2648
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2649
|
+
const args = type.getTypeArguments();
|
|
2650
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2651
|
+
}
|
|
2652
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2653
|
+
return "unknown";
|
|
2654
|
+
}
|
|
2655
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2656
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2657
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2658
|
+
const inner = { ...state, stack };
|
|
2659
|
+
const fields = [];
|
|
2660
|
+
for (const prop of orderedProperties(type)) {
|
|
2661
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2662
|
+
const propName = prop.getName();
|
|
2663
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2664
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2665
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2666
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2667
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2668
|
+
}
|
|
2669
|
+
const stringIndex = type.getStringIndexType();
|
|
2670
|
+
if (stringIndex) {
|
|
2671
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2672
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2673
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2674
|
+
fields.push(`[key: string]: ${value}`);
|
|
2675
|
+
}
|
|
2676
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2677
|
+
}
|
|
2678
|
+
function stateAt(at) {
|
|
2679
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2680
|
+
}
|
|
2681
|
+
function requestTypeOf(types) {
|
|
2682
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2683
|
+
}
|
|
2684
|
+
function responseTypeOf(types) {
|
|
2685
|
+
return render(types.output, stateAt(types.at));
|
|
2686
|
+
}
|
|
2687
|
+
function requestFieldsOf(types) {
|
|
2688
|
+
const { input, output, at } = types;
|
|
2689
|
+
const objectInput = input.getNonNullableType();
|
|
2690
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2691
|
+
const state = stateAt(at);
|
|
2692
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2693
|
+
name: prop.getName(),
|
|
2694
|
+
type: render(
|
|
2695
|
+
prop.getTypeAtLocation(at),
|
|
2696
|
+
state,
|
|
2697
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2698
|
+
),
|
|
2699
|
+
optional: isOptionalSymbol(prop)
|
|
2700
|
+
}));
|
|
2701
|
+
}
|
|
2702
|
+
function acceptsUndefined(types) {
|
|
2703
|
+
const { input, output } = types;
|
|
2704
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2705
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2706
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2710
|
+
var import_ts_morph9 = require("ts-morph");
|
|
2711
|
+
function zodAstToTs(node) {
|
|
2712
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
|
|
2713
|
+
const expr = node.getExpression();
|
|
2714
|
+
if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
|
|
2715
|
+
const methodName = expr.getName();
|
|
2716
|
+
const receiver = expr.getExpression();
|
|
2717
|
+
if (methodName === "optional") {
|
|
2718
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2719
|
+
}
|
|
2720
|
+
if (methodName === "nullable") {
|
|
2721
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2722
|
+
}
|
|
2723
|
+
const args = node.getArguments();
|
|
2724
|
+
switch (methodName) {
|
|
2725
|
+
case "string":
|
|
2726
|
+
return "string";
|
|
2727
|
+
case "number":
|
|
2728
|
+
return "number";
|
|
2729
|
+
case "boolean":
|
|
2730
|
+
return "boolean";
|
|
2731
|
+
case "unknown":
|
|
2732
|
+
return "unknown";
|
|
2733
|
+
case "any":
|
|
2734
|
+
return "unknown";
|
|
2735
|
+
case "literal": {
|
|
2736
|
+
const lit = args[0];
|
|
2737
|
+
if (!lit) return "unknown";
|
|
2738
|
+
if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2739
|
+
if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2740
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
|
|
2741
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
|
|
2742
|
+
return "unknown";
|
|
2743
|
+
}
|
|
2744
|
+
case "enum": {
|
|
2745
|
+
const arrArg = args[0];
|
|
2746
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2747
|
+
const members = arrArg.getElements().map(
|
|
2748
|
+
(el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2749
|
+
);
|
|
2750
|
+
return members.join(" | ");
|
|
2751
|
+
}
|
|
2752
|
+
case "array": {
|
|
2753
|
+
const inner = args[0];
|
|
2754
|
+
if (!inner) return "unknown";
|
|
2755
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2756
|
+
}
|
|
2757
|
+
case "object": {
|
|
2758
|
+
const objArg = args[0];
|
|
2759
|
+
if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2760
|
+
const lines = [];
|
|
2761
|
+
for (const prop of objArg.getProperties()) {
|
|
2762
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
2763
|
+
const key = prop.getName();
|
|
2764
|
+
const valNode = prop.getInitializer();
|
|
2765
|
+
if (!valNode) continue;
|
|
2766
|
+
const tsType = zodAstToTs(valNode);
|
|
2767
|
+
const isOpt = isOptionalChain(valNode);
|
|
2768
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2769
|
+
}
|
|
2770
|
+
return `{ ${lines.join("; ")} }`;
|
|
2771
|
+
}
|
|
2772
|
+
case "union": {
|
|
2773
|
+
const arrArg = args[0];
|
|
2774
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2775
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2776
|
+
}
|
|
2777
|
+
case "record": {
|
|
2778
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2779
|
+
if (!valArg) return "unknown";
|
|
2780
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2781
|
+
}
|
|
2782
|
+
case "tuple": {
|
|
2783
|
+
const arrArg = args[0];
|
|
2784
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2785
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2786
|
+
}
|
|
2787
|
+
default:
|
|
2788
|
+
return "unknown";
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
return "unknown";
|
|
2792
|
+
}
|
|
2793
|
+
function isOptionalChain(node) {
|
|
2794
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return false;
|
|
2795
|
+
const expr = node.getExpression();
|
|
2796
|
+
return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2797
|
+
}
|
|
2798
|
+
function memberType(val, position, project) {
|
|
2799
|
+
if (project) {
|
|
2800
|
+
const inferred = inferSchema(val, project);
|
|
2801
|
+
if (inferred.kind === "schema") {
|
|
2802
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
return zodAstToTs(val);
|
|
2806
|
+
}
|
|
2807
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2808
|
+
if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
|
|
2809
|
+
const callee = callExpr.getExpression();
|
|
2810
|
+
const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2811
|
+
if (calleeName !== "defineContract") return null;
|
|
2812
|
+
const args = callExpr.getArguments();
|
|
2813
|
+
const optsArg = args[0];
|
|
2814
|
+
if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2815
|
+
let query = null;
|
|
2816
|
+
let body = null;
|
|
2817
|
+
let response = "unknown";
|
|
2818
|
+
let error = null;
|
|
2819
|
+
let bodyZodText = null;
|
|
2820
|
+
let queryZodText = null;
|
|
2821
|
+
for (const prop of optsArg.getProperties()) {
|
|
2822
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
2823
|
+
const propName = prop.getName();
|
|
2824
|
+
const val = prop.getInitializer();
|
|
2825
|
+
if (!val) continue;
|
|
2826
|
+
if (propName === "query") {
|
|
2827
|
+
query = memberType(val, "request", project);
|
|
2828
|
+
queryZodText = val.getText();
|
|
2829
|
+
} else if (propName === "body") {
|
|
2830
|
+
body = memberType(val, "request", project);
|
|
2831
|
+
bodyZodText = val.getText();
|
|
2832
|
+
} else if (propName === "response") {
|
|
2833
|
+
response = memberType(val, "response", project);
|
|
2834
|
+
} else if (propName === "error") {
|
|
2835
|
+
error = memberType(val, "response", project);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/discovery/pipe-schema.ts
|
|
2842
|
+
function pipeSchemaOf(decorator, project) {
|
|
2843
|
+
for (const pipe of decorator.getArguments()) {
|
|
2844
|
+
if (!import_ts_morph10.Node.isNewExpression(pipe) && !import_ts_morph10.Node.isCallExpression(pipe)) continue;
|
|
2845
|
+
for (const arg of pipe.getArguments()) {
|
|
2846
|
+
if (!couldBeSchema(arg)) continue;
|
|
2847
|
+
const inferred = inferSchema(arg, project);
|
|
2848
|
+
if (inferred.kind === "schema") {
|
|
2849
|
+
return {
|
|
2850
|
+
type: requestTypeOf(inferred.types),
|
|
2851
|
+
optional: acceptsUndefined(inferred.types),
|
|
2852
|
+
fields: requestFieldsOf(inferred.types)
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
if (inferred.kind === "unresolved") {
|
|
2856
|
+
const fallback = syntacticZodType(arg, project);
|
|
2857
|
+
if (fallback) {
|
|
2858
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
return null;
|
|
2864
|
+
}
|
|
2865
|
+
function couldBeSchema(arg) {
|
|
2866
|
+
return import_ts_morph10.Node.isIdentifier(arg) || import_ts_morph10.Node.isPropertyAccessExpression(arg) || import_ts_morph10.Node.isCallExpression(arg) || import_ts_morph10.Node.isAsExpression(arg) || import_ts_morph10.Node.isSatisfiesExpression(arg) || import_ts_morph10.Node.isParenthesizedExpression(arg);
|
|
2867
|
+
}
|
|
2868
|
+
function syntacticZodType(arg, project) {
|
|
2869
|
+
let expr = arg;
|
|
2870
|
+
if (import_ts_morph10.Node.isIdentifier(arg)) {
|
|
2871
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2872
|
+
expr = resolved?.decl.getInitializer();
|
|
2873
|
+
}
|
|
2874
|
+
if (!expr || !import_ts_morph10.Node.isCallExpression(expr)) return null;
|
|
2875
|
+
const type = zodAstToTs(expr);
|
|
2876
|
+
return type === "unknown" ? null : type;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2124
2879
|
// src/discovery/dto-type-resolver.ts
|
|
2125
2880
|
var WRAPPER_TYPES = {
|
|
2126
2881
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2152,22 +2907,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2152
2907
|
]);
|
|
2153
2908
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2154
2909
|
if (depth <= 0) return "unknown";
|
|
2155
|
-
if (
|
|
2910
|
+
if (import_ts_morph11.Node.isArrayTypeNode(typeNode)) {
|
|
2156
2911
|
const elementType = typeNode.getElementTypeNode();
|
|
2157
2912
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2158
2913
|
}
|
|
2159
|
-
if (
|
|
2914
|
+
if (import_ts_morph11.Node.isUnionTypeNode(typeNode)) {
|
|
2160
2915
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2161
2916
|
}
|
|
2162
|
-
if (
|
|
2917
|
+
if (import_ts_morph11.Node.isIntersectionTypeNode(typeNode)) {
|
|
2163
2918
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2164
2919
|
}
|
|
2165
|
-
if (
|
|
2920
|
+
if (import_ts_morph11.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2166
2921
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2167
2922
|
}
|
|
2168
|
-
if (
|
|
2923
|
+
if (import_ts_morph11.Node.isTypeReference(typeNode)) {
|
|
2169
2924
|
const typeName = typeNode.getTypeName();
|
|
2170
|
-
const name =
|
|
2925
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2171
2926
|
const bound = subst.get(name);
|
|
2172
2927
|
if (bound !== void 0) return bound;
|
|
2173
2928
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2190,13 +2945,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2190
2945
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2191
2946
|
return "unknown";
|
|
2192
2947
|
}
|
|
2193
|
-
if (
|
|
2948
|
+
if (import_ts_morph11.Node.isTypeLiteral(typeNode)) {
|
|
2194
2949
|
const members = [];
|
|
2195
2950
|
for (const member of typeNode.getMembers()) {
|
|
2196
|
-
if (
|
|
2951
|
+
if (import_ts_morph11.Node.isPropertySignature(member)) {
|
|
2197
2952
|
const memberTypeNode = member.getTypeNode();
|
|
2198
|
-
const
|
|
2199
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2953
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2954
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2200
2955
|
} else {
|
|
2201
2956
|
members.push(member.getText());
|
|
2202
2957
|
}
|
|
@@ -2204,11 +2959,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2204
2959
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2205
2960
|
}
|
|
2206
2961
|
const kind = typeNode.getKind();
|
|
2207
|
-
if (kind ===
|
|
2208
|
-
if (kind ===
|
|
2209
|
-
if (kind ===
|
|
2210
|
-
if (kind ===
|
|
2211
|
-
if (kind ===
|
|
2962
|
+
if (kind === import_ts_morph11.SyntaxKind.StringKeyword) return "string";
|
|
2963
|
+
if (kind === import_ts_morph11.SyntaxKind.NumberKeyword) return "number";
|
|
2964
|
+
if (kind === import_ts_morph11.SyntaxKind.BooleanKeyword) return "boolean";
|
|
2965
|
+
if (kind === import_ts_morph11.SyntaxKind.UnknownKeyword) return "unknown";
|
|
2966
|
+
if (kind === import_ts_morph11.SyntaxKind.AnyKeyword) return "unknown";
|
|
2212
2967
|
return typeNode.getText();
|
|
2213
2968
|
}
|
|
2214
2969
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2256,20 +3011,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2256
3011
|
const propName = prop.getName();
|
|
2257
3012
|
const isOptional = prop.hasQuestionToken();
|
|
2258
3013
|
const propTypeNode = prop.getTypeNode();
|
|
2259
|
-
let
|
|
3014
|
+
let propType2 = "unknown";
|
|
2260
3015
|
if (propTypeNode) {
|
|
2261
|
-
|
|
3016
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2262
3017
|
}
|
|
2263
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
3018
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2264
3019
|
}
|
|
2265
3020
|
return `{ ${lines.join("; ")} }`;
|
|
2266
3021
|
}
|
|
3022
|
+
function namedField(decorator) {
|
|
3023
|
+
const first = decorator.getArguments()[0];
|
|
3024
|
+
return first && import_ts_morph11.Node.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
3025
|
+
}
|
|
2267
3026
|
function extractBodyType(method, sourceFile, project) {
|
|
2268
3027
|
for (const param of method.getParameters()) {
|
|
2269
3028
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2270
|
-
if (!bodyDecorator) continue;
|
|
2271
|
-
const
|
|
2272
|
-
if (
|
|
3029
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
3030
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
3031
|
+
if (piped) return piped.type;
|
|
2273
3032
|
const typeNode = param.getTypeNode();
|
|
2274
3033
|
if (typeNode) {
|
|
2275
3034
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2277,11 +3036,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2277
3036
|
}
|
|
2278
3037
|
return null;
|
|
2279
3038
|
}
|
|
3039
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
3040
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
3041
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
3042
|
+
}
|
|
2280
3043
|
function extractQueryType(method, sourceFile, project) {
|
|
2281
3044
|
for (const param of method.getParameters()) {
|
|
2282
3045
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2283
|
-
if (!queryDecorator) continue;
|
|
2284
|
-
|
|
3046
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
3047
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3048
|
+
if (piped) return piped.type;
|
|
2285
3049
|
const typeNode = param.getTypeNode();
|
|
2286
3050
|
if (typeNode) {
|
|
2287
3051
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2291,10 +3055,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2291
3055
|
for (const param of method.getParameters()) {
|
|
2292
3056
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2293
3057
|
if (!queryDecorator) continue;
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
3058
|
+
const queryName = namedField(queryDecorator);
|
|
3059
|
+
if (queryName === null) continue;
|
|
3060
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3061
|
+
if (piped) {
|
|
3062
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
2298
3065
|
const typeNode = param.getTypeNode();
|
|
2299
3066
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2300
3067
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2304,8 +3071,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2304
3071
|
function isParamOptional(param) {
|
|
2305
3072
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2306
3073
|
const typeNode = param.getTypeNode();
|
|
2307
|
-
if (typeNode &&
|
|
2308
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
3074
|
+
if (typeNode && import_ts_morph11.Node.isUnionTypeNode(typeNode)) {
|
|
3075
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === import_ts_morph11.SyntaxKind.UndefinedKeyword);
|
|
2309
3076
|
}
|
|
2310
3077
|
return false;
|
|
2311
3078
|
}
|
|
@@ -2317,7 +3084,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2317
3084
|
const paramArgs = paramDecorator.getArguments();
|
|
2318
3085
|
if (paramArgs.length === 0) continue;
|
|
2319
3086
|
const nameArg = paramArgs[0];
|
|
2320
|
-
if (!
|
|
3087
|
+
if (!import_ts_morph11.Node.isStringLiteral(nameArg)) continue;
|
|
2321
3088
|
const paramName = nameArg.getLiteralValue();
|
|
2322
3089
|
const typeNode = param.getTypeNode();
|
|
2323
3090
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2325,6 +3092,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2325
3092
|
}
|
|
2326
3093
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2327
3094
|
}
|
|
3095
|
+
function extractPipedParamTypes(method, project) {
|
|
3096
|
+
const types = {};
|
|
3097
|
+
for (const param of method.getParameters()) {
|
|
3098
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
3099
|
+
if (!paramDecorator) continue;
|
|
3100
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
3101
|
+
if (!piped) continue;
|
|
3102
|
+
const name = namedField(paramDecorator);
|
|
3103
|
+
if (name !== null) {
|
|
3104
|
+
types[name] = piped.type;
|
|
3105
|
+
} else {
|
|
3106
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
3110
|
+
}
|
|
2328
3111
|
function extractUploadedFiles(method) {
|
|
2329
3112
|
const FILE = "File | Blob";
|
|
2330
3113
|
const entries = [];
|
|
@@ -2338,28 +3121,28 @@ function extractUploadedFiles(method) {
|
|
|
2338
3121
|
for (const decorator of method.getDecorators()) {
|
|
2339
3122
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2340
3123
|
for (const arg of decorator.getArguments()) {
|
|
2341
|
-
if (!
|
|
3124
|
+
if (!import_ts_morph11.Node.isCallExpression(arg)) continue;
|
|
2342
3125
|
const interceptor = arg.getExpression().getText();
|
|
2343
3126
|
const callArgs = arg.getArguments();
|
|
2344
3127
|
const firstArg2 = callArgs[0];
|
|
2345
3128
|
if (interceptor === "FileInterceptor") {
|
|
2346
|
-
if (firstArg2 &&
|
|
3129
|
+
if (firstArg2 && import_ts_morph11.Node.isStringLiteral(firstArg2)) {
|
|
2347
3130
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2348
3131
|
multipart = true;
|
|
2349
3132
|
}
|
|
2350
3133
|
} else if (interceptor === "FilesInterceptor") {
|
|
2351
|
-
if (firstArg2 &&
|
|
3134
|
+
if (firstArg2 && import_ts_morph11.Node.isStringLiteral(firstArg2)) {
|
|
2352
3135
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2353
3136
|
multipart = true;
|
|
2354
3137
|
}
|
|
2355
3138
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2356
|
-
if (firstArg2 &&
|
|
3139
|
+
if (firstArg2 && import_ts_morph11.Node.isArrayLiteralExpression(firstArg2)) {
|
|
2357
3140
|
for (const el of firstArg2.getElements()) {
|
|
2358
|
-
if (!
|
|
3141
|
+
if (!import_ts_morph11.Node.isObjectLiteralExpression(el)) continue;
|
|
2359
3142
|
const nameProp = el.getProperty("name");
|
|
2360
|
-
if (nameProp &&
|
|
3143
|
+
if (nameProp && import_ts_morph11.Node.isPropertyAssignment(nameProp)) {
|
|
2361
3144
|
const init = nameProp.getInitializer();
|
|
2362
|
-
if (init &&
|
|
3145
|
+
if (init && import_ts_morph11.Node.isStringLiteral(init)) {
|
|
2363
3146
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2364
3147
|
}
|
|
2365
3148
|
}
|
|
@@ -2379,13 +3162,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2379
3162
|
if (apiResponseDecorator) {
|
|
2380
3163
|
const args = apiResponseDecorator.getArguments();
|
|
2381
3164
|
const optsArg = args[0];
|
|
2382
|
-
if (optsArg &&
|
|
3165
|
+
if (optsArg && import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) {
|
|
2383
3166
|
for (const prop of optsArg.getProperties()) {
|
|
2384
|
-
if (!
|
|
3167
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2385
3168
|
if (prop.getName() !== "type") continue;
|
|
2386
3169
|
const val = prop.getInitializer();
|
|
2387
3170
|
if (!val) continue;
|
|
2388
|
-
if (
|
|
3171
|
+
if (import_ts_morph11.Node.isArrayLiteralExpression(val)) {
|
|
2389
3172
|
const elements = val.getElements();
|
|
2390
3173
|
const firstEl = elements[0];
|
|
2391
3174
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2406,24 +3189,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2406
3189
|
}
|
|
2407
3190
|
function apiResponseStatus(decorator) {
|
|
2408
3191
|
const optsArg = decorator.getArguments()[0];
|
|
2409
|
-
if (!optsArg || !
|
|
3192
|
+
if (!optsArg || !import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2410
3193
|
for (const prop of optsArg.getProperties()) {
|
|
2411
|
-
if (!
|
|
3194
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2412
3195
|
if (prop.getName() !== "status") continue;
|
|
2413
3196
|
const val = prop.getInitializer();
|
|
2414
|
-
if (val &&
|
|
3197
|
+
if (val && import_ts_morph11.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2415
3198
|
}
|
|
2416
3199
|
return null;
|
|
2417
3200
|
}
|
|
2418
3201
|
function apiResponseTypeNode(decorator) {
|
|
2419
3202
|
const optsArg = decorator.getArguments()[0];
|
|
2420
|
-
if (!optsArg || !
|
|
3203
|
+
if (!optsArg || !import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2421
3204
|
for (const prop of optsArg.getProperties()) {
|
|
2422
|
-
if (!
|
|
3205
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2423
3206
|
if (prop.getName() !== "type") continue;
|
|
2424
3207
|
const val = prop.getInitializer();
|
|
2425
3208
|
if (!val) return null;
|
|
2426
|
-
if (
|
|
3209
|
+
if (import_ts_morph11.Node.isArrayLiteralExpression(val)) {
|
|
2427
3210
|
const first = val.getElements()[0];
|
|
2428
3211
|
return first ? { node: first, isArray: true } : null;
|
|
2429
3212
|
}
|
|
@@ -2441,7 +3224,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2441
3224
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2442
3225
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2443
3226
|
let ref = null;
|
|
2444
|
-
if (
|
|
3227
|
+
if (import_ts_morph11.Node.isIdentifier(typeInfo.node)) {
|
|
2445
3228
|
const name = typeInfo.node.getText();
|
|
2446
3229
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2447
3230
|
if (localDecl?.isExported()) {
|
|
@@ -2458,7 +3241,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2458
3241
|
return null;
|
|
2459
3242
|
}
|
|
2460
3243
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2461
|
-
if (!
|
|
3244
|
+
if (!import_ts_morph11.Node.isIdentifier(node)) return "unknown";
|
|
2462
3245
|
const name = node.getText();
|
|
2463
3246
|
const resolved = findType(name, sourceFile, project);
|
|
2464
3247
|
if (resolved) {
|
|
@@ -2478,9 +3261,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2478
3261
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2479
3262
|
function detectBinaryResponse(method) {
|
|
2480
3263
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2481
|
-
if (!node || !
|
|
3264
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return false;
|
|
2482
3265
|
const typeName = node.getTypeName();
|
|
2483
|
-
const name =
|
|
3266
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2484
3267
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2485
3268
|
}
|
|
2486
3269
|
function detectStreamElement(method) {
|
|
@@ -2495,9 +3278,9 @@ function detectStreamElement(method) {
|
|
|
2495
3278
|
return null;
|
|
2496
3279
|
}
|
|
2497
3280
|
function streamContainerElement(node) {
|
|
2498
|
-
if (!node || !
|
|
3281
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return null;
|
|
2499
3282
|
const typeName = node.getTypeName();
|
|
2500
|
-
const name =
|
|
3283
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2501
3284
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2502
3285
|
return node.getTypeArguments()[0] ?? null;
|
|
2503
3286
|
}
|
|
@@ -2507,9 +3290,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2507
3290
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2508
3291
|
}
|
|
2509
3292
|
function unwrapNamedContainer(node, names) {
|
|
2510
|
-
if (!node || !
|
|
3293
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return node;
|
|
2511
3294
|
const typeName = node.getTypeName();
|
|
2512
|
-
const name =
|
|
3295
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2513
3296
|
if (names.has(name)) {
|
|
2514
3297
|
return node.getTypeArguments()[0] ?? node;
|
|
2515
3298
|
}
|
|
@@ -2530,19 +3313,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2530
3313
|
body = body ?? bodyType;
|
|
2531
3314
|
}
|
|
2532
3315
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3316
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2533
3317
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2534
3318
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
2535
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3319
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2536
3320
|
return null;
|
|
2537
3321
|
}
|
|
2538
3322
|
let bodyRef = null;
|
|
2539
3323
|
let queryRef = null;
|
|
2540
3324
|
let responseRef = null;
|
|
2541
3325
|
for (const param of method.getParameters()) {
|
|
2542
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3326
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2543
3327
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2544
3328
|
}
|
|
2545
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3329
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2546
3330
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2547
3331
|
}
|
|
2548
3332
|
}
|
|
@@ -2555,11 +3339,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2555
3339
|
if (apiResp) {
|
|
2556
3340
|
const args = apiResp.getArguments();
|
|
2557
3341
|
const optsArg = args[0];
|
|
2558
|
-
if (optsArg &&
|
|
3342
|
+
if (optsArg && import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) {
|
|
2559
3343
|
for (const prop of optsArg.getProperties()) {
|
|
2560
|
-
if (
|
|
3344
|
+
if (import_ts_morph11.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2561
3345
|
const val = prop.getInitializer();
|
|
2562
|
-
if (val &&
|
|
3346
|
+
if (val && import_ts_morph11.Node.isIdentifier(val)) {
|
|
2563
3347
|
const name = val.getText();
|
|
2564
3348
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2565
3349
|
if (localDecl?.isExported()) {
|
|
@@ -2595,6 +3379,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2595
3379
|
response,
|
|
2596
3380
|
error: errorInfo?.type ?? null,
|
|
2597
3381
|
params: paramsType,
|
|
3382
|
+
paramTypes,
|
|
2598
3383
|
queryRef,
|
|
2599
3384
|
bodyRef,
|
|
2600
3385
|
responseRef,
|
|
@@ -2615,6 +3400,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2615
3400
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2616
3401
|
for (const param of method.getParameters()) {
|
|
2617
3402
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3403
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2618
3404
|
const typeNode = param.getTypeNode();
|
|
2619
3405
|
if (!typeNode) continue;
|
|
2620
3406
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2627,129 +3413,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2627
3413
|
return null;
|
|
2628
3414
|
}
|
|
2629
3415
|
|
|
2630
|
-
// src/discovery/zod-ast-to-ts.ts
|
|
2631
|
-
var import_ts_morph8 = require("ts-morph");
|
|
2632
|
-
function zodAstToTs(node) {
|
|
2633
|
-
if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
|
|
2634
|
-
const expr = node.getExpression();
|
|
2635
|
-
if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
|
|
2636
|
-
const methodName = expr.getName();
|
|
2637
|
-
const receiver = expr.getExpression();
|
|
2638
|
-
if (methodName === "optional") {
|
|
2639
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
2640
|
-
}
|
|
2641
|
-
if (methodName === "nullable") {
|
|
2642
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
2643
|
-
}
|
|
2644
|
-
const args = node.getArguments();
|
|
2645
|
-
switch (methodName) {
|
|
2646
|
-
case "string":
|
|
2647
|
-
return "string";
|
|
2648
|
-
case "number":
|
|
2649
|
-
return "number";
|
|
2650
|
-
case "boolean":
|
|
2651
|
-
return "boolean";
|
|
2652
|
-
case "unknown":
|
|
2653
|
-
return "unknown";
|
|
2654
|
-
case "any":
|
|
2655
|
-
return "unknown";
|
|
2656
|
-
case "literal": {
|
|
2657
|
-
const lit = args[0];
|
|
2658
|
-
if (!lit) return "unknown";
|
|
2659
|
-
if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2660
|
-
if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2661
|
-
if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
|
|
2662
|
-
if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
|
|
2663
|
-
return "unknown";
|
|
2664
|
-
}
|
|
2665
|
-
case "enum": {
|
|
2666
|
-
const arrArg = args[0];
|
|
2667
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2668
|
-
const members = arrArg.getElements().map(
|
|
2669
|
-
(el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2670
|
-
);
|
|
2671
|
-
return members.join(" | ");
|
|
2672
|
-
}
|
|
2673
|
-
case "array": {
|
|
2674
|
-
const inner = args[0];
|
|
2675
|
-
if (!inner) return "unknown";
|
|
2676
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
2677
|
-
}
|
|
2678
|
-
case "object": {
|
|
2679
|
-
const objArg = args[0];
|
|
2680
|
-
if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2681
|
-
const lines = [];
|
|
2682
|
-
for (const prop of objArg.getProperties()) {
|
|
2683
|
-
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2684
|
-
const key = prop.getName();
|
|
2685
|
-
const valNode = prop.getInitializer();
|
|
2686
|
-
if (!valNode) continue;
|
|
2687
|
-
const tsType = zodAstToTs(valNode);
|
|
2688
|
-
const isOpt = isOptionalChain(valNode);
|
|
2689
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2690
|
-
}
|
|
2691
|
-
return `{ ${lines.join("; ")} }`;
|
|
2692
|
-
}
|
|
2693
|
-
case "union": {
|
|
2694
|
-
const arrArg = args[0];
|
|
2695
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2696
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2697
|
-
}
|
|
2698
|
-
case "record": {
|
|
2699
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2700
|
-
if (!valArg) return "unknown";
|
|
2701
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2702
|
-
}
|
|
2703
|
-
case "tuple": {
|
|
2704
|
-
const arrArg = args[0];
|
|
2705
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2706
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2707
|
-
}
|
|
2708
|
-
default:
|
|
2709
|
-
return "unknown";
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
return "unknown";
|
|
2713
|
-
}
|
|
2714
|
-
function isOptionalChain(node) {
|
|
2715
|
-
if (!import_ts_morph8.Node.isCallExpression(node)) return false;
|
|
2716
|
-
const expr = node.getExpression();
|
|
2717
|
-
return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2718
|
-
}
|
|
2719
|
-
function parseDefineContractCall(callExpr) {
|
|
2720
|
-
if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
|
|
2721
|
-
const callee = callExpr.getExpression();
|
|
2722
|
-
const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2723
|
-
if (calleeName !== "defineContract") return null;
|
|
2724
|
-
const args = callExpr.getArguments();
|
|
2725
|
-
const optsArg = args[0];
|
|
2726
|
-
if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2727
|
-
let query = null;
|
|
2728
|
-
let body = null;
|
|
2729
|
-
let response = "unknown";
|
|
2730
|
-
let error = null;
|
|
2731
|
-
let bodyZodText = null;
|
|
2732
|
-
let queryZodText = null;
|
|
2733
|
-
for (const prop of optsArg.getProperties()) {
|
|
2734
|
-
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2735
|
-
const propName = prop.getName();
|
|
2736
|
-
const val = prop.getInitializer();
|
|
2737
|
-
if (!val) continue;
|
|
2738
|
-
if (propName === "query") {
|
|
2739
|
-
query = zodAstToTs(val);
|
|
2740
|
-
queryZodText = val.getText();
|
|
2741
|
-
} else if (propName === "body") {
|
|
2742
|
-
body = zodAstToTs(val);
|
|
2743
|
-
bodyZodText = val.getText();
|
|
2744
|
-
} else if (propName === "response") {
|
|
2745
|
-
response = zodAstToTs(val);
|
|
2746
|
-
} else if (propName === "error") {
|
|
2747
|
-
error = zodAstToTs(val);
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
3416
|
// src/discovery/contracts-fast.ts
|
|
2754
3417
|
async function discoverContractsFast(opts) {
|
|
2755
3418
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2762,7 +3425,9 @@ async function discoverContractsFast(opts) {
|
|
|
2762
3425
|
}
|
|
2763
3426
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2764
3427
|
clearMixinTypeProject();
|
|
2765
|
-
|
|
3428
|
+
const routes = extractAllRoutes(project);
|
|
3429
|
+
clearSchemaTypeProject(project);
|
|
3430
|
+
return routes;
|
|
2766
3431
|
}
|
|
2767
3432
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2768
3433
|
return tsconfig ? (0, import_node_path5.resolve)(tsconfig) : (0, import_node_path5.join)(cwd, "tsconfig.json");
|
|
@@ -2774,25 +3439,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2774
3439
|
};
|
|
2775
3440
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2776
3441
|
if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
|
|
2777
|
-
const read =
|
|
3442
|
+
const read = import_ts_morph12.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
|
|
2778
3443
|
if (read.error) {
|
|
2779
3444
|
return {
|
|
2780
3445
|
options: null,
|
|
2781
3446
|
files: [tsconfigPath],
|
|
2782
|
-
error:
|
|
3447
|
+
error: import_ts_morph12.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2783
3448
|
};
|
|
2784
3449
|
}
|
|
2785
3450
|
const host = {
|
|
2786
|
-
useCaseSensitiveFileNames:
|
|
3451
|
+
useCaseSensitiveFileNames: import_ts_morph12.ts.sys.useCaseSensitiveFileNames,
|
|
2787
3452
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2788
3453
|
readDirectory: () => [],
|
|
2789
|
-
fileExists: (path) =>
|
|
3454
|
+
fileExists: (path) => import_ts_morph12.ts.sys.fileExists(path),
|
|
2790
3455
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2791
3456
|
// in a base tsconfig keeps working.
|
|
2792
|
-
readFile: (path) =>
|
|
3457
|
+
readFile: (path) => import_ts_morph12.ts.sys.readFile(path)
|
|
2793
3458
|
};
|
|
2794
|
-
const sourceFile =
|
|
2795
|
-
const parsed =
|
|
3459
|
+
const sourceFile = import_ts_morph12.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
|
|
3460
|
+
const parsed = import_ts_morph12.ts.parseJsonSourceFileConfigFileContent(
|
|
2796
3461
|
sourceFile,
|
|
2797
3462
|
host,
|
|
2798
3463
|
(0, import_node_path5.dirname)(tsconfigPath),
|
|
@@ -2815,7 +3480,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2815
3480
|
`[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.`
|
|
2816
3481
|
);
|
|
2817
3482
|
}
|
|
2818
|
-
return new
|
|
3483
|
+
return new import_ts_morph12.Project({
|
|
2819
3484
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2820
3485
|
skipAddingFilesFromTsConfig: true,
|
|
2821
3486
|
skipLoadingLibFiles: true,
|
|
@@ -2927,15 +3592,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2927
3592
|
clearTypeResolutionCaches(this.project);
|
|
2928
3593
|
clearEnumCache(this.project);
|
|
2929
3594
|
clearMixinTypeProject();
|
|
3595
|
+
clearSchemaTypeProject(this.project);
|
|
2930
3596
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2931
3597
|
}
|
|
2932
3598
|
};
|
|
2933
3599
|
function decoratorStringArg(decoratorExpr) {
|
|
2934
3600
|
if (!decoratorExpr) return void 0;
|
|
2935
|
-
if (
|
|
2936
|
-
if (
|
|
3601
|
+
if (import_ts_morph12.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3602
|
+
if (import_ts_morph12.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
2937
3603
|
const first = decoratorExpr.getElements()[0];
|
|
2938
|
-
if (first &&
|
|
3604
|
+
if (first && import_ts_morph12.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
2939
3605
|
}
|
|
2940
3606
|
return void 0;
|
|
2941
3607
|
}
|
|
@@ -3056,9 +3722,9 @@ function extractContractRoute(args) {
|
|
|
3056
3722
|
let contractDef = null;
|
|
3057
3723
|
let bodyZodRef = null;
|
|
3058
3724
|
let queryZodRef = null;
|
|
3059
|
-
if (
|
|
3060
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3061
|
-
} else if (
|
|
3725
|
+
if (import_ts_morph12.Node.isCallExpression(firstDecoratorArg)) {
|
|
3726
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3727
|
+
} else if (import_ts_morph12.Node.isIdentifier(firstDecoratorArg)) {
|
|
3062
3728
|
const identName = firstDecoratorArg.getText();
|
|
3063
3729
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3064
3730
|
if (!resolvedVar) {
|
|
@@ -3070,7 +3736,7 @@ function extractContractRoute(args) {
|
|
|
3070
3736
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3071
3737
|
const initializer = varDecl.getInitializer();
|
|
3072
3738
|
if (!initializer) return null;
|
|
3073
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3739
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3074
3740
|
if (contractDef && varDecl.isExported()) {
|
|
3075
3741
|
const filePath = declFile.getFilePath();
|
|
3076
3742
|
if (contractDef.body !== null) {
|
|
@@ -3141,6 +3807,7 @@ function extractDtoRoute(args) {
|
|
|
3141
3807
|
body: dtoContract?.body ?? null,
|
|
3142
3808
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3143
3809
|
error: dtoContract?.error ?? null,
|
|
3810
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3144
3811
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3145
3812
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3146
3813
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3263,7 +3930,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3263
3930
|
},
|
|
3264
3931
|
project() {
|
|
3265
3932
|
if (!project) {
|
|
3266
|
-
project = new
|
|
3933
|
+
project = new import_ts_morph13.Project({
|
|
3267
3934
|
skipAddingFilesFromTsConfig: true,
|
|
3268
3935
|
skipLoadingLibFiles: true,
|
|
3269
3936
|
skipFileDependencyResolution: true,
|
|
@@ -3381,10 +4048,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3381
4048
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3382
4049
|
}
|
|
3383
4050
|
}
|
|
3384
|
-
function buildParamsType(params) {
|
|
4051
|
+
function buildParamsType(params, paramTypes) {
|
|
3385
4052
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3386
4053
|
if (pathParams.length === 0) return "never";
|
|
3387
|
-
|
|
4054
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
4055
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3388
4056
|
}
|
|
3389
4057
|
function hasPathParams(params) {
|
|
3390
4058
|
return params.some((p) => p.source === "path");
|
|
@@ -3478,7 +4146,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3478
4146
|
}
|
|
3479
4147
|
const response = buildResponseType(c, outDir, serialization);
|
|
3480
4148
|
const error = buildErrorType(c);
|
|
3481
|
-
const params = buildParamsType(c.params);
|
|
4149
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3482
4150
|
const safeMethod = JSON.stringify(method);
|
|
3483
4151
|
const safeUrl = JSON.stringify(c.path);
|
|
3484
4152
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3566,28 +4234,28 @@ function renderFetcherRequest(req, binaryResponse) {
|
|
|
3566
4234
|
}
|
|
3567
4235
|
function emitReqHelper() {
|
|
3568
4236
|
return [
|
|
3569
|
-
"/**
|
|
3570
|
-
"type
|
|
3571
|
-
"
|
|
4237
|
+
"/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
|
|
4238
|
+
"export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
|
|
4239
|
+
"class __Request<R> extends Promise<R> {",
|
|
4240
|
+
" static override get [Symbol.species](): PromiseConstructor {",
|
|
4241
|
+
" return Promise;",
|
|
4242
|
+
" }",
|
|
4243
|
+
" private __run: () => Promise<R>;",
|
|
4244
|
+
" private __p: Promise<R> | undefined;",
|
|
4245
|
+
" constructor(run: () => Promise<R>) {",
|
|
4246
|
+
" super(() => {});",
|
|
4247
|
+
" this.__run = run;",
|
|
4248
|
+
" }",
|
|
4249
|
+
" override then<T1 = R, T2 = never>(",
|
|
3572
4250
|
" onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
|
|
3573
4251
|
" onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
|
|
3574
|
-
" ): Promise<T1 | T2
|
|
3575
|
-
"
|
|
3576
|
-
"
|
|
3577
|
-
"
|
|
3578
|
-
"}
|
|
3579
|
-
"function __req<R>(run: () => Promise<R
|
|
3580
|
-
"
|
|
3581
|
-
" const __promise = () => {",
|
|
3582
|
-
" __p ??= run();",
|
|
3583
|
-
" return __p;",
|
|
3584
|
-
" };",
|
|
3585
|
-
" return {",
|
|
3586
|
-
" then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
|
|
3587
|
-
" catch: (onrejected) => __promise().catch(onrejected),",
|
|
3588
|
-
" finally: (onfinally) => __promise().finally(onfinally),",
|
|
3589
|
-
" fetch: run,",
|
|
3590
|
-
" };",
|
|
4252
|
+
" ): Promise<T1 | T2> {",
|
|
4253
|
+
" this.__p ??= this.__run();",
|
|
4254
|
+
" return this.__p.then(onfulfilled, onrejected);",
|
|
4255
|
+
" }",
|
|
4256
|
+
"}",
|
|
4257
|
+
"function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
|
|
4258
|
+
" return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
|
|
3591
4259
|
"}",
|
|
3592
4260
|
""
|
|
3593
4261
|
];
|
|
@@ -3605,18 +4273,26 @@ function emitFilterFieldGuard() {
|
|
|
3605
4273
|
];
|
|
3606
4274
|
}
|
|
3607
4275
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
3608
|
-
const
|
|
3609
|
-
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4276
|
+
const memberLines = [];
|
|
3610
4277
|
if (req.filterFieldsExpr) {
|
|
3611
|
-
|
|
4278
|
+
memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
3612
4279
|
}
|
|
3613
4280
|
if (streamExpr) {
|
|
3614
|
-
|
|
4281
|
+
memberLines.push(`${pad} stream: () => ${streamExpr},`);
|
|
3615
4282
|
}
|
|
3616
4283
|
for (const [name, value] of Object.entries(members)) {
|
|
3617
|
-
|
|
3618
|
-
}
|
|
3619
|
-
|
|
4284
|
+
memberLines.push(`${pad} ${name}: ${value},`);
|
|
4285
|
+
}
|
|
4286
|
+
const body = [requestExpr, ...memberLines].join("\n");
|
|
4287
|
+
const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
|
|
4288
|
+
const lines = [`${pad}${objKey}: (${param}) =>`];
|
|
4289
|
+
if (memberLines.length === 0) {
|
|
4290
|
+
lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4291
|
+
return lines;
|
|
4292
|
+
}
|
|
4293
|
+
lines.push(`${pad} __req(() => ${requestExpr}, {`);
|
|
4294
|
+
lines.push(...memberLines);
|
|
4295
|
+
lines.push(`${pad} }),`);
|
|
3620
4296
|
return lines;
|
|
3621
4297
|
}
|
|
3622
4298
|
function renderStreamExpr(req) {
|
|
@@ -3747,6 +4423,26 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3747
4423
|
"}",
|
|
3748
4424
|
""
|
|
3749
4425
|
];
|
|
4426
|
+
function isClientCallable(route) {
|
|
4427
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4428
|
+
}
|
|
4429
|
+
var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
|
|
4430
|
+
var ROUTES_EXPORTS = [
|
|
4431
|
+
// Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
|
|
4432
|
+
{ name: "route", typeOnly: false },
|
|
4433
|
+
{ name: "ROUTES", typeOnly: false },
|
|
4434
|
+
{ name: "RouteName", typeOnly: true },
|
|
4435
|
+
{ name: "ExtractParams", typeOnly: true },
|
|
4436
|
+
{ name: "RouteParams", typeOnly: true }
|
|
4437
|
+
];
|
|
4438
|
+
function finishRoutesImport(lines) {
|
|
4439
|
+
const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
|
|
4440
|
+
const used = ROUTES_EXPORTS.filter(
|
|
4441
|
+
({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
|
|
4442
|
+
).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
|
|
4443
|
+
const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
|
|
4444
|
+
return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
|
|
4445
|
+
}
|
|
3750
4446
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3751
4447
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3752
4448
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3754,7 +4450,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3754
4450
|
const { layer } = resolveApiSlots(extensions);
|
|
3755
4451
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3756
4452
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3757
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4453
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3758
4454
|
const ctx = opts.ctx ?? {
|
|
3759
4455
|
cwd: outDir ?? "",
|
|
3760
4456
|
outDir: outDir ?? "",
|
|
@@ -3806,9 +4502,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3806
4502
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
3807
4503
|
}
|
|
3808
4504
|
lines.push(...extImports);
|
|
3809
|
-
lines.push(
|
|
3810
|
-
"import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
|
|
3811
|
-
);
|
|
4505
|
+
lines.push(ROUTES_IMPORT_PLACEHOLDER);
|
|
3812
4506
|
const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
|
|
3813
4507
|
lines.push(`import type { Fetcher } from '${runtimeImport}';`);
|
|
3814
4508
|
if (serialization === "json" && contracted.length > 0) {
|
|
@@ -3859,7 +4553,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3859
4553
|
lines.push(...statements, "");
|
|
3860
4554
|
}
|
|
3861
4555
|
}
|
|
3862
|
-
return lines
|
|
4556
|
+
return finishRoutesImport(lines);
|
|
3863
4557
|
}
|
|
3864
4558
|
const tree = /* @__PURE__ */ new Map();
|
|
3865
4559
|
for (const r of contracted) {
|
|
@@ -3912,7 +4606,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3912
4606
|
lines.push(...statements, "");
|
|
3913
4607
|
}
|
|
3914
4608
|
}
|
|
3915
|
-
return lines
|
|
4609
|
+
return finishRoutesImport(lines);
|
|
3916
4610
|
}
|
|
3917
4611
|
|
|
3918
4612
|
// src/emit/emit-cache.ts
|
|
@@ -3925,6 +4619,7 @@ async function emitCache(pages, outDir) {
|
|
|
3925
4619
|
const s = await (0, import_promises4.stat)(p.absolutePath);
|
|
3926
4620
|
return {
|
|
3927
4621
|
name: p.name,
|
|
4622
|
+
scope: p.scope ?? "default",
|
|
3928
4623
|
relativePath: p.relativePath,
|
|
3929
4624
|
mtime: s.mtime.toISOString()
|
|
3930
4625
|
};
|
|
@@ -4675,6 +5370,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4675
5370
|
const paths = {};
|
|
4676
5371
|
for (const route of routes) {
|
|
4677
5372
|
if (!route.contract) continue;
|
|
5373
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4678
5374
|
const oaPath = toOpenApiPath(route.path);
|
|
4679
5375
|
const method = route.method.toLowerCase();
|
|
4680
5376
|
let pathItem = paths[oaPath];
|
|
@@ -4718,10 +5414,11 @@ async function emitPages(pages, outDir, _options = {}) {
|
|
|
4718
5414
|
}).join("\n");
|
|
4719
5415
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
4720
5416
|
const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
|
|
5417
|
+
const scopesBlock = buildScopesBlock(pages);
|
|
4721
5418
|
const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
|
|
4722
5419
|
|
|
4723
5420
|
export type InertiaPageName = ${pageNameUnion};
|
|
4724
|
-
${propsHelper}
|
|
5421
|
+
${propsHelper}${scopesBlock}
|
|
4725
5422
|
declare module '@dudousxd/nestjs-inertia' {
|
|
4726
5423
|
interface InertiaPages {
|
|
4727
5424
|
${augBody}
|
|
@@ -4730,6 +5427,31 @@ ${sharedPropsBlock}}
|
|
|
4730
5427
|
`;
|
|
4731
5428
|
await (0, import_promises9.writeFile)((0, import_node_path13.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
4732
5429
|
}
|
|
5430
|
+
function buildScopesBlock(pages) {
|
|
5431
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
5432
|
+
for (const page of pages) {
|
|
5433
|
+
const scope = page.scope ?? "default";
|
|
5434
|
+
const names = byScope.get(scope) ?? [];
|
|
5435
|
+
names.push(JSON.stringify(page.name));
|
|
5436
|
+
byScope.set(scope, names);
|
|
5437
|
+
}
|
|
5438
|
+
if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
|
|
5439
|
+
if (!byScope.has("default")) byScope.set("default", []);
|
|
5440
|
+
const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
|
|
5441
|
+
const body = scopes.map((scope) => {
|
|
5442
|
+
const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
|
|
5443
|
+
const names = byScope.get(scope) ?? [];
|
|
5444
|
+
return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
|
|
5445
|
+
}).join("\n");
|
|
5446
|
+
return `
|
|
5447
|
+
/** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
|
|
5448
|
+
export interface InertiaScopePages {
|
|
5449
|
+
${body}
|
|
5450
|
+
}
|
|
5451
|
+
|
|
5452
|
+
export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
|
|
5453
|
+
`;
|
|
5454
|
+
}
|
|
4733
5455
|
function buildSharedPropsBlock(sharedProps) {
|
|
4734
5456
|
if (!sharedProps) return "";
|
|
4735
5457
|
if (sharedProps.isImportRef) {
|
|
@@ -4937,8 +5659,7 @@ function diffConfigKeyHashes(previous, current) {
|
|
|
4937
5659
|
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
4938
5660
|
}
|
|
4939
5661
|
async function discoverInputFiles(config) {
|
|
4940
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
4941
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
5662
|
+
const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
|
|
4942
5663
|
const cwd = config.codegen.cwd;
|
|
4943
5664
|
const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4944
5665
|
return [...new Set(matched)].sort();
|
|
@@ -5069,12 +5790,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
5069
5790
|
}
|
|
5070
5791
|
if (config.pages) {
|
|
5071
5792
|
const pagesConfig = config.pages;
|
|
5072
|
-
const pages = await
|
|
5073
|
-
glob: pagesConfig.glob,
|
|
5074
|
-
cwd: config.codegen.cwd,
|
|
5075
|
-
propsExport: pagesConfig.propsExport,
|
|
5076
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
5077
|
-
});
|
|
5793
|
+
const pages = await discoverPageScopes(config);
|
|
5078
5794
|
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
5079
5795
|
await emitPages(pages, config.codegen.outDir, {
|
|
5080
5796
|
propsExport: pagesConfig.propsExport,
|
|
@@ -5249,12 +5965,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
5249
5965
|
await runInitialPass();
|
|
5250
5966
|
}
|
|
5251
5967
|
let pagesDebounceTimer;
|
|
5252
|
-
const
|
|
5253
|
-
const pagesWatcher = import_chokidar.default.watch(
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5968
|
+
const pageGlobs = allPageGlobs(config);
|
|
5969
|
+
const pagesWatcher = import_chokidar.default.watch(
|
|
5970
|
+
(pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
|
|
5971
|
+
(glob) => (0, import_node_path18.join)(config.codegen.cwd, glob)
|
|
5972
|
+
),
|
|
5973
|
+
{
|
|
5974
|
+
ignoreInitial: true,
|
|
5975
|
+
persistent: true,
|
|
5976
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
5977
|
+
}
|
|
5978
|
+
);
|
|
5258
5979
|
function schedulePagesRegenerate() {
|
|
5259
5980
|
if (pagesDebounceTimer !== void 0) {
|
|
5260
5981
|
clearTimeout(pagesDebounceTimer);
|
|
@@ -5390,7 +6111,7 @@ function toObjectKey2(name) {
|
|
|
5390
6111
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
5391
6112
|
}
|
|
5392
6113
|
function createChainModuleRenderer(opts) {
|
|
5393
|
-
const { render, recursiveAnnotation } = opts;
|
|
6114
|
+
const { render: render2, recursiveAnnotation } = opts;
|
|
5394
6115
|
return (mod) => {
|
|
5395
6116
|
const ctx = { named: mod.named };
|
|
5396
6117
|
const recursive = mod.recursive ?? /* @__PURE__ */ new Set();
|
|
@@ -5399,7 +6120,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5399
6120
|
const namedTypeAliases = /* @__PURE__ */ new Map();
|
|
5400
6121
|
const namedAnnotations = /* @__PURE__ */ new Map();
|
|
5401
6122
|
for (const [name, node] of mod.named) {
|
|
5402
|
-
namedNestedSchemas.set(name,
|
|
6123
|
+
namedNestedSchemas.set(name, render2(node, ctx));
|
|
5403
6124
|
if (recursive.has(name)) {
|
|
5404
6125
|
const typeName = typeNameFor(name);
|
|
5405
6126
|
namedTypeAliases.set(name, `type ${typeName} = ${renderTsType(node, tctx)}`);
|
|
@@ -5407,7 +6128,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5407
6128
|
}
|
|
5408
6129
|
}
|
|
5409
6130
|
return {
|
|
5410
|
-
schemaText:
|
|
6131
|
+
schemaText: render2(mod.root, ctx),
|
|
5411
6132
|
namedNestedSchemas,
|
|
5412
6133
|
namedTypeAliases,
|
|
5413
6134
|
namedAnnotations,
|
|
@@ -5417,7 +6138,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5417
6138
|
}
|
|
5418
6139
|
|
|
5419
6140
|
// src/index.ts
|
|
5420
|
-
var VERSION = "0.
|
|
6141
|
+
var VERSION = "0.27.0";
|
|
5421
6142
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5422
6143
|
0 && (module.exports = {
|
|
5423
6144
|
CodegenError,
|