@dudousxd/nestjs-codegen 0.25.2 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +2 -2
- package/dist/cli/main.cjs +991 -270
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +997 -274
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-Ce3YnfwS.d.cts → index-Dc4s883u.d.cts} +49 -5
- package/dist/{index-Ce3YnfwS.d.ts → index-Dc4s883u.d.ts} +49 -5
- package/dist/index.cjs +994 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1000 -277
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +1037 -318
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +1040 -319
- package/dist/nest/index.js.map +1 -1
- package/package.json +11 -7
package/dist/cli/main.cjs
CHANGED
|
@@ -117,11 +117,32 @@ function validateUserConfig(userConfig) {
|
|
|
117
117
|
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
|
-
if (userConfig.pages &&
|
|
120
|
+
if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
|
|
121
121
|
throw new ConfigError(
|
|
122
|
-
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
122
|
+
"Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
|
|
123
123
|
);
|
|
124
124
|
}
|
|
125
|
+
for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
|
|
126
|
+
if (scope === "default") {
|
|
127
|
+
throw new ConfigError(
|
|
128
|
+
'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!isGlobInput(scopeConfig?.glob)) {
|
|
132
|
+
throw new ConfigError(
|
|
133
|
+
`Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
|
|
138
|
+
console.warn(
|
|
139
|
+
"[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)."
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function isGlobInput(value) {
|
|
144
|
+
if (typeof value === "string") return true;
|
|
145
|
+
return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
|
|
125
146
|
}
|
|
126
147
|
function applyDefaults(userConfig, cwd) {
|
|
127
148
|
validateUserConfig(userConfig);
|
|
@@ -149,7 +170,17 @@ function applyDefaults(userConfig, cwd) {
|
|
|
149
170
|
pages: userConfig.pages ? {
|
|
150
171
|
glob: userConfig.pages.glob,
|
|
151
172
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
152
|
-
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
|
|
173
|
+
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
|
|
174
|
+
scopes: Object.fromEntries(
|
|
175
|
+
Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
|
|
176
|
+
scope,
|
|
177
|
+
{
|
|
178
|
+
glob: scopeConfig.glob,
|
|
179
|
+
prefix: scopeConfig.prefix ?? `${scope}/`,
|
|
180
|
+
componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
|
|
181
|
+
}
|
|
182
|
+
])
|
|
183
|
+
)
|
|
153
184
|
} : null,
|
|
154
185
|
contracts: {
|
|
155
186
|
glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
|
|
@@ -226,23 +257,81 @@ var import_promises2 = require("fs/promises");
|
|
|
226
257
|
var import_node_path2 = require("path");
|
|
227
258
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
228
259
|
var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
|
|
260
|
+
var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
|
|
261
|
+
function globStaticBase(glob) {
|
|
262
|
+
const segments = glob.split("/");
|
|
263
|
+
const staticSegments = [];
|
|
264
|
+
for (const segment of segments.slice(0, -1)) {
|
|
265
|
+
if (MAGIC_SEGMENT_RE.test(segment)) break;
|
|
266
|
+
staticSegments.push(segment);
|
|
267
|
+
}
|
|
268
|
+
return staticSegments.join("/");
|
|
269
|
+
}
|
|
229
270
|
async function discoverPages(opts) {
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStatic);
|
|
271
|
+
const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
|
|
272
|
+
const prefix = opts.prefix ?? "";
|
|
273
|
+
const scope = opts.scope ?? "default";
|
|
274
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
235
275
|
const out = [];
|
|
236
|
-
for (const
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
276
|
+
for (const glob of globs) {
|
|
277
|
+
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();
|
|
278
|
+
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStaticBase(glob));
|
|
279
|
+
for (const file of files) {
|
|
280
|
+
seenFiles.add(file);
|
|
281
|
+
const rel = (0, import_node_path2.relative)(opts.cwd, file);
|
|
282
|
+
const nameRel = (0, import_node_path2.relative)(pagesBase, file).split(import_node_path2.sep).join("/");
|
|
283
|
+
const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
|
|
284
|
+
const source = await (0, import_promises2.readFile)(file, "utf8");
|
|
285
|
+
const propsSource = extractPropsSource(source, opts.propsExport);
|
|
286
|
+
out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
assertUniqueNames(out);
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
async function discoverPageScopes(config) {
|
|
293
|
+
const pages = config.pages;
|
|
294
|
+
if (!pages) return [];
|
|
295
|
+
const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
|
|
296
|
+
const out = await discoverPages({
|
|
297
|
+
...common,
|
|
298
|
+
glob: pages.glob,
|
|
299
|
+
componentNameStrategy: pages.componentNameStrategy
|
|
300
|
+
});
|
|
301
|
+
for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
|
|
302
|
+
out.push(
|
|
303
|
+
...await discoverPages({
|
|
304
|
+
...common,
|
|
305
|
+
glob: scopeConfig.glob,
|
|
306
|
+
componentNameStrategy: scopeConfig.componentNameStrategy,
|
|
307
|
+
prefix: scopeConfig.prefix,
|
|
308
|
+
scope
|
|
309
|
+
})
|
|
310
|
+
);
|
|
243
311
|
}
|
|
312
|
+
assertUniqueNames(out);
|
|
244
313
|
return out;
|
|
245
314
|
}
|
|
315
|
+
function allPageGlobs(config) {
|
|
316
|
+
if (!config.pages) return [];
|
|
317
|
+
const asList = (glob) => typeof glob === "string" ? [glob] : glob;
|
|
318
|
+
return [
|
|
319
|
+
...asList(config.pages.glob),
|
|
320
|
+
...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
function assertUniqueNames(pages) {
|
|
324
|
+
const byName = /* @__PURE__ */ new Map();
|
|
325
|
+
for (const page of pages) {
|
|
326
|
+
const previous = byName.get(page.name);
|
|
327
|
+
if (previous) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`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.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
byName.set(page.name, page);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
246
335
|
function computeName(rel, strat) {
|
|
247
336
|
if (typeof strat === "function") return strat(rel);
|
|
248
337
|
const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
|
|
@@ -552,16 +641,16 @@ var import_node_path7 = require("path");
|
|
|
552
641
|
|
|
553
642
|
// src/extension/registry.ts
|
|
554
643
|
var import_node_path6 = require("path");
|
|
555
|
-
var
|
|
644
|
+
var import_ts_morph13 = require("ts-morph");
|
|
556
645
|
|
|
557
646
|
// src/discovery/contracts-fast.ts
|
|
558
647
|
var import_node_fs = require("fs");
|
|
559
648
|
var import_node_path5 = require("path");
|
|
560
649
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
561
|
-
var
|
|
650
|
+
var import_ts_morph12 = require("ts-morph");
|
|
562
651
|
|
|
563
652
|
// src/discovery/dto-type-resolver.ts
|
|
564
|
-
var
|
|
653
|
+
var import_ts_morph11 = require("ts-morph");
|
|
565
654
|
|
|
566
655
|
// src/discovery/dto-to-ir.ts
|
|
567
656
|
var import_ts_morph3 = require("ts-morph");
|
|
@@ -2089,6 +2178,672 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
|
|
|
2089
2178
|
return fields;
|
|
2090
2179
|
}
|
|
2091
2180
|
|
|
2181
|
+
// src/discovery/pipe-schema.ts
|
|
2182
|
+
var import_ts_morph10 = require("ts-morph");
|
|
2183
|
+
|
|
2184
|
+
// src/discovery/schema-type-inference.ts
|
|
2185
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2186
|
+
|
|
2187
|
+
// src/discovery/schema-type-program.ts
|
|
2188
|
+
var import_ts_morph7 = require("ts-morph");
|
|
2189
|
+
var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
|
|
2190
|
+
var SchemaTypeProgram = class {
|
|
2191
|
+
constructor(discovery) {
|
|
2192
|
+
this.discovery = discovery;
|
|
2193
|
+
const base = discovery.getCompilerOptions();
|
|
2194
|
+
this.options = {
|
|
2195
|
+
...base,
|
|
2196
|
+
// A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
|
|
2197
|
+
// `Promise` for the schema libraries' declarations to lean on.
|
|
2198
|
+
target: base.target ?? import_ts_morph7.ts.ScriptTarget.ES2022,
|
|
2199
|
+
// With neither set, resolve the way a bundler does: package `exports`
|
|
2200
|
+
// (`zod/v4`) and extensionless or `.js` relative imports all resolve.
|
|
2201
|
+
...base.module === void 0 && base.moduleResolution === void 0 ? { module: import_ts_morph7.ts.ModuleKind.ESNext, moduleResolution: import_ts_morph7.ts.ModuleResolutionKind.Bundler } : {},
|
|
2202
|
+
// Without it `.nullable()` and `.optional()` would erase to the bare type.
|
|
2203
|
+
strictNullChecks: true,
|
|
2204
|
+
// No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
|
|
2205
|
+
// need them, and they are a hundred-odd files of program to build.
|
|
2206
|
+
types: [],
|
|
2207
|
+
noEmit: true
|
|
2208
|
+
};
|
|
2209
|
+
this.project = new import_ts_morph7.Project({
|
|
2210
|
+
skipAddingFilesFromTsConfig: true,
|
|
2211
|
+
compilerOptions: this.options
|
|
2212
|
+
});
|
|
2213
|
+
for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
|
|
2214
|
+
this.flush();
|
|
2215
|
+
}
|
|
2216
|
+
project;
|
|
2217
|
+
options;
|
|
2218
|
+
/** Parses a file that is not in discovery's Project, without adding it there. */
|
|
2219
|
+
scratch = new import_ts_morph7.Project({
|
|
2220
|
+
skipAddingFilesFromTsConfig: true,
|
|
2221
|
+
skipLoadingLibFiles: true,
|
|
2222
|
+
skipFileDependencyResolution: true
|
|
2223
|
+
});
|
|
2224
|
+
needs = /* @__PURE__ */ new Map();
|
|
2225
|
+
/** Pruned text per file, as last written into {@link project}. */
|
|
2226
|
+
written = /* @__PURE__ */ new Map();
|
|
2227
|
+
/**
|
|
2228
|
+
* The typed copy of a discovery file, with its schema positions kept. Adding one
|
|
2229
|
+
* that was not there yet (a contract file discovery reached lazily) rebuilds the
|
|
2230
|
+
* program once.
|
|
2231
|
+
*/
|
|
2232
|
+
sourceFileFor(sf) {
|
|
2233
|
+
this.request(sf.getFilePath(), { schema: true });
|
|
2234
|
+
this.flush();
|
|
2235
|
+
return this.project.getSourceFile(sf.getFilePath());
|
|
2236
|
+
}
|
|
2237
|
+
// -------------------------------------------------------------------------
|
|
2238
|
+
queue = [];
|
|
2239
|
+
request(path, want) {
|
|
2240
|
+
let needs = this.needs.get(path);
|
|
2241
|
+
const isNew = !needs;
|
|
2242
|
+
needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
|
|
2243
|
+
let grew = isNew;
|
|
2244
|
+
if (want.schema && !needs.schema) {
|
|
2245
|
+
needs.schema = true;
|
|
2246
|
+
grew = true;
|
|
2247
|
+
}
|
|
2248
|
+
if (want.all && !needs.all) {
|
|
2249
|
+
needs.all = true;
|
|
2250
|
+
grew = true;
|
|
2251
|
+
}
|
|
2252
|
+
for (const name of want.names ?? []) {
|
|
2253
|
+
if (!needs.names.has(name)) {
|
|
2254
|
+
needs.names.add(name);
|
|
2255
|
+
grew = true;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
this.needs.set(path, needs);
|
|
2259
|
+
if (grew) this.queue.push(path);
|
|
2260
|
+
}
|
|
2261
|
+
/** Run the worklist to a fixpoint, then write every changed file into the program. */
|
|
2262
|
+
flush() {
|
|
2263
|
+
const changed = /* @__PURE__ */ new Map();
|
|
2264
|
+
while (this.queue.length > 0) {
|
|
2265
|
+
const path = this.queue.pop();
|
|
2266
|
+
const original = this.original(path);
|
|
2267
|
+
if (!original) continue;
|
|
2268
|
+
const needs = this.needs.get(path);
|
|
2269
|
+
const text = this.prune(original, needs);
|
|
2270
|
+
if (this.written.get(path) !== text) changed.set(path, text);
|
|
2271
|
+
}
|
|
2272
|
+
for (const [path, text] of changed) {
|
|
2273
|
+
this.project.createSourceFile(path, text, { overwrite: true });
|
|
2274
|
+
this.written.set(path, text);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
/** The file as discovery sees it (its in-memory text), else as it is on disk. */
|
|
2278
|
+
original(path) {
|
|
2279
|
+
return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* `sf`'s text with every import/re-export nothing in `needs` references blanked,
|
|
2283
|
+
* requesting from each kept one's target the names it brings in.
|
|
2284
|
+
*/
|
|
2285
|
+
prune(sf, needs) {
|
|
2286
|
+
const locals = topLevelDeclarations(sf);
|
|
2287
|
+
const roots = [];
|
|
2288
|
+
if (needs.all) roots.push(...sf.getStatements());
|
|
2289
|
+
if (needs.schema) roots.push(...schemaPositions(sf));
|
|
2290
|
+
const reexported = /* @__PURE__ */ new Map();
|
|
2291
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2292
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2293
|
+
if (spec === void 0) {
|
|
2294
|
+
for (const named of exp.getNamedExports()) {
|
|
2295
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2296
|
+
if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
|
|
2297
|
+
}
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
|
|
2301
|
+
for (const named of exp.getNamedExports()) {
|
|
2302
|
+
const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
2303
|
+
const list = reexported.get(exposed) ?? [];
|
|
2304
|
+
list.push({ from: spec, name: named.getNameNode().getText() });
|
|
2305
|
+
reexported.set(exposed, list);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
const unresolvedNames = [];
|
|
2309
|
+
for (const name of needs.names) {
|
|
2310
|
+
const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
|
|
2311
|
+
if (decls.length > 0) roots.push(...decls);
|
|
2312
|
+
else if (!reexported.has(name)) unresolvedNames.push(name);
|
|
2313
|
+
}
|
|
2314
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2315
|
+
const queue = [...roots];
|
|
2316
|
+
const seen = new Set(queue);
|
|
2317
|
+
while (queue.length > 0) {
|
|
2318
|
+
const node = queue.pop();
|
|
2319
|
+
const ids = import_ts_morph7.Node.isIdentifier(node) ? [node] : node.getDescendantsOfKind(import_ts_morph7.ts.SyntaxKind.Identifier);
|
|
2320
|
+
for (const id of ids) {
|
|
2321
|
+
const name = id.getText();
|
|
2322
|
+
if (referenced.has(name)) continue;
|
|
2323
|
+
referenced.add(name);
|
|
2324
|
+
for (const local of locals.get(name) ?? []) {
|
|
2325
|
+
if (seen.has(local)) continue;
|
|
2326
|
+
seen.add(local);
|
|
2327
|
+
queue.push(local);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
const chars = sf.getFullText().split("");
|
|
2332
|
+
const blank = (node) => {
|
|
2333
|
+
for (let i = node.getStart(); i < node.getEnd(); i++) {
|
|
2334
|
+
if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
|
|
2335
|
+
}
|
|
2336
|
+
};
|
|
2337
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
2338
|
+
const target = this.resolve(decl.getModuleSpecifierValue(), sf);
|
|
2339
|
+
const namespace = decl.getNamespaceImport()?.getText();
|
|
2340
|
+
const def = decl.getDefaultImport()?.getText();
|
|
2341
|
+
const named = decl.getNamedImports().map((n) => ({
|
|
2342
|
+
local: (n.getAliasNode() ?? n.getNameNode()).getText(),
|
|
2343
|
+
imported: n.getName()
|
|
2344
|
+
})).filter((n) => referenced.has(n.local));
|
|
2345
|
+
const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
|
|
2346
|
+
const wantsDefault = def !== void 0 && referenced.has(def);
|
|
2347
|
+
if (!wantsNamespace && !wantsDefault && named.length === 0) {
|
|
2348
|
+
blank(decl);
|
|
2349
|
+
continue;
|
|
2350
|
+
}
|
|
2351
|
+
if (target) {
|
|
2352
|
+
this.request(target, {
|
|
2353
|
+
all: wantsNamespace,
|
|
2354
|
+
names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
2359
|
+
const spec = exp.getModuleSpecifierValue();
|
|
2360
|
+
if (spec === void 0) continue;
|
|
2361
|
+
const target = this.resolve(spec, sf);
|
|
2362
|
+
if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
|
|
2363
|
+
if (!needs.all && unresolvedNames.length === 0) {
|
|
2364
|
+
blank(exp);
|
|
2365
|
+
} else if (target) {
|
|
2366
|
+
this.request(target, { all: needs.all, names: unresolvedNames });
|
|
2367
|
+
}
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
const wanted = exp.getNamedExports().filter(
|
|
2371
|
+
(n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
|
|
2372
|
+
).map((n) => n.getNameNode().getText());
|
|
2373
|
+
if (wanted.length === 0) blank(exp);
|
|
2374
|
+
else if (target) this.request(target, { names: wanted });
|
|
2375
|
+
}
|
|
2376
|
+
return chars.join("");
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* The project source file a module specifier resolves to, or undefined for a
|
|
2380
|
+
* package (read unpruned by the program itself) or an unresolvable one.
|
|
2381
|
+
*/
|
|
2382
|
+
resolve(specifier, from) {
|
|
2383
|
+
const resolved = import_ts_morph7.ts.resolveModuleName(
|
|
2384
|
+
specifier,
|
|
2385
|
+
from.getFilePath(),
|
|
2386
|
+
this.options,
|
|
2387
|
+
import_ts_morph7.ts.sys
|
|
2388
|
+
).resolvedModule;
|
|
2389
|
+
if (!resolved || resolved.isExternalLibraryImport) return void 0;
|
|
2390
|
+
const file = resolved.resolvedFileName;
|
|
2391
|
+
if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
|
|
2392
|
+
return file;
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
function schemaPositions(sf) {
|
|
2396
|
+
const roots = [];
|
|
2397
|
+
sf.forEachDescendant((node) => {
|
|
2398
|
+
if (import_ts_morph7.Node.isDecorator(node)) {
|
|
2399
|
+
const name = node.getName();
|
|
2400
|
+
if (name === "ApplyContract") {
|
|
2401
|
+
roots.push(...node.getArguments());
|
|
2402
|
+
} else if (PARAM_DECORATORS.has(name)) {
|
|
2403
|
+
for (const arg of node.getArguments()) {
|
|
2404
|
+
if (import_ts_morph7.Node.isNewExpression(arg) || import_ts_morph7.Node.isCallExpression(arg)) {
|
|
2405
|
+
roots.push(...arg.getArguments());
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
} else if (import_ts_morph7.Node.isCallExpression(node)) {
|
|
2410
|
+
const callee = node.getExpression();
|
|
2411
|
+
const name = import_ts_morph7.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph7.Node.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
|
|
2412
|
+
if (name === "defineContract") roots.push(...node.getArguments());
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
return roots;
|
|
2416
|
+
}
|
|
2417
|
+
function topLevelDeclarations(sf) {
|
|
2418
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2419
|
+
const add = (name, node) => {
|
|
2420
|
+
if (!name) return;
|
|
2421
|
+
const list = byName.get(name);
|
|
2422
|
+
if (list) list.push(node);
|
|
2423
|
+
else byName.set(name, [node]);
|
|
2424
|
+
};
|
|
2425
|
+
for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
|
|
2426
|
+
for (const decl of sf.getFunctions()) add(decl.getName(), decl);
|
|
2427
|
+
for (const decl of sf.getClasses()) add(decl.getName(), decl);
|
|
2428
|
+
for (const decl of sf.getEnums()) add(decl.getName(), decl);
|
|
2429
|
+
for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
|
|
2430
|
+
for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
|
|
2431
|
+
for (const decl of sf.getModules()) add(decl.getName(), decl);
|
|
2432
|
+
return byName;
|
|
2433
|
+
}
|
|
2434
|
+
function defaultExport(sf) {
|
|
2435
|
+
const nodes = [];
|
|
2436
|
+
for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
|
|
2437
|
+
for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
|
|
2438
|
+
for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
|
|
2439
|
+
return nodes;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
// src/discovery/schema-type-inference.ts
|
|
2443
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
2444
|
+
function programFor(discovery) {
|
|
2445
|
+
let program = programs.get(discovery);
|
|
2446
|
+
if (!program) {
|
|
2447
|
+
program = new SchemaTypeProgram(discovery);
|
|
2448
|
+
programs.set(discovery, program);
|
|
2449
|
+
}
|
|
2450
|
+
return program;
|
|
2451
|
+
}
|
|
2452
|
+
function clearSchemaTypeProject(discovery) {
|
|
2453
|
+
programs.delete(discovery);
|
|
2454
|
+
}
|
|
2455
|
+
function counterpart(typedSf, node) {
|
|
2456
|
+
const start = node.getStart();
|
|
2457
|
+
const end = node.getEnd();
|
|
2458
|
+
const kind = node.getKind();
|
|
2459
|
+
let candidate = typedSf.getDescendantAtPos(start);
|
|
2460
|
+
while (candidate) {
|
|
2461
|
+
if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
|
|
2462
|
+
return candidate;
|
|
2463
|
+
}
|
|
2464
|
+
if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
|
|
2465
|
+
candidate = candidate.getParent();
|
|
2466
|
+
}
|
|
2467
|
+
return void 0;
|
|
2468
|
+
}
|
|
2469
|
+
function propType(type, name, at) {
|
|
2470
|
+
return type.getProperty(name)?.getTypeAtLocation(at);
|
|
2471
|
+
}
|
|
2472
|
+
function isAnyOrUnknown(type) {
|
|
2473
|
+
return type.isAny() || type.isUnknown();
|
|
2474
|
+
}
|
|
2475
|
+
function inferSchema(expr, discovery) {
|
|
2476
|
+
try {
|
|
2477
|
+
const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
|
|
2478
|
+
const at = typedSf && counterpart(typedSf, expr);
|
|
2479
|
+
if (!at) return { kind: "unresolved" };
|
|
2480
|
+
const schemaType = at.getType();
|
|
2481
|
+
if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
|
|
2482
|
+
const standard = propType(schemaType, "~standard", at);
|
|
2483
|
+
const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
|
|
2484
|
+
const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
|
|
2485
|
+
const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
|
|
2486
|
+
if (stdInput && stdOutput) {
|
|
2487
|
+
return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
|
|
2488
|
+
}
|
|
2489
|
+
const zodInput = propType(schemaType, "_input", at);
|
|
2490
|
+
const zodOutput = propType(schemaType, "_output", at);
|
|
2491
|
+
if (zodInput && zodOutput) {
|
|
2492
|
+
return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
|
|
2493
|
+
}
|
|
2494
|
+
return { kind: "not-schema" };
|
|
2495
|
+
} catch (err) {
|
|
2496
|
+
dbg("schema inference failed:", err);
|
|
2497
|
+
return { kind: "unresolved" };
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
2501
|
+
"Date",
|
|
2502
|
+
"File",
|
|
2503
|
+
"Blob",
|
|
2504
|
+
"FormData",
|
|
2505
|
+
"URL",
|
|
2506
|
+
"RegExp",
|
|
2507
|
+
"Map",
|
|
2508
|
+
"Set",
|
|
2509
|
+
"ArrayBuffer",
|
|
2510
|
+
"Uint8Array"
|
|
2511
|
+
]);
|
|
2512
|
+
var MAX_DEPTH = 12;
|
|
2513
|
+
function objectKey(name) {
|
|
2514
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2515
|
+
}
|
|
2516
|
+
function isOptionalSymbol(sym) {
|
|
2517
|
+
return (sym.getFlags() & import_ts_morph8.ts.SymbolFlags.Optional) !== 0;
|
|
2518
|
+
}
|
|
2519
|
+
function isSymbolKeyed(sym) {
|
|
2520
|
+
return sym.getName().startsWith("__@");
|
|
2521
|
+
}
|
|
2522
|
+
function orderedProperties(type) {
|
|
2523
|
+
const position = (sym) => {
|
|
2524
|
+
const decl = sym.getDeclarations()[0];
|
|
2525
|
+
return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
|
|
2526
|
+
};
|
|
2527
|
+
return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
|
|
2528
|
+
if (a.at && b.at) {
|
|
2529
|
+
if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
|
|
2530
|
+
if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
|
|
2531
|
+
} else if (a.at || b.at) {
|
|
2532
|
+
return a.at ? -1 : 1;
|
|
2533
|
+
}
|
|
2534
|
+
return a.index - b.index;
|
|
2535
|
+
}).map((entry) => entry.sym);
|
|
2536
|
+
}
|
|
2537
|
+
function isFromDeclarationFile(sym) {
|
|
2538
|
+
const decl = sym?.getDeclarations()[0];
|
|
2539
|
+
return !!decl && decl.getSourceFile().isDeclarationFile();
|
|
2540
|
+
}
|
|
2541
|
+
function render(type, state, paired) {
|
|
2542
|
+
if (state.depth > MAX_DEPTH) return "unknown";
|
|
2543
|
+
if (isAnyOrUnknown(type)) {
|
|
2544
|
+
return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
|
|
2545
|
+
}
|
|
2546
|
+
if (type.isNever()) return "never";
|
|
2547
|
+
if (type.isString() || type.isTemplateLiteral()) return "string";
|
|
2548
|
+
if (type.isNumber()) return "number";
|
|
2549
|
+
if (type.isBoolean()) return "boolean";
|
|
2550
|
+
if (type.isBigInt()) return "bigint";
|
|
2551
|
+
if (type.isNull()) return "null";
|
|
2552
|
+
if (type.isUndefined() || type.isVoid()) return "undefined";
|
|
2553
|
+
if (type.isBooleanLiteral()) return type.getText();
|
|
2554
|
+
if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
|
|
2555
|
+
const value = type.getLiteralValue();
|
|
2556
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2557
|
+
}
|
|
2558
|
+
if (type.isBigIntLiteral()) return type.getText();
|
|
2559
|
+
if (type.isTypeParameter()) return "unknown";
|
|
2560
|
+
if (type.isUnion()) return renderUnion(type, state, paired);
|
|
2561
|
+
if (type.isIntersection()) return renderIntersection(type, state);
|
|
2562
|
+
const next = { ...state, depth: state.depth + 1 };
|
|
2563
|
+
if (type.isArray() || type.isReadonlyArray()) {
|
|
2564
|
+
const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
|
|
2565
|
+
const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
|
|
2566
|
+
return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
|
|
2567
|
+
}
|
|
2568
|
+
if (type.isTuple()) {
|
|
2569
|
+
const elements = type.getTupleElements();
|
|
2570
|
+
const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
|
|
2571
|
+
return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
|
|
2572
|
+
}
|
|
2573
|
+
if (type.isObject()) return renderObject(type, next, paired);
|
|
2574
|
+
return "unknown";
|
|
2575
|
+
}
|
|
2576
|
+
function renderUnion(type, state, paired) {
|
|
2577
|
+
const members = type.getUnionTypes();
|
|
2578
|
+
const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
|
|
2579
|
+
const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
|
|
2580
|
+
const pairedNonNull = paired?.getNonNullableType();
|
|
2581
|
+
const arms = [];
|
|
2582
|
+
let sawBoolean = false;
|
|
2583
|
+
let hasNull = false;
|
|
2584
|
+
let hasUndefined = false;
|
|
2585
|
+
for (const member of members) {
|
|
2586
|
+
if (member.isNull()) {
|
|
2587
|
+
hasNull = true;
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
if (member.isUndefined()) {
|
|
2591
|
+
hasUndefined = true;
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
if (member.isBooleanLiteral() && hasTrue && hasFalse) {
|
|
2595
|
+
if (!sawBoolean) arms.push("boolean");
|
|
2596
|
+
sawBoolean = true;
|
|
2597
|
+
continue;
|
|
2598
|
+
}
|
|
2599
|
+
arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
|
|
2600
|
+
}
|
|
2601
|
+
if (hasNull) arms.push("null");
|
|
2602
|
+
if (hasUndefined) arms.push("undefined");
|
|
2603
|
+
const unique = [...new Set(arms)];
|
|
2604
|
+
if (unique.includes("unknown")) return "unknown";
|
|
2605
|
+
return unique.join(" | ");
|
|
2606
|
+
}
|
|
2607
|
+
function renderIntersection(type, state) {
|
|
2608
|
+
const members = type.getIntersectionTypes();
|
|
2609
|
+
const primitive = members.find((m) => !m.isObject());
|
|
2610
|
+
if (primitive) return render(primitive, state);
|
|
2611
|
+
return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
|
|
2612
|
+
}
|
|
2613
|
+
function renderObject(type, state, paired) {
|
|
2614
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
2615
|
+
const name = symbol?.getName();
|
|
2616
|
+
if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
|
|
2617
|
+
const args = type.getTypeArguments();
|
|
2618
|
+
return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
|
|
2619
|
+
}
|
|
2620
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
|
|
2621
|
+
return "unknown";
|
|
2622
|
+
}
|
|
2623
|
+
if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
|
|
2624
|
+
if (state.stack.has(type.compilerType)) return "unknown";
|
|
2625
|
+
const stack = new Set(state.stack).add(type.compilerType);
|
|
2626
|
+
const inner = { ...state, stack };
|
|
2627
|
+
const fields = [];
|
|
2628
|
+
for (const prop of orderedProperties(type)) {
|
|
2629
|
+
if (isSymbolKeyed(prop)) continue;
|
|
2630
|
+
const propName = prop.getName();
|
|
2631
|
+
const propT = prop.getTypeAtLocation(state.at);
|
|
2632
|
+
if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
|
|
2633
|
+
const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
|
|
2634
|
+
const optional = isOptionalSymbol(prop) ? "?" : "";
|
|
2635
|
+
fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
|
|
2636
|
+
}
|
|
2637
|
+
const stringIndex = type.getStringIndexType();
|
|
2638
|
+
if (stringIndex) {
|
|
2639
|
+
const pairedIndex = paired?.getStringIndexType();
|
|
2640
|
+
const value = render(stringIndex, inner, pairedIndex);
|
|
2641
|
+
if (fields.length === 0) return `Record<string, ${value}>`;
|
|
2642
|
+
fields.push(`[key: string]: ${value}`);
|
|
2643
|
+
}
|
|
2644
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
2645
|
+
}
|
|
2646
|
+
function stateAt(at) {
|
|
2647
|
+
return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
|
|
2648
|
+
}
|
|
2649
|
+
function requestTypeOf(types) {
|
|
2650
|
+
return render(types.input, stateAt(types.at), types.output);
|
|
2651
|
+
}
|
|
2652
|
+
function responseTypeOf(types) {
|
|
2653
|
+
return render(types.output, stateAt(types.at));
|
|
2654
|
+
}
|
|
2655
|
+
function requestFieldsOf(types) {
|
|
2656
|
+
const { input, output, at } = types;
|
|
2657
|
+
const objectInput = input.getNonNullableType();
|
|
2658
|
+
if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
|
|
2659
|
+
const state = stateAt(at);
|
|
2660
|
+
return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
|
|
2661
|
+
name: prop.getName(),
|
|
2662
|
+
type: render(
|
|
2663
|
+
prop.getTypeAtLocation(at),
|
|
2664
|
+
state,
|
|
2665
|
+
output.getProperty(prop.getName())?.getTypeAtLocation(at)
|
|
2666
|
+
),
|
|
2667
|
+
optional: isOptionalSymbol(prop)
|
|
2668
|
+
}));
|
|
2669
|
+
}
|
|
2670
|
+
function acceptsUndefined(types) {
|
|
2671
|
+
const { input, output } = types;
|
|
2672
|
+
const accepted = isAnyOrUnknown(input) ? output : input;
|
|
2673
|
+
if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
|
|
2674
|
+
return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2678
|
+
var import_ts_morph9 = require("ts-morph");
|
|
2679
|
+
function zodAstToTs(node) {
|
|
2680
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
|
|
2681
|
+
const expr = node.getExpression();
|
|
2682
|
+
if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
|
|
2683
|
+
const methodName = expr.getName();
|
|
2684
|
+
const receiver = expr.getExpression();
|
|
2685
|
+
if (methodName === "optional") {
|
|
2686
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2687
|
+
}
|
|
2688
|
+
if (methodName === "nullable") {
|
|
2689
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2690
|
+
}
|
|
2691
|
+
const args = node.getArguments();
|
|
2692
|
+
switch (methodName) {
|
|
2693
|
+
case "string":
|
|
2694
|
+
return "string";
|
|
2695
|
+
case "number":
|
|
2696
|
+
return "number";
|
|
2697
|
+
case "boolean":
|
|
2698
|
+
return "boolean";
|
|
2699
|
+
case "unknown":
|
|
2700
|
+
return "unknown";
|
|
2701
|
+
case "any":
|
|
2702
|
+
return "unknown";
|
|
2703
|
+
case "literal": {
|
|
2704
|
+
const lit = args[0];
|
|
2705
|
+
if (!lit) return "unknown";
|
|
2706
|
+
if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2707
|
+
if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2708
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
|
|
2709
|
+
if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
|
|
2710
|
+
return "unknown";
|
|
2711
|
+
}
|
|
2712
|
+
case "enum": {
|
|
2713
|
+
const arrArg = args[0];
|
|
2714
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2715
|
+
const members = arrArg.getElements().map(
|
|
2716
|
+
(el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2717
|
+
);
|
|
2718
|
+
return members.join(" | ");
|
|
2719
|
+
}
|
|
2720
|
+
case "array": {
|
|
2721
|
+
const inner = args[0];
|
|
2722
|
+
if (!inner) return "unknown";
|
|
2723
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2724
|
+
}
|
|
2725
|
+
case "object": {
|
|
2726
|
+
const objArg = args[0];
|
|
2727
|
+
if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2728
|
+
const lines = [];
|
|
2729
|
+
for (const prop of objArg.getProperties()) {
|
|
2730
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
2731
|
+
const key = prop.getName();
|
|
2732
|
+
const valNode = prop.getInitializer();
|
|
2733
|
+
if (!valNode) continue;
|
|
2734
|
+
const tsType = zodAstToTs(valNode);
|
|
2735
|
+
const isOpt = isOptionalChain(valNode);
|
|
2736
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2737
|
+
}
|
|
2738
|
+
return `{ ${lines.join("; ")} }`;
|
|
2739
|
+
}
|
|
2740
|
+
case "union": {
|
|
2741
|
+
const arrArg = args[0];
|
|
2742
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2743
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2744
|
+
}
|
|
2745
|
+
case "record": {
|
|
2746
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2747
|
+
if (!valArg) return "unknown";
|
|
2748
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2749
|
+
}
|
|
2750
|
+
case "tuple": {
|
|
2751
|
+
const arrArg = args[0];
|
|
2752
|
+
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2753
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2754
|
+
}
|
|
2755
|
+
default:
|
|
2756
|
+
return "unknown";
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
return "unknown";
|
|
2760
|
+
}
|
|
2761
|
+
function isOptionalChain(node) {
|
|
2762
|
+
if (!import_ts_morph9.Node.isCallExpression(node)) return false;
|
|
2763
|
+
const expr = node.getExpression();
|
|
2764
|
+
return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2765
|
+
}
|
|
2766
|
+
function memberType(val, position, project) {
|
|
2767
|
+
if (project) {
|
|
2768
|
+
const inferred = inferSchema(val, project);
|
|
2769
|
+
if (inferred.kind === "schema") {
|
|
2770
|
+
return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
return zodAstToTs(val);
|
|
2774
|
+
}
|
|
2775
|
+
function parseDefineContractCall(callExpr, project) {
|
|
2776
|
+
if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
|
|
2777
|
+
const callee = callExpr.getExpression();
|
|
2778
|
+
const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2779
|
+
if (calleeName !== "defineContract") return null;
|
|
2780
|
+
const args = callExpr.getArguments();
|
|
2781
|
+
const optsArg = args[0];
|
|
2782
|
+
if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2783
|
+
let query = null;
|
|
2784
|
+
let body = null;
|
|
2785
|
+
let response = "unknown";
|
|
2786
|
+
let error = null;
|
|
2787
|
+
let bodyZodText = null;
|
|
2788
|
+
let queryZodText = null;
|
|
2789
|
+
for (const prop of optsArg.getProperties()) {
|
|
2790
|
+
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
2791
|
+
const propName = prop.getName();
|
|
2792
|
+
const val = prop.getInitializer();
|
|
2793
|
+
if (!val) continue;
|
|
2794
|
+
if (propName === "query") {
|
|
2795
|
+
query = memberType(val, "request", project);
|
|
2796
|
+
queryZodText = val.getText();
|
|
2797
|
+
} else if (propName === "body") {
|
|
2798
|
+
body = memberType(val, "request", project);
|
|
2799
|
+
bodyZodText = val.getText();
|
|
2800
|
+
} else if (propName === "response") {
|
|
2801
|
+
response = memberType(val, "response", project);
|
|
2802
|
+
} else if (propName === "error") {
|
|
2803
|
+
error = memberType(val, "response", project);
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// src/discovery/pipe-schema.ts
|
|
2810
|
+
function pipeSchemaOf(decorator, project) {
|
|
2811
|
+
for (const pipe of decorator.getArguments()) {
|
|
2812
|
+
if (!import_ts_morph10.Node.isNewExpression(pipe) && !import_ts_morph10.Node.isCallExpression(pipe)) continue;
|
|
2813
|
+
for (const arg of pipe.getArguments()) {
|
|
2814
|
+
if (!couldBeSchema(arg)) continue;
|
|
2815
|
+
const inferred = inferSchema(arg, project);
|
|
2816
|
+
if (inferred.kind === "schema") {
|
|
2817
|
+
return {
|
|
2818
|
+
type: requestTypeOf(inferred.types),
|
|
2819
|
+
optional: acceptsUndefined(inferred.types),
|
|
2820
|
+
fields: requestFieldsOf(inferred.types)
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
if (inferred.kind === "unresolved") {
|
|
2824
|
+
const fallback = syntacticZodType(arg, project);
|
|
2825
|
+
if (fallback) {
|
|
2826
|
+
return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
2833
|
+
function couldBeSchema(arg) {
|
|
2834
|
+
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);
|
|
2835
|
+
}
|
|
2836
|
+
function syntacticZodType(arg, project) {
|
|
2837
|
+
let expr = arg;
|
|
2838
|
+
if (import_ts_morph10.Node.isIdentifier(arg)) {
|
|
2839
|
+
const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
|
|
2840
|
+
expr = resolved?.decl.getInitializer();
|
|
2841
|
+
}
|
|
2842
|
+
if (!expr || !import_ts_morph10.Node.isCallExpression(expr)) return null;
|
|
2843
|
+
const type = zodAstToTs(expr);
|
|
2844
|
+
return type === "unknown" ? null : type;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2092
2847
|
// src/discovery/dto-type-resolver.ts
|
|
2093
2848
|
var WRAPPER_TYPES = {
|
|
2094
2849
|
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
@@ -2120,22 +2875,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2120
2875
|
]);
|
|
2121
2876
|
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2122
2877
|
if (depth <= 0) return "unknown";
|
|
2123
|
-
if (
|
|
2878
|
+
if (import_ts_morph11.Node.isArrayTypeNode(typeNode)) {
|
|
2124
2879
|
const elementType = typeNode.getElementTypeNode();
|
|
2125
2880
|
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2126
2881
|
}
|
|
2127
|
-
if (
|
|
2882
|
+
if (import_ts_morph11.Node.isUnionTypeNode(typeNode)) {
|
|
2128
2883
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2129
2884
|
}
|
|
2130
|
-
if (
|
|
2885
|
+
if (import_ts_morph11.Node.isIntersectionTypeNode(typeNode)) {
|
|
2131
2886
|
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2132
2887
|
}
|
|
2133
|
-
if (
|
|
2888
|
+
if (import_ts_morph11.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2134
2889
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2135
2890
|
}
|
|
2136
|
-
if (
|
|
2891
|
+
if (import_ts_morph11.Node.isTypeReference(typeNode)) {
|
|
2137
2892
|
const typeName = typeNode.getTypeName();
|
|
2138
|
-
const name =
|
|
2893
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2139
2894
|
const bound = subst.get(name);
|
|
2140
2895
|
if (bound !== void 0) return bound;
|
|
2141
2896
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
@@ -2158,13 +2913,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2158
2913
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2159
2914
|
return "unknown";
|
|
2160
2915
|
}
|
|
2161
|
-
if (
|
|
2916
|
+
if (import_ts_morph11.Node.isTypeLiteral(typeNode)) {
|
|
2162
2917
|
const members = [];
|
|
2163
2918
|
for (const member of typeNode.getMembers()) {
|
|
2164
|
-
if (
|
|
2919
|
+
if (import_ts_morph11.Node.isPropertySignature(member)) {
|
|
2165
2920
|
const memberTypeNode = member.getTypeNode();
|
|
2166
|
-
const
|
|
2167
|
-
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${
|
|
2921
|
+
const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
|
|
2922
|
+
members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
|
|
2168
2923
|
} else {
|
|
2169
2924
|
members.push(member.getText());
|
|
2170
2925
|
}
|
|
@@ -2172,11 +2927,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
|
|
|
2172
2927
|
return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
|
|
2173
2928
|
}
|
|
2174
2929
|
const kind = typeNode.getKind();
|
|
2175
|
-
if (kind ===
|
|
2176
|
-
if (kind ===
|
|
2177
|
-
if (kind ===
|
|
2178
|
-
if (kind ===
|
|
2179
|
-
if (kind ===
|
|
2930
|
+
if (kind === import_ts_morph11.SyntaxKind.StringKeyword) return "string";
|
|
2931
|
+
if (kind === import_ts_morph11.SyntaxKind.NumberKeyword) return "number";
|
|
2932
|
+
if (kind === import_ts_morph11.SyntaxKind.BooleanKeyword) return "boolean";
|
|
2933
|
+
if (kind === import_ts_morph11.SyntaxKind.UnknownKeyword) return "unknown";
|
|
2934
|
+
if (kind === import_ts_morph11.SyntaxKind.AnyKeyword) return "unknown";
|
|
2180
2935
|
return typeNode.getText();
|
|
2181
2936
|
}
|
|
2182
2937
|
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
@@ -2224,20 +2979,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
|
|
|
2224
2979
|
const propName = prop.getName();
|
|
2225
2980
|
const isOptional = prop.hasQuestionToken();
|
|
2226
2981
|
const propTypeNode = prop.getTypeNode();
|
|
2227
|
-
let
|
|
2982
|
+
let propType2 = "unknown";
|
|
2228
2983
|
if (propTypeNode) {
|
|
2229
|
-
|
|
2984
|
+
propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2230
2985
|
}
|
|
2231
|
-
lines.push(`${propName}${isOptional ? "?" : ""}: ${
|
|
2986
|
+
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
|
|
2232
2987
|
}
|
|
2233
2988
|
return `{ ${lines.join("; ")} }`;
|
|
2234
2989
|
}
|
|
2990
|
+
function namedField(decorator) {
|
|
2991
|
+
const first = decorator.getArguments()[0];
|
|
2992
|
+
return first && import_ts_morph11.Node.isStringLiteral(first) ? first.getLiteralValue() : null;
|
|
2993
|
+
}
|
|
2235
2994
|
function extractBodyType(method, sourceFile, project) {
|
|
2236
2995
|
for (const param of method.getParameters()) {
|
|
2237
2996
|
const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
|
|
2238
|
-
if (!bodyDecorator) continue;
|
|
2239
|
-
const
|
|
2240
|
-
if (
|
|
2997
|
+
if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
|
|
2998
|
+
const piped = pipeSchemaOf(bodyDecorator, project);
|
|
2999
|
+
if (piped) return piped.type;
|
|
2241
3000
|
const typeNode = param.getTypeNode();
|
|
2242
3001
|
if (typeNode) {
|
|
2243
3002
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2245,11 +3004,16 @@ function extractBodyType(method, sourceFile, project) {
|
|
|
2245
3004
|
}
|
|
2246
3005
|
return null;
|
|
2247
3006
|
}
|
|
3007
|
+
function hasPipeSchema(param, decoratorName, project) {
|
|
3008
|
+
const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
|
|
3009
|
+
return !!decorator && pipeSchemaOf(decorator, project) !== null;
|
|
3010
|
+
}
|
|
2248
3011
|
function extractQueryType(method, sourceFile, project) {
|
|
2249
3012
|
for (const param of method.getParameters()) {
|
|
2250
3013
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2251
|
-
if (!queryDecorator) continue;
|
|
2252
|
-
|
|
3014
|
+
if (!queryDecorator || namedField(queryDecorator) !== null) continue;
|
|
3015
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3016
|
+
if (piped) return piped.type;
|
|
2253
3017
|
const typeNode = param.getTypeNode();
|
|
2254
3018
|
if (typeNode) {
|
|
2255
3019
|
return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
|
|
@@ -2259,10 +3023,13 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2259
3023
|
for (const param of method.getParameters()) {
|
|
2260
3024
|
const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
|
|
2261
3025
|
if (!queryDecorator) continue;
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
3026
|
+
const queryName = namedField(queryDecorator);
|
|
3027
|
+
if (queryName === null) continue;
|
|
3028
|
+
const piped = pipeSchemaOf(queryDecorator, project);
|
|
3029
|
+
if (piped) {
|
|
3030
|
+
entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
2266
3033
|
const typeNode = param.getTypeNode();
|
|
2267
3034
|
const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
2268
3035
|
entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
|
|
@@ -2272,8 +3039,8 @@ function extractQueryType(method, sourceFile, project) {
|
|
|
2272
3039
|
function isParamOptional(param) {
|
|
2273
3040
|
if (param.hasQuestionToken() || param.hasInitializer()) return true;
|
|
2274
3041
|
const typeNode = param.getTypeNode();
|
|
2275
|
-
if (typeNode &&
|
|
2276
|
-
return typeNode.getTypeNodes().some((t) => t.getKind() ===
|
|
3042
|
+
if (typeNode && import_ts_morph11.Node.isUnionTypeNode(typeNode)) {
|
|
3043
|
+
return typeNode.getTypeNodes().some((t) => t.getKind() === import_ts_morph11.SyntaxKind.UndefinedKeyword);
|
|
2277
3044
|
}
|
|
2278
3045
|
return false;
|
|
2279
3046
|
}
|
|
@@ -2285,7 +3052,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2285
3052
|
const paramArgs = paramDecorator.getArguments();
|
|
2286
3053
|
if (paramArgs.length === 0) continue;
|
|
2287
3054
|
const nameArg = paramArgs[0];
|
|
2288
|
-
if (!
|
|
3055
|
+
if (!import_ts_morph11.Node.isStringLiteral(nameArg)) continue;
|
|
2289
3056
|
const paramName = nameArg.getLiteralValue();
|
|
2290
3057
|
const typeNode = param.getTypeNode();
|
|
2291
3058
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -2293,6 +3060,22 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2293
3060
|
}
|
|
2294
3061
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2295
3062
|
}
|
|
3063
|
+
function extractPipedParamTypes(method, project) {
|
|
3064
|
+
const types = {};
|
|
3065
|
+
for (const param of method.getParameters()) {
|
|
3066
|
+
const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
|
|
3067
|
+
if (!paramDecorator) continue;
|
|
3068
|
+
const piped = pipeSchemaOf(paramDecorator, project);
|
|
3069
|
+
if (!piped) continue;
|
|
3070
|
+
const name = namedField(paramDecorator);
|
|
3071
|
+
if (name !== null) {
|
|
3072
|
+
types[name] = piped.type;
|
|
3073
|
+
} else {
|
|
3074
|
+
for (const field of piped.fields ?? []) types[field.name] = field.type;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
return Object.keys(types).length > 0 ? types : null;
|
|
3078
|
+
}
|
|
2296
3079
|
function extractUploadedFiles(method) {
|
|
2297
3080
|
const FILE = "File | Blob";
|
|
2298
3081
|
const entries = [];
|
|
@@ -2306,28 +3089,28 @@ function extractUploadedFiles(method) {
|
|
|
2306
3089
|
for (const decorator of method.getDecorators()) {
|
|
2307
3090
|
if (decorator.getName() !== "UseInterceptors") continue;
|
|
2308
3091
|
for (const arg of decorator.getArguments()) {
|
|
2309
|
-
if (!
|
|
3092
|
+
if (!import_ts_morph11.Node.isCallExpression(arg)) continue;
|
|
2310
3093
|
const interceptor = arg.getExpression().getText();
|
|
2311
3094
|
const callArgs = arg.getArguments();
|
|
2312
3095
|
const firstArg2 = callArgs[0];
|
|
2313
3096
|
if (interceptor === "FileInterceptor") {
|
|
2314
|
-
if (firstArg2 &&
|
|
3097
|
+
if (firstArg2 && import_ts_morph11.Node.isStringLiteral(firstArg2)) {
|
|
2315
3098
|
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
2316
3099
|
multipart = true;
|
|
2317
3100
|
}
|
|
2318
3101
|
} else if (interceptor === "FilesInterceptor") {
|
|
2319
|
-
if (firstArg2 &&
|
|
3102
|
+
if (firstArg2 && import_ts_morph11.Node.isStringLiteral(firstArg2)) {
|
|
2320
3103
|
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
2321
3104
|
multipart = true;
|
|
2322
3105
|
}
|
|
2323
3106
|
} else if (interceptor === "FileFieldsInterceptor") {
|
|
2324
|
-
if (firstArg2 &&
|
|
3107
|
+
if (firstArg2 && import_ts_morph11.Node.isArrayLiteralExpression(firstArg2)) {
|
|
2325
3108
|
for (const el of firstArg2.getElements()) {
|
|
2326
|
-
if (!
|
|
3109
|
+
if (!import_ts_morph11.Node.isObjectLiteralExpression(el)) continue;
|
|
2327
3110
|
const nameProp = el.getProperty("name");
|
|
2328
|
-
if (nameProp &&
|
|
3111
|
+
if (nameProp && import_ts_morph11.Node.isPropertyAssignment(nameProp)) {
|
|
2329
3112
|
const init = nameProp.getInitializer();
|
|
2330
|
-
if (init &&
|
|
3113
|
+
if (init && import_ts_morph11.Node.isStringLiteral(init)) {
|
|
2331
3114
|
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
2332
3115
|
}
|
|
2333
3116
|
}
|
|
@@ -2347,13 +3130,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2347
3130
|
if (apiResponseDecorator) {
|
|
2348
3131
|
const args = apiResponseDecorator.getArguments();
|
|
2349
3132
|
const optsArg = args[0];
|
|
2350
|
-
if (optsArg &&
|
|
3133
|
+
if (optsArg && import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) {
|
|
2351
3134
|
for (const prop of optsArg.getProperties()) {
|
|
2352
|
-
if (!
|
|
3135
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2353
3136
|
if (prop.getName() !== "type") continue;
|
|
2354
3137
|
const val = prop.getInitializer();
|
|
2355
3138
|
if (!val) continue;
|
|
2356
|
-
if (
|
|
3139
|
+
if (import_ts_morph11.Node.isArrayLiteralExpression(val)) {
|
|
2357
3140
|
const elements = val.getElements();
|
|
2358
3141
|
const firstEl = elements[0];
|
|
2359
3142
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -2374,24 +3157,24 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2374
3157
|
}
|
|
2375
3158
|
function apiResponseStatus(decorator) {
|
|
2376
3159
|
const optsArg = decorator.getArguments()[0];
|
|
2377
|
-
if (!optsArg || !
|
|
3160
|
+
if (!optsArg || !import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2378
3161
|
for (const prop of optsArg.getProperties()) {
|
|
2379
|
-
if (!
|
|
3162
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2380
3163
|
if (prop.getName() !== "status") continue;
|
|
2381
3164
|
const val = prop.getInitializer();
|
|
2382
|
-
if (val &&
|
|
3165
|
+
if (val && import_ts_morph11.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
2383
3166
|
}
|
|
2384
3167
|
return null;
|
|
2385
3168
|
}
|
|
2386
3169
|
function apiResponseTypeNode(decorator) {
|
|
2387
3170
|
const optsArg = decorator.getArguments()[0];
|
|
2388
|
-
if (!optsArg || !
|
|
3171
|
+
if (!optsArg || !import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2389
3172
|
for (const prop of optsArg.getProperties()) {
|
|
2390
|
-
if (!
|
|
3173
|
+
if (!import_ts_morph11.Node.isPropertyAssignment(prop)) continue;
|
|
2391
3174
|
if (prop.getName() !== "type") continue;
|
|
2392
3175
|
const val = prop.getInitializer();
|
|
2393
3176
|
if (!val) return null;
|
|
2394
|
-
if (
|
|
3177
|
+
if (import_ts_morph11.Node.isArrayLiteralExpression(val)) {
|
|
2395
3178
|
const first = val.getElements()[0];
|
|
2396
3179
|
return first ? { node: first, isArray: true } : null;
|
|
2397
3180
|
}
|
|
@@ -2409,7 +3192,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2409
3192
|
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
2410
3193
|
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
2411
3194
|
let ref = null;
|
|
2412
|
-
if (
|
|
3195
|
+
if (import_ts_morph11.Node.isIdentifier(typeInfo.node)) {
|
|
2413
3196
|
const name = typeInfo.node.getText();
|
|
2414
3197
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2415
3198
|
if (localDecl?.isExported()) {
|
|
@@ -2426,7 +3209,7 @@ function extractErrorType(method, sourceFile, project) {
|
|
|
2426
3209
|
return null;
|
|
2427
3210
|
}
|
|
2428
3211
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2429
|
-
if (!
|
|
3212
|
+
if (!import_ts_morph11.Node.isIdentifier(node)) return "unknown";
|
|
2430
3213
|
const name = node.getText();
|
|
2431
3214
|
const resolved = findType(name, sourceFile, project);
|
|
2432
3215
|
if (resolved) {
|
|
@@ -2446,9 +3229,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
|
|
|
2446
3229
|
var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
|
|
2447
3230
|
function detectBinaryResponse(method) {
|
|
2448
3231
|
const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
|
|
2449
|
-
if (!node || !
|
|
3232
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return false;
|
|
2450
3233
|
const typeName = node.getTypeName();
|
|
2451
|
-
const name =
|
|
3234
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2452
3235
|
return BINARY_RESPONSE_TYPES.has(name);
|
|
2453
3236
|
}
|
|
2454
3237
|
function detectStreamElement(method) {
|
|
@@ -2463,9 +3246,9 @@ function detectStreamElement(method) {
|
|
|
2463
3246
|
return null;
|
|
2464
3247
|
}
|
|
2465
3248
|
function streamContainerElement(node) {
|
|
2466
|
-
if (!node || !
|
|
3249
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return null;
|
|
2467
3250
|
const typeName = node.getTypeName();
|
|
2468
|
-
const name =
|
|
3251
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2469
3252
|
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
2470
3253
|
return node.getTypeArguments()[0] ?? null;
|
|
2471
3254
|
}
|
|
@@ -2475,9 +3258,9 @@ function hasAsQueryDecorator(method) {
|
|
|
2475
3258
|
return method.getDecorators().some((d) => d.getName() === "AsQuery");
|
|
2476
3259
|
}
|
|
2477
3260
|
function unwrapNamedContainer(node, names) {
|
|
2478
|
-
if (!node || !
|
|
3261
|
+
if (!node || !import_ts_morph11.Node.isTypeReference(node)) return node;
|
|
2479
3262
|
const typeName = node.getTypeName();
|
|
2480
|
-
const name =
|
|
3263
|
+
const name = import_ts_morph11.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
2481
3264
|
if (names.has(name)) {
|
|
2482
3265
|
return node.getTypeArguments()[0] ?? node;
|
|
2483
3266
|
}
|
|
@@ -2498,19 +3281,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2498
3281
|
body = body ?? bodyType;
|
|
2499
3282
|
}
|
|
2500
3283
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3284
|
+
const paramTypes = extractPipedParamTypes(method, project);
|
|
2501
3285
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
2502
3286
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
2503
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
3287
|
+
if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
|
|
2504
3288
|
return null;
|
|
2505
3289
|
}
|
|
2506
3290
|
let bodyRef = null;
|
|
2507
3291
|
let queryRef = null;
|
|
2508
3292
|
let responseRef = null;
|
|
2509
3293
|
for (const param of method.getParameters()) {
|
|
2510
|
-
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
|
|
3294
|
+
if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
|
|
2511
3295
|
bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2512
3296
|
}
|
|
2513
|
-
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
|
|
3297
|
+
if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
|
|
2514
3298
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2515
3299
|
}
|
|
2516
3300
|
}
|
|
@@ -2523,11 +3307,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2523
3307
|
if (apiResp) {
|
|
2524
3308
|
const args = apiResp.getArguments();
|
|
2525
3309
|
const optsArg = args[0];
|
|
2526
|
-
if (optsArg &&
|
|
3310
|
+
if (optsArg && import_ts_morph11.Node.isObjectLiteralExpression(optsArg)) {
|
|
2527
3311
|
for (const prop of optsArg.getProperties()) {
|
|
2528
|
-
if (
|
|
3312
|
+
if (import_ts_morph11.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
2529
3313
|
const val = prop.getInitializer();
|
|
2530
|
-
if (val &&
|
|
3314
|
+
if (val && import_ts_morph11.Node.isIdentifier(val)) {
|
|
2531
3315
|
const name = val.getText();
|
|
2532
3316
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
2533
3317
|
if (localDecl?.isExported()) {
|
|
@@ -2563,6 +3347,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2563
3347
|
response,
|
|
2564
3348
|
error: errorInfo?.type ?? null,
|
|
2565
3349
|
params: paramsType,
|
|
3350
|
+
paramTypes,
|
|
2566
3351
|
queryRef,
|
|
2567
3352
|
bodyRef,
|
|
2568
3353
|
responseRef,
|
|
@@ -2583,6 +3368,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
|
|
|
2583
3368
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2584
3369
|
for (const param of method.getParameters()) {
|
|
2585
3370
|
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
3371
|
+
if (hasPipeSchema(param, decoratorName, project)) continue;
|
|
2586
3372
|
const typeNode = param.getTypeNode();
|
|
2587
3373
|
if (!typeNode) continue;
|
|
2588
3374
|
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
@@ -2595,129 +3381,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
2595
3381
|
return null;
|
|
2596
3382
|
}
|
|
2597
3383
|
|
|
2598
|
-
// src/discovery/zod-ast-to-ts.ts
|
|
2599
|
-
var import_ts_morph8 = require("ts-morph");
|
|
2600
|
-
function zodAstToTs(node) {
|
|
2601
|
-
if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
|
|
2602
|
-
const expr = node.getExpression();
|
|
2603
|
-
if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
|
|
2604
|
-
const methodName = expr.getName();
|
|
2605
|
-
const receiver = expr.getExpression();
|
|
2606
|
-
if (methodName === "optional") {
|
|
2607
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
2608
|
-
}
|
|
2609
|
-
if (methodName === "nullable") {
|
|
2610
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
2611
|
-
}
|
|
2612
|
-
const args = node.getArguments();
|
|
2613
|
-
switch (methodName) {
|
|
2614
|
-
case "string":
|
|
2615
|
-
return "string";
|
|
2616
|
-
case "number":
|
|
2617
|
-
return "number";
|
|
2618
|
-
case "boolean":
|
|
2619
|
-
return "boolean";
|
|
2620
|
-
case "unknown":
|
|
2621
|
-
return "unknown";
|
|
2622
|
-
case "any":
|
|
2623
|
-
return "unknown";
|
|
2624
|
-
case "literal": {
|
|
2625
|
-
const lit = args[0];
|
|
2626
|
-
if (!lit) return "unknown";
|
|
2627
|
-
if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2628
|
-
if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2629
|
-
if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
|
|
2630
|
-
if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
|
|
2631
|
-
return "unknown";
|
|
2632
|
-
}
|
|
2633
|
-
case "enum": {
|
|
2634
|
-
const arrArg = args[0];
|
|
2635
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2636
|
-
const members = arrArg.getElements().map(
|
|
2637
|
-
(el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2638
|
-
);
|
|
2639
|
-
return members.join(" | ");
|
|
2640
|
-
}
|
|
2641
|
-
case "array": {
|
|
2642
|
-
const inner = args[0];
|
|
2643
|
-
if (!inner) return "unknown";
|
|
2644
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
2645
|
-
}
|
|
2646
|
-
case "object": {
|
|
2647
|
-
const objArg = args[0];
|
|
2648
|
-
if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2649
|
-
const lines = [];
|
|
2650
|
-
for (const prop of objArg.getProperties()) {
|
|
2651
|
-
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2652
|
-
const key = prop.getName();
|
|
2653
|
-
const valNode = prop.getInitializer();
|
|
2654
|
-
if (!valNode) continue;
|
|
2655
|
-
const tsType = zodAstToTs(valNode);
|
|
2656
|
-
const isOpt = isOptionalChain(valNode);
|
|
2657
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2658
|
-
}
|
|
2659
|
-
return `{ ${lines.join("; ")} }`;
|
|
2660
|
-
}
|
|
2661
|
-
case "union": {
|
|
2662
|
-
const arrArg = args[0];
|
|
2663
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2664
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2665
|
-
}
|
|
2666
|
-
case "record": {
|
|
2667
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2668
|
-
if (!valArg) return "unknown";
|
|
2669
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2670
|
-
}
|
|
2671
|
-
case "tuple": {
|
|
2672
|
-
const arrArg = args[0];
|
|
2673
|
-
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2674
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
2675
|
-
}
|
|
2676
|
-
default:
|
|
2677
|
-
return "unknown";
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
return "unknown";
|
|
2681
|
-
}
|
|
2682
|
-
function isOptionalChain(node) {
|
|
2683
|
-
if (!import_ts_morph8.Node.isCallExpression(node)) return false;
|
|
2684
|
-
const expr = node.getExpression();
|
|
2685
|
-
return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
2686
|
-
}
|
|
2687
|
-
function parseDefineContractCall(callExpr) {
|
|
2688
|
-
if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
|
|
2689
|
-
const callee = callExpr.getExpression();
|
|
2690
|
-
const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
2691
|
-
if (calleeName !== "defineContract") return null;
|
|
2692
|
-
const args = callExpr.getArguments();
|
|
2693
|
-
const optsArg = args[0];
|
|
2694
|
-
if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2695
|
-
let query = null;
|
|
2696
|
-
let body = null;
|
|
2697
|
-
let response = "unknown";
|
|
2698
|
-
let error = null;
|
|
2699
|
-
let bodyZodText = null;
|
|
2700
|
-
let queryZodText = null;
|
|
2701
|
-
for (const prop of optsArg.getProperties()) {
|
|
2702
|
-
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2703
|
-
const propName = prop.getName();
|
|
2704
|
-
const val = prop.getInitializer();
|
|
2705
|
-
if (!val) continue;
|
|
2706
|
-
if (propName === "query") {
|
|
2707
|
-
query = zodAstToTs(val);
|
|
2708
|
-
queryZodText = val.getText();
|
|
2709
|
-
} else if (propName === "body") {
|
|
2710
|
-
body = zodAstToTs(val);
|
|
2711
|
-
bodyZodText = val.getText();
|
|
2712
|
-
} else if (propName === "response") {
|
|
2713
|
-
response = zodAstToTs(val);
|
|
2714
|
-
} else if (propName === "error") {
|
|
2715
|
-
error = zodAstToTs(val);
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
return { query, body, response, error, bodyZodText, queryZodText };
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
3384
|
// src/discovery/contracts-fast.ts
|
|
2722
3385
|
async function discoverContractsFast(opts) {
|
|
2723
3386
|
const { cwd, glob, tsconfig } = opts;
|
|
@@ -2730,7 +3393,9 @@ async function discoverContractsFast(opts) {
|
|
|
2730
3393
|
}
|
|
2731
3394
|
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
2732
3395
|
clearMixinTypeProject();
|
|
2733
|
-
|
|
3396
|
+
const routes = extractAllRoutes(project);
|
|
3397
|
+
clearSchemaTypeProject(project);
|
|
3398
|
+
return routes;
|
|
2734
3399
|
}
|
|
2735
3400
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
2736
3401
|
return tsconfig ? (0, import_node_path5.resolve)(tsconfig) : (0, import_node_path5.join)(cwd, "tsconfig.json");
|
|
@@ -2742,25 +3407,25 @@ var NO_TSCONFIG_OPTIONS = {
|
|
|
2742
3407
|
};
|
|
2743
3408
|
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
2744
3409
|
if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
|
|
2745
|
-
const read =
|
|
3410
|
+
const read = import_ts_morph12.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
|
|
2746
3411
|
if (read.error) {
|
|
2747
3412
|
return {
|
|
2748
3413
|
options: null,
|
|
2749
3414
|
files: [tsconfigPath],
|
|
2750
|
-
error:
|
|
3415
|
+
error: import_ts_morph12.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
2751
3416
|
};
|
|
2752
3417
|
}
|
|
2753
3418
|
const host = {
|
|
2754
|
-
useCaseSensitiveFileNames:
|
|
3419
|
+
useCaseSensitiveFileNames: import_ts_morph12.ts.sys.useCaseSensitiveFileNames,
|
|
2755
3420
|
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
2756
3421
|
readDirectory: () => [],
|
|
2757
|
-
fileExists: (path) =>
|
|
3422
|
+
fileExists: (path) => import_ts_morph12.ts.sys.fileExists(path),
|
|
2758
3423
|
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
2759
3424
|
// in a base tsconfig keeps working.
|
|
2760
|
-
readFile: (path) =>
|
|
3425
|
+
readFile: (path) => import_ts_morph12.ts.sys.readFile(path)
|
|
2761
3426
|
};
|
|
2762
|
-
const sourceFile =
|
|
2763
|
-
const parsed =
|
|
3427
|
+
const sourceFile = import_ts_morph12.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph12.ts.sys.readFile(path));
|
|
3428
|
+
const parsed = import_ts_morph12.ts.parseJsonSourceFileConfigFileContent(
|
|
2764
3429
|
sourceFile,
|
|
2765
3430
|
host,
|
|
2766
3431
|
(0, import_node_path5.dirname)(tsconfigPath),
|
|
@@ -2783,7 +3448,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
|
|
|
2783
3448
|
`[nestjs-codegen/fast] Could not load ${tsconfigPath}: ${tsconfig.error} \u2014 continuing WITHOUT its compiler options, so path alias imports (e.g. '@/...') will not resolve and controllers extending an alias-imported factory will contribute NO routes to the generated client.`
|
|
2784
3449
|
);
|
|
2785
3450
|
}
|
|
2786
|
-
return new
|
|
3451
|
+
return new import_ts_morph12.Project({
|
|
2787
3452
|
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
2788
3453
|
skipAddingFilesFromTsConfig: true,
|
|
2789
3454
|
skipLoadingLibFiles: true,
|
|
@@ -2895,15 +3560,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
2895
3560
|
clearTypeResolutionCaches(this.project);
|
|
2896
3561
|
clearEnumCache(this.project);
|
|
2897
3562
|
clearMixinTypeProject();
|
|
3563
|
+
clearSchemaTypeProject(this.project);
|
|
2898
3564
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
2899
3565
|
}
|
|
2900
3566
|
};
|
|
2901
3567
|
function decoratorStringArg(decoratorExpr) {
|
|
2902
3568
|
if (!decoratorExpr) return void 0;
|
|
2903
|
-
if (
|
|
2904
|
-
if (
|
|
3569
|
+
if (import_ts_morph12.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3570
|
+
if (import_ts_morph12.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
2905
3571
|
const first = decoratorExpr.getElements()[0];
|
|
2906
|
-
if (first &&
|
|
3572
|
+
if (first && import_ts_morph12.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
2907
3573
|
}
|
|
2908
3574
|
return void 0;
|
|
2909
3575
|
}
|
|
@@ -3024,9 +3690,9 @@ function extractContractRoute(args) {
|
|
|
3024
3690
|
let contractDef = null;
|
|
3025
3691
|
let bodyZodRef = null;
|
|
3026
3692
|
let queryZodRef = null;
|
|
3027
|
-
if (
|
|
3028
|
-
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3029
|
-
} else if (
|
|
3693
|
+
if (import_ts_morph12.Node.isCallExpression(firstDecoratorArg)) {
|
|
3694
|
+
contractDef = parseDefineContractCall(firstDecoratorArg, project);
|
|
3695
|
+
} else if (import_ts_morph12.Node.isIdentifier(firstDecoratorArg)) {
|
|
3030
3696
|
const identName = firstDecoratorArg.getText();
|
|
3031
3697
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
3032
3698
|
if (!resolvedVar) {
|
|
@@ -3038,7 +3704,7 @@ function extractContractRoute(args) {
|
|
|
3038
3704
|
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3039
3705
|
const initializer = varDecl.getInitializer();
|
|
3040
3706
|
if (!initializer) return null;
|
|
3041
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3707
|
+
contractDef = parseDefineContractCall(initializer, project);
|
|
3042
3708
|
if (contractDef && varDecl.isExported()) {
|
|
3043
3709
|
const filePath = declFile.getFilePath();
|
|
3044
3710
|
if (contractDef.body !== null) {
|
|
@@ -3109,6 +3775,7 @@ function extractDtoRoute(args) {
|
|
|
3109
3775
|
body: dtoContract?.body ?? null,
|
|
3110
3776
|
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
3111
3777
|
error: dtoContract?.error ?? null,
|
|
3778
|
+
paramTypes: dtoContract?.paramTypes ?? null,
|
|
3112
3779
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3113
3780
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3114
3781
|
responseRef: dtoContract?.responseRef ?? null,
|
|
@@ -3231,7 +3898,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
|
3231
3898
|
},
|
|
3232
3899
|
project() {
|
|
3233
3900
|
if (!project) {
|
|
3234
|
-
project = new
|
|
3901
|
+
project = new import_ts_morph13.Project({
|
|
3235
3902
|
skipAddingFilesFromTsConfig: true,
|
|
3236
3903
|
skipLoadingLibFiles: true,
|
|
3237
3904
|
skipFileDependencyResolution: true,
|
|
@@ -3349,10 +4016,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
3349
4016
|
insertIntoTree(branch.children, rest, leaf, fullName);
|
|
3350
4017
|
}
|
|
3351
4018
|
}
|
|
3352
|
-
function buildParamsType(params) {
|
|
4019
|
+
function buildParamsType(params, paramTypes) {
|
|
3353
4020
|
const pathParams = params.filter((p) => p.source === "path");
|
|
3354
4021
|
if (pathParams.length === 0) return "never";
|
|
3355
|
-
|
|
4022
|
+
const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
|
|
4023
|
+
return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
|
|
3356
4024
|
}
|
|
3357
4025
|
function hasPathParams(params) {
|
|
3358
4026
|
return params.some((p) => p.source === "path");
|
|
@@ -3446,7 +4114,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
|
|
|
3446
4114
|
}
|
|
3447
4115
|
const response = buildResponseType(c, outDir, serialization);
|
|
3448
4116
|
const error = buildErrorType(c);
|
|
3449
|
-
const params = buildParamsType(c.params);
|
|
4117
|
+
const params = buildParamsType(c.params, c.contractSource.paramTypes);
|
|
3450
4118
|
const safeMethod = JSON.stringify(method);
|
|
3451
4119
|
const safeUrl = JSON.stringify(c.path);
|
|
3452
4120
|
const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
|
|
@@ -3534,28 +4202,28 @@ function renderFetcherRequest(req, binaryResponse) {
|
|
|
3534
4202
|
}
|
|
3535
4203
|
function emitReqHelper() {
|
|
3536
4204
|
return [
|
|
3537
|
-
"/**
|
|
3538
|
-
"type
|
|
3539
|
-
"
|
|
4205
|
+
"/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
|
|
4206
|
+
"export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
|
|
4207
|
+
"class __Request<R> extends Promise<R> {",
|
|
4208
|
+
" static override get [Symbol.species](): PromiseConstructor {",
|
|
4209
|
+
" return Promise;",
|
|
4210
|
+
" }",
|
|
4211
|
+
" private __run: () => Promise<R>;",
|
|
4212
|
+
" private __p: Promise<R> | undefined;",
|
|
4213
|
+
" constructor(run: () => Promise<R>) {",
|
|
4214
|
+
" super(() => {});",
|
|
4215
|
+
" this.__run = run;",
|
|
4216
|
+
" }",
|
|
4217
|
+
" override then<T1 = R, T2 = never>(",
|
|
3540
4218
|
" onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
|
|
3541
4219
|
" onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
|
|
3542
|
-
" ): Promise<T1 | T2
|
|
3543
|
-
"
|
|
3544
|
-
"
|
|
3545
|
-
"
|
|
3546
|
-
"}
|
|
3547
|
-
"function __req<R>(run: () => Promise<R
|
|
3548
|
-
"
|
|
3549
|
-
" const __promise = () => {",
|
|
3550
|
-
" __p ??= run();",
|
|
3551
|
-
" return __p;",
|
|
3552
|
-
" };",
|
|
3553
|
-
" return {",
|
|
3554
|
-
" then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
|
|
3555
|
-
" catch: (onrejected) => __promise().catch(onrejected),",
|
|
3556
|
-
" finally: (onfinally) => __promise().finally(onfinally),",
|
|
3557
|
-
" fetch: run,",
|
|
3558
|
-
" };",
|
|
4220
|
+
" ): Promise<T1 | T2> {",
|
|
4221
|
+
" this.__p ??= this.__run();",
|
|
4222
|
+
" return this.__p.then(onfulfilled, onrejected);",
|
|
4223
|
+
" }",
|
|
4224
|
+
"}",
|
|
4225
|
+
"function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
|
|
4226
|
+
" return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
|
|
3559
4227
|
"}",
|
|
3560
4228
|
""
|
|
3561
4229
|
];
|
|
@@ -3573,18 +4241,26 @@ function emitFilterFieldGuard() {
|
|
|
3573
4241
|
];
|
|
3574
4242
|
}
|
|
3575
4243
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
3576
|
-
const
|
|
3577
|
-
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4244
|
+
const memberLines = [];
|
|
3578
4245
|
if (req.filterFieldsExpr) {
|
|
3579
|
-
|
|
4246
|
+
memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
3580
4247
|
}
|
|
3581
4248
|
if (streamExpr) {
|
|
3582
|
-
|
|
4249
|
+
memberLines.push(`${pad} stream: () => ${streamExpr},`);
|
|
3583
4250
|
}
|
|
3584
4251
|
for (const [name, value] of Object.entries(members)) {
|
|
3585
|
-
|
|
3586
|
-
}
|
|
3587
|
-
|
|
4252
|
+
memberLines.push(`${pad} ${name}: ${value},`);
|
|
4253
|
+
}
|
|
4254
|
+
const body = [requestExpr, ...memberLines].join("\n");
|
|
4255
|
+
const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
|
|
4256
|
+
const lines = [`${pad}${objKey}: (${param}) =>`];
|
|
4257
|
+
if (memberLines.length === 0) {
|
|
4258
|
+
lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4259
|
+
return lines;
|
|
4260
|
+
}
|
|
4261
|
+
lines.push(`${pad} __req(() => ${requestExpr}, {`);
|
|
4262
|
+
lines.push(...memberLines);
|
|
4263
|
+
lines.push(`${pad} }),`);
|
|
3588
4264
|
return lines;
|
|
3589
4265
|
}
|
|
3590
4266
|
function renderStreamExpr(req) {
|
|
@@ -3715,6 +4391,26 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
3715
4391
|
"}",
|
|
3716
4392
|
""
|
|
3717
4393
|
];
|
|
4394
|
+
function isClientCallable(route) {
|
|
4395
|
+
return route.method.toUpperCase() !== "ALL";
|
|
4396
|
+
}
|
|
4397
|
+
var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
|
|
4398
|
+
var ROUTES_EXPORTS = [
|
|
4399
|
+
// Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
|
|
4400
|
+
{ name: "route", typeOnly: false },
|
|
4401
|
+
{ name: "ROUTES", typeOnly: false },
|
|
4402
|
+
{ name: "RouteName", typeOnly: true },
|
|
4403
|
+
{ name: "ExtractParams", typeOnly: true },
|
|
4404
|
+
{ name: "RouteParams", typeOnly: true }
|
|
4405
|
+
];
|
|
4406
|
+
function finishRoutesImport(lines) {
|
|
4407
|
+
const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
|
|
4408
|
+
const used = ROUTES_EXPORTS.filter(
|
|
4409
|
+
({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
|
|
4410
|
+
).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
|
|
4411
|
+
const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
|
|
4412
|
+
return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
|
|
4413
|
+
}
|
|
3718
4414
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
3719
4415
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
3720
4416
|
const serialization = opts.serialization ?? "json";
|
|
@@ -3722,7 +4418,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3722
4418
|
const { layer } = resolveApiSlots(extensions);
|
|
3723
4419
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
3724
4420
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
3725
|
-
const contracted = routes.filter((r) => r.contract);
|
|
4421
|
+
const contracted = routes.filter((r) => r.contract && isClientCallable(r));
|
|
3726
4422
|
const ctx = opts.ctx ?? {
|
|
3727
4423
|
cwd: outDir ?? "",
|
|
3728
4424
|
outDir: outDir ?? "",
|
|
@@ -3774,9 +4470,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3774
4470
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
3775
4471
|
}
|
|
3776
4472
|
lines.push(...extImports);
|
|
3777
|
-
lines.push(
|
|
3778
|
-
"import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
|
|
3779
|
-
);
|
|
4473
|
+
lines.push(ROUTES_IMPORT_PLACEHOLDER);
|
|
3780
4474
|
const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
|
|
3781
4475
|
lines.push(`import type { Fetcher } from '${runtimeImport}';`);
|
|
3782
4476
|
if (serialization === "json" && contracted.length > 0) {
|
|
@@ -3827,7 +4521,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3827
4521
|
lines.push(...statements, "");
|
|
3828
4522
|
}
|
|
3829
4523
|
}
|
|
3830
|
-
return lines
|
|
4524
|
+
return finishRoutesImport(lines);
|
|
3831
4525
|
}
|
|
3832
4526
|
const tree = /* @__PURE__ */ new Map();
|
|
3833
4527
|
for (const r of contracted) {
|
|
@@ -3880,7 +4574,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
3880
4574
|
lines.push(...statements, "");
|
|
3881
4575
|
}
|
|
3882
4576
|
}
|
|
3883
|
-
return lines
|
|
4577
|
+
return finishRoutesImport(lines);
|
|
3884
4578
|
}
|
|
3885
4579
|
|
|
3886
4580
|
// src/emit/emit-cache.ts
|
|
@@ -3893,6 +4587,7 @@ async function emitCache(pages, outDir) {
|
|
|
3893
4587
|
const s = await (0, import_promises4.stat)(p.absolutePath);
|
|
3894
4588
|
return {
|
|
3895
4589
|
name: p.name,
|
|
4590
|
+
scope: p.scope ?? "default",
|
|
3896
4591
|
relativePath: p.relativePath,
|
|
3897
4592
|
mtime: s.mtime.toISOString()
|
|
3898
4593
|
};
|
|
@@ -4640,6 +5335,7 @@ function buildOpenApiSpec(routes, opts = {}) {
|
|
|
4640
5335
|
const paths = {};
|
|
4641
5336
|
for (const route of routes) {
|
|
4642
5337
|
if (!route.contract) continue;
|
|
5338
|
+
if (route.method.toUpperCase() === "ALL") continue;
|
|
4643
5339
|
const oaPath = toOpenApiPath(route.path);
|
|
4644
5340
|
const method = route.method.toLowerCase();
|
|
4645
5341
|
let pathItem = paths[oaPath];
|
|
@@ -4683,10 +5379,11 @@ async function emitPages(pages, outDir, _options = {}) {
|
|
|
4683
5379
|
}).join("\n");
|
|
4684
5380
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
4685
5381
|
const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
|
|
5382
|
+
const scopesBlock = buildScopesBlock(pages);
|
|
4686
5383
|
const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
|
|
4687
5384
|
|
|
4688
5385
|
export type InertiaPageName = ${pageNameUnion};
|
|
4689
|
-
${propsHelper}
|
|
5386
|
+
${propsHelper}${scopesBlock}
|
|
4690
5387
|
declare module '@dudousxd/nestjs-inertia' {
|
|
4691
5388
|
interface InertiaPages {
|
|
4692
5389
|
${augBody}
|
|
@@ -4695,6 +5392,31 @@ ${sharedPropsBlock}}
|
|
|
4695
5392
|
`;
|
|
4696
5393
|
await (0, import_promises9.writeFile)((0, import_node_path13.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
4697
5394
|
}
|
|
5395
|
+
function buildScopesBlock(pages) {
|
|
5396
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
5397
|
+
for (const page of pages) {
|
|
5398
|
+
const scope = page.scope ?? "default";
|
|
5399
|
+
const names = byScope.get(scope) ?? [];
|
|
5400
|
+
names.push(JSON.stringify(page.name));
|
|
5401
|
+
byScope.set(scope, names);
|
|
5402
|
+
}
|
|
5403
|
+
if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
|
|
5404
|
+
if (!byScope.has("default")) byScope.set("default", []);
|
|
5405
|
+
const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
|
|
5406
|
+
const body = scopes.map((scope) => {
|
|
5407
|
+
const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
|
|
5408
|
+
const names = byScope.get(scope) ?? [];
|
|
5409
|
+
return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
|
|
5410
|
+
}).join("\n");
|
|
5411
|
+
return `
|
|
5412
|
+
/** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
|
|
5413
|
+
export interface InertiaScopePages {
|
|
5414
|
+
${body}
|
|
5415
|
+
}
|
|
5416
|
+
|
|
5417
|
+
export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
|
|
5418
|
+
`;
|
|
5419
|
+
}
|
|
4698
5420
|
function buildSharedPropsBlock(sharedProps) {
|
|
4699
5421
|
if (!sharedProps) return "";
|
|
4700
5422
|
if (sharedProps.isImportRef) {
|
|
@@ -4902,8 +5624,7 @@ function diffConfigKeyHashes(previous, current) {
|
|
|
4902
5624
|
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
4903
5625
|
}
|
|
4904
5626
|
async function discoverInputFiles(config) {
|
|
4905
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
4906
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
5627
|
+
const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
|
|
4907
5628
|
const cwd = config.codegen.cwd;
|
|
4908
5629
|
const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4909
5630
|
return [...new Set(matched)].sort();
|
|
@@ -5034,12 +5755,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
5034
5755
|
}
|
|
5035
5756
|
if (config.pages) {
|
|
5036
5757
|
const pagesConfig = config.pages;
|
|
5037
|
-
const pages = await
|
|
5038
|
-
glob: pagesConfig.glob,
|
|
5039
|
-
cwd: config.codegen.cwd,
|
|
5040
|
-
propsExport: pagesConfig.propsExport,
|
|
5041
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
5042
|
-
});
|
|
5758
|
+
const pages = await discoverPageScopes(config);
|
|
5043
5759
|
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
5044
5760
|
await emitPages(pages, config.codegen.outDir, {
|
|
5045
5761
|
propsExport: pagesConfig.propsExport,
|
|
@@ -5214,12 +5930,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
5214
5930
|
await runInitialPass();
|
|
5215
5931
|
}
|
|
5216
5932
|
let pagesDebounceTimer;
|
|
5217
|
-
const
|
|
5218
|
-
const pagesWatcher = import_chokidar.default.watch(
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5933
|
+
const pageGlobs = allPageGlobs(config);
|
|
5934
|
+
const pagesWatcher = import_chokidar.default.watch(
|
|
5935
|
+
(pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
|
|
5936
|
+
(glob) => (0, import_node_path18.join)(config.codegen.cwd, glob)
|
|
5937
|
+
),
|
|
5938
|
+
{
|
|
5939
|
+
ignoreInitial: true,
|
|
5940
|
+
persistent: true,
|
|
5941
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
5942
|
+
}
|
|
5943
|
+
);
|
|
5223
5944
|
function schedulePagesRegenerate() {
|
|
5224
5945
|
if (pagesDebounceTimer !== void 0) {
|
|
5225
5946
|
clearTimeout(pagesDebounceTimer);
|
|
@@ -5299,7 +6020,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
5299
6020
|
}
|
|
5300
6021
|
|
|
5301
6022
|
// src/index.ts
|
|
5302
|
-
var VERSION = "0.
|
|
6023
|
+
var VERSION = "0.27.0";
|
|
5303
6024
|
|
|
5304
6025
|
// src/cli/codegen.ts
|
|
5305
6026
|
async function runCodegen(opts = {}) {
|