@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.
@@ -109,11 +109,32 @@ function validateUserConfig(userConfig) {
109
109
  "validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
110
110
  );
111
111
  }
112
- if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
112
+ if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
113
113
  throw new ConfigError(
114
- "Config validation failed: `pages.glob` must be a string when `pages` is set"
114
+ "Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
115
115
  );
116
116
  }
117
+ for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
118
+ if (scope === "default") {
119
+ throw new ConfigError(
120
+ 'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
121
+ );
122
+ }
123
+ if (!isGlobInput(scopeConfig?.glob)) {
124
+ throw new ConfigError(
125
+ `Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
126
+ );
127
+ }
128
+ }
129
+ if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
130
+ console.warn(
131
+ "[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)."
132
+ );
133
+ }
134
+ }
135
+ function isGlobInput(value) {
136
+ if (typeof value === "string") return true;
137
+ return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
117
138
  }
118
139
  function applyDefaults(userConfig, cwd) {
119
140
  validateUserConfig(userConfig);
@@ -141,7 +162,17 @@ function applyDefaults(userConfig, cwd) {
141
162
  pages: userConfig.pages ? {
142
163
  glob: userConfig.pages.glob,
143
164
  propsExport: userConfig.pages.propsExport ?? "ComponentProps",
144
- componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
165
+ componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
166
+ scopes: Object.fromEntries(
167
+ Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
168
+ scope,
169
+ {
170
+ glob: scopeConfig.glob,
171
+ prefix: scopeConfig.prefix ?? `${scope}/`,
172
+ componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
173
+ }
174
+ ])
175
+ )
145
176
  } : null,
146
177
  contracts: {
147
178
  glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
@@ -187,10 +218,10 @@ var import_fast_glob3 = __toESM(require("fast-glob"), 1);
187
218
  var import_node_fs = require("fs");
188
219
  var import_node_path3 = require("path");
189
220
  var import_fast_glob = __toESM(require("fast-glob"), 1);
190
- var import_ts_morph8 = require("ts-morph");
221
+ var import_ts_morph11 = require("ts-morph");
191
222
 
192
223
  // src/discovery/dto-type-resolver.ts
193
- var import_ts_morph6 = require("ts-morph");
224
+ var import_ts_morph10 = require("ts-morph");
194
225
 
195
226
  // src/discovery/dto-to-ir.ts
196
227
  var import_ts_morph2 = require("ts-morph");
@@ -1718,6 +1749,672 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
1718
1749
  return fields;
1719
1750
  }
1720
1751
 
1752
+ // src/discovery/pipe-schema.ts
1753
+ var import_ts_morph9 = require("ts-morph");
1754
+
1755
+ // src/discovery/schema-type-inference.ts
1756
+ var import_ts_morph7 = require("ts-morph");
1757
+
1758
+ // src/discovery/schema-type-program.ts
1759
+ var import_ts_morph6 = require("ts-morph");
1760
+ var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
1761
+ var SchemaTypeProgram = class {
1762
+ constructor(discovery) {
1763
+ this.discovery = discovery;
1764
+ const base = discovery.getCompilerOptions();
1765
+ this.options = {
1766
+ ...base,
1767
+ // A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
1768
+ // `Promise` for the schema libraries' declarations to lean on.
1769
+ target: base.target ?? import_ts_morph6.ts.ScriptTarget.ES2022,
1770
+ // With neither set, resolve the way a bundler does: package `exports`
1771
+ // (`zod/v4`) and extensionless or `.js` relative imports all resolve.
1772
+ ...base.module === void 0 && base.moduleResolution === void 0 ? { module: import_ts_morph6.ts.ModuleKind.ESNext, moduleResolution: import_ts_morph6.ts.ModuleResolutionKind.Bundler } : {},
1773
+ // Without it `.nullable()` and `.optional()` would erase to the bare type.
1774
+ strictNullChecks: true,
1775
+ // No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
1776
+ // need them, and they are a hundred-odd files of program to build.
1777
+ types: [],
1778
+ noEmit: true
1779
+ };
1780
+ this.project = new import_ts_morph6.Project({
1781
+ skipAddingFilesFromTsConfig: true,
1782
+ compilerOptions: this.options
1783
+ });
1784
+ for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
1785
+ this.flush();
1786
+ }
1787
+ project;
1788
+ options;
1789
+ /** Parses a file that is not in discovery's Project, without adding it there. */
1790
+ scratch = new import_ts_morph6.Project({
1791
+ skipAddingFilesFromTsConfig: true,
1792
+ skipLoadingLibFiles: true,
1793
+ skipFileDependencyResolution: true
1794
+ });
1795
+ needs = /* @__PURE__ */ new Map();
1796
+ /** Pruned text per file, as last written into {@link project}. */
1797
+ written = /* @__PURE__ */ new Map();
1798
+ /**
1799
+ * The typed copy of a discovery file, with its schema positions kept. Adding one
1800
+ * that was not there yet (a contract file discovery reached lazily) rebuilds the
1801
+ * program once.
1802
+ */
1803
+ sourceFileFor(sf) {
1804
+ this.request(sf.getFilePath(), { schema: true });
1805
+ this.flush();
1806
+ return this.project.getSourceFile(sf.getFilePath());
1807
+ }
1808
+ // -------------------------------------------------------------------------
1809
+ queue = [];
1810
+ request(path, want) {
1811
+ let needs = this.needs.get(path);
1812
+ const isNew = !needs;
1813
+ needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
1814
+ let grew = isNew;
1815
+ if (want.schema && !needs.schema) {
1816
+ needs.schema = true;
1817
+ grew = true;
1818
+ }
1819
+ if (want.all && !needs.all) {
1820
+ needs.all = true;
1821
+ grew = true;
1822
+ }
1823
+ for (const name of want.names ?? []) {
1824
+ if (!needs.names.has(name)) {
1825
+ needs.names.add(name);
1826
+ grew = true;
1827
+ }
1828
+ }
1829
+ this.needs.set(path, needs);
1830
+ if (grew) this.queue.push(path);
1831
+ }
1832
+ /** Run the worklist to a fixpoint, then write every changed file into the program. */
1833
+ flush() {
1834
+ const changed = /* @__PURE__ */ new Map();
1835
+ while (this.queue.length > 0) {
1836
+ const path = this.queue.pop();
1837
+ const original = this.original(path);
1838
+ if (!original) continue;
1839
+ const needs = this.needs.get(path);
1840
+ const text = this.prune(original, needs);
1841
+ if (this.written.get(path) !== text) changed.set(path, text);
1842
+ }
1843
+ for (const [path, text] of changed) {
1844
+ this.project.createSourceFile(path, text, { overwrite: true });
1845
+ this.written.set(path, text);
1846
+ }
1847
+ }
1848
+ /** The file as discovery sees it (its in-memory text), else as it is on disk. */
1849
+ original(path) {
1850
+ return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
1851
+ }
1852
+ /**
1853
+ * `sf`'s text with every import/re-export nothing in `needs` references blanked,
1854
+ * requesting from each kept one's target the names it brings in.
1855
+ */
1856
+ prune(sf, needs) {
1857
+ const locals = topLevelDeclarations(sf);
1858
+ const roots = [];
1859
+ if (needs.all) roots.push(...sf.getStatements());
1860
+ if (needs.schema) roots.push(...schemaPositions(sf));
1861
+ const reexported = /* @__PURE__ */ new Map();
1862
+ for (const exp of sf.getExportDeclarations()) {
1863
+ const spec = exp.getModuleSpecifierValue();
1864
+ if (spec === void 0) {
1865
+ for (const named of exp.getNamedExports()) {
1866
+ const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
1867
+ if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
1868
+ }
1869
+ continue;
1870
+ }
1871
+ if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
1872
+ for (const named of exp.getNamedExports()) {
1873
+ const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
1874
+ const list = reexported.get(exposed) ?? [];
1875
+ list.push({ from: spec, name: named.getNameNode().getText() });
1876
+ reexported.set(exposed, list);
1877
+ }
1878
+ }
1879
+ const unresolvedNames = [];
1880
+ for (const name of needs.names) {
1881
+ const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
1882
+ if (decls.length > 0) roots.push(...decls);
1883
+ else if (!reexported.has(name)) unresolvedNames.push(name);
1884
+ }
1885
+ const referenced = /* @__PURE__ */ new Set();
1886
+ const queue = [...roots];
1887
+ const seen = new Set(queue);
1888
+ while (queue.length > 0) {
1889
+ const node = queue.pop();
1890
+ const ids = import_ts_morph6.Node.isIdentifier(node) ? [node] : node.getDescendantsOfKind(import_ts_morph6.ts.SyntaxKind.Identifier);
1891
+ for (const id of ids) {
1892
+ const name = id.getText();
1893
+ if (referenced.has(name)) continue;
1894
+ referenced.add(name);
1895
+ for (const local of locals.get(name) ?? []) {
1896
+ if (seen.has(local)) continue;
1897
+ seen.add(local);
1898
+ queue.push(local);
1899
+ }
1900
+ }
1901
+ }
1902
+ const chars = sf.getFullText().split("");
1903
+ const blank = (node) => {
1904
+ for (let i = node.getStart(); i < node.getEnd(); i++) {
1905
+ if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
1906
+ }
1907
+ };
1908
+ for (const decl of sf.getImportDeclarations()) {
1909
+ const target = this.resolve(decl.getModuleSpecifierValue(), sf);
1910
+ const namespace = decl.getNamespaceImport()?.getText();
1911
+ const def = decl.getDefaultImport()?.getText();
1912
+ const named = decl.getNamedImports().map((n) => ({
1913
+ local: (n.getAliasNode() ?? n.getNameNode()).getText(),
1914
+ imported: n.getName()
1915
+ })).filter((n) => referenced.has(n.local));
1916
+ const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
1917
+ const wantsDefault = def !== void 0 && referenced.has(def);
1918
+ if (!wantsNamespace && !wantsDefault && named.length === 0) {
1919
+ blank(decl);
1920
+ continue;
1921
+ }
1922
+ if (target) {
1923
+ this.request(target, {
1924
+ all: wantsNamespace,
1925
+ names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
1926
+ });
1927
+ }
1928
+ }
1929
+ for (const exp of sf.getExportDeclarations()) {
1930
+ const spec = exp.getModuleSpecifierValue();
1931
+ if (spec === void 0) continue;
1932
+ const target = this.resolve(spec, sf);
1933
+ if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
1934
+ if (!needs.all && unresolvedNames.length === 0) {
1935
+ blank(exp);
1936
+ } else if (target) {
1937
+ this.request(target, { all: needs.all, names: unresolvedNames });
1938
+ }
1939
+ continue;
1940
+ }
1941
+ const wanted = exp.getNamedExports().filter(
1942
+ (n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
1943
+ ).map((n) => n.getNameNode().getText());
1944
+ if (wanted.length === 0) blank(exp);
1945
+ else if (target) this.request(target, { names: wanted });
1946
+ }
1947
+ return chars.join("");
1948
+ }
1949
+ /**
1950
+ * The project source file a module specifier resolves to, or undefined for a
1951
+ * package (read unpruned by the program itself) or an unresolvable one.
1952
+ */
1953
+ resolve(specifier, from) {
1954
+ const resolved = import_ts_morph6.ts.resolveModuleName(
1955
+ specifier,
1956
+ from.getFilePath(),
1957
+ this.options,
1958
+ import_ts_morph6.ts.sys
1959
+ ).resolvedModule;
1960
+ if (!resolved || resolved.isExternalLibraryImport) return void 0;
1961
+ const file = resolved.resolvedFileName;
1962
+ if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
1963
+ return file;
1964
+ }
1965
+ };
1966
+ function schemaPositions(sf) {
1967
+ const roots = [];
1968
+ sf.forEachDescendant((node) => {
1969
+ if (import_ts_morph6.Node.isDecorator(node)) {
1970
+ const name = node.getName();
1971
+ if (name === "ApplyContract") {
1972
+ roots.push(...node.getArguments());
1973
+ } else if (PARAM_DECORATORS.has(name)) {
1974
+ for (const arg of node.getArguments()) {
1975
+ if (import_ts_morph6.Node.isNewExpression(arg) || import_ts_morph6.Node.isCallExpression(arg)) {
1976
+ roots.push(...arg.getArguments());
1977
+ }
1978
+ }
1979
+ }
1980
+ } else if (import_ts_morph6.Node.isCallExpression(node)) {
1981
+ const callee = node.getExpression();
1982
+ const name = import_ts_morph6.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph6.Node.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
1983
+ if (name === "defineContract") roots.push(...node.getArguments());
1984
+ }
1985
+ });
1986
+ return roots;
1987
+ }
1988
+ function topLevelDeclarations(sf) {
1989
+ const byName = /* @__PURE__ */ new Map();
1990
+ const add = (name, node) => {
1991
+ if (!name) return;
1992
+ const list = byName.get(name);
1993
+ if (list) list.push(node);
1994
+ else byName.set(name, [node]);
1995
+ };
1996
+ for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
1997
+ for (const decl of sf.getFunctions()) add(decl.getName(), decl);
1998
+ for (const decl of sf.getClasses()) add(decl.getName(), decl);
1999
+ for (const decl of sf.getEnums()) add(decl.getName(), decl);
2000
+ for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
2001
+ for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
2002
+ for (const decl of sf.getModules()) add(decl.getName(), decl);
2003
+ return byName;
2004
+ }
2005
+ function defaultExport(sf) {
2006
+ const nodes = [];
2007
+ for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
2008
+ for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
2009
+ for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
2010
+ return nodes;
2011
+ }
2012
+
2013
+ // src/discovery/schema-type-inference.ts
2014
+ var programs = /* @__PURE__ */ new WeakMap();
2015
+ function programFor(discovery) {
2016
+ let program = programs.get(discovery);
2017
+ if (!program) {
2018
+ program = new SchemaTypeProgram(discovery);
2019
+ programs.set(discovery, program);
2020
+ }
2021
+ return program;
2022
+ }
2023
+ function clearSchemaTypeProject(discovery) {
2024
+ programs.delete(discovery);
2025
+ }
2026
+ function counterpart(typedSf, node) {
2027
+ const start = node.getStart();
2028
+ const end = node.getEnd();
2029
+ const kind = node.getKind();
2030
+ let candidate = typedSf.getDescendantAtPos(start);
2031
+ while (candidate) {
2032
+ if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
2033
+ return candidate;
2034
+ }
2035
+ if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
2036
+ candidate = candidate.getParent();
2037
+ }
2038
+ return void 0;
2039
+ }
2040
+ function propType(type, name, at) {
2041
+ return type.getProperty(name)?.getTypeAtLocation(at);
2042
+ }
2043
+ function isAnyOrUnknown(type) {
2044
+ return type.isAny() || type.isUnknown();
2045
+ }
2046
+ function inferSchema(expr, discovery) {
2047
+ try {
2048
+ const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
2049
+ const at = typedSf && counterpart(typedSf, expr);
2050
+ if (!at) return { kind: "unresolved" };
2051
+ const schemaType = at.getType();
2052
+ if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
2053
+ const standard = propType(schemaType, "~standard", at);
2054
+ const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
2055
+ const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
2056
+ const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
2057
+ if (stdInput && stdOutput) {
2058
+ return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
2059
+ }
2060
+ const zodInput = propType(schemaType, "_input", at);
2061
+ const zodOutput = propType(schemaType, "_output", at);
2062
+ if (zodInput && zodOutput) {
2063
+ return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
2064
+ }
2065
+ return { kind: "not-schema" };
2066
+ } catch (err) {
2067
+ dbg("schema inference failed:", err);
2068
+ return { kind: "unresolved" };
2069
+ }
2070
+ }
2071
+ var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
2072
+ "Date",
2073
+ "File",
2074
+ "Blob",
2075
+ "FormData",
2076
+ "URL",
2077
+ "RegExp",
2078
+ "Map",
2079
+ "Set",
2080
+ "ArrayBuffer",
2081
+ "Uint8Array"
2082
+ ]);
2083
+ var MAX_DEPTH = 12;
2084
+ function objectKey(name) {
2085
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2086
+ }
2087
+ function isOptionalSymbol(sym) {
2088
+ return (sym.getFlags() & import_ts_morph7.ts.SymbolFlags.Optional) !== 0;
2089
+ }
2090
+ function isSymbolKeyed(sym) {
2091
+ return sym.getName().startsWith("__@");
2092
+ }
2093
+ function orderedProperties(type) {
2094
+ const position = (sym) => {
2095
+ const decl = sym.getDeclarations()[0];
2096
+ return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
2097
+ };
2098
+ return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
2099
+ if (a.at && b.at) {
2100
+ if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
2101
+ if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
2102
+ } else if (a.at || b.at) {
2103
+ return a.at ? -1 : 1;
2104
+ }
2105
+ return a.index - b.index;
2106
+ }).map((entry) => entry.sym);
2107
+ }
2108
+ function isFromDeclarationFile(sym) {
2109
+ const decl = sym?.getDeclarations()[0];
2110
+ return !!decl && decl.getSourceFile().isDeclarationFile();
2111
+ }
2112
+ function render(type, state, paired) {
2113
+ if (state.depth > MAX_DEPTH) return "unknown";
2114
+ if (isAnyOrUnknown(type)) {
2115
+ return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
2116
+ }
2117
+ if (type.isNever()) return "never";
2118
+ if (type.isString() || type.isTemplateLiteral()) return "string";
2119
+ if (type.isNumber()) return "number";
2120
+ if (type.isBoolean()) return "boolean";
2121
+ if (type.isBigInt()) return "bigint";
2122
+ if (type.isNull()) return "null";
2123
+ if (type.isUndefined() || type.isVoid()) return "undefined";
2124
+ if (type.isBooleanLiteral()) return type.getText();
2125
+ if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
2126
+ const value = type.getLiteralValue();
2127
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
2128
+ }
2129
+ if (type.isBigIntLiteral()) return type.getText();
2130
+ if (type.isTypeParameter()) return "unknown";
2131
+ if (type.isUnion()) return renderUnion(type, state, paired);
2132
+ if (type.isIntersection()) return renderIntersection(type, state);
2133
+ const next = { ...state, depth: state.depth + 1 };
2134
+ if (type.isArray() || type.isReadonlyArray()) {
2135
+ const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
2136
+ const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
2137
+ return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
2138
+ }
2139
+ if (type.isTuple()) {
2140
+ const elements = type.getTupleElements();
2141
+ const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
2142
+ return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
2143
+ }
2144
+ if (type.isObject()) return renderObject(type, next, paired);
2145
+ return "unknown";
2146
+ }
2147
+ function renderUnion(type, state, paired) {
2148
+ const members = type.getUnionTypes();
2149
+ const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
2150
+ const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
2151
+ const pairedNonNull = paired?.getNonNullableType();
2152
+ const arms = [];
2153
+ let sawBoolean = false;
2154
+ let hasNull = false;
2155
+ let hasUndefined = false;
2156
+ for (const member of members) {
2157
+ if (member.isNull()) {
2158
+ hasNull = true;
2159
+ continue;
2160
+ }
2161
+ if (member.isUndefined()) {
2162
+ hasUndefined = true;
2163
+ continue;
2164
+ }
2165
+ if (member.isBooleanLiteral() && hasTrue && hasFalse) {
2166
+ if (!sawBoolean) arms.push("boolean");
2167
+ sawBoolean = true;
2168
+ continue;
2169
+ }
2170
+ arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
2171
+ }
2172
+ if (hasNull) arms.push("null");
2173
+ if (hasUndefined) arms.push("undefined");
2174
+ const unique = [...new Set(arms)];
2175
+ if (unique.includes("unknown")) return "unknown";
2176
+ return unique.join(" | ");
2177
+ }
2178
+ function renderIntersection(type, state) {
2179
+ const members = type.getIntersectionTypes();
2180
+ const primitive = members.find((m) => !m.isObject());
2181
+ if (primitive) return render(primitive, state);
2182
+ return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
2183
+ }
2184
+ function renderObject(type, state, paired) {
2185
+ const symbol = type.getSymbol() ?? type.getAliasSymbol();
2186
+ const name = symbol?.getName();
2187
+ if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
2188
+ const args = type.getTypeArguments();
2189
+ return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
2190
+ }
2191
+ if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
2192
+ return "unknown";
2193
+ }
2194
+ if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
2195
+ if (state.stack.has(type.compilerType)) return "unknown";
2196
+ const stack = new Set(state.stack).add(type.compilerType);
2197
+ const inner = { ...state, stack };
2198
+ const fields = [];
2199
+ for (const prop of orderedProperties(type)) {
2200
+ if (isSymbolKeyed(prop)) continue;
2201
+ const propName = prop.getName();
2202
+ const propT = prop.getTypeAtLocation(state.at);
2203
+ if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
2204
+ const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
2205
+ const optional = isOptionalSymbol(prop) ? "?" : "";
2206
+ fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
2207
+ }
2208
+ const stringIndex = type.getStringIndexType();
2209
+ if (stringIndex) {
2210
+ const pairedIndex = paired?.getStringIndexType();
2211
+ const value = render(stringIndex, inner, pairedIndex);
2212
+ if (fields.length === 0) return `Record<string, ${value}>`;
2213
+ fields.push(`[key: string]: ${value}`);
2214
+ }
2215
+ return fields.length ? `{ ${fields.join("; ")} }` : "{}";
2216
+ }
2217
+ function stateAt(at) {
2218
+ return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
2219
+ }
2220
+ function requestTypeOf(types) {
2221
+ return render(types.input, stateAt(types.at), types.output);
2222
+ }
2223
+ function responseTypeOf(types) {
2224
+ return render(types.output, stateAt(types.at));
2225
+ }
2226
+ function requestFieldsOf(types) {
2227
+ const { input, output, at } = types;
2228
+ const objectInput = input.getNonNullableType();
2229
+ if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
2230
+ const state = stateAt(at);
2231
+ return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
2232
+ name: prop.getName(),
2233
+ type: render(
2234
+ prop.getTypeAtLocation(at),
2235
+ state,
2236
+ output.getProperty(prop.getName())?.getTypeAtLocation(at)
2237
+ ),
2238
+ optional: isOptionalSymbol(prop)
2239
+ }));
2240
+ }
2241
+ function acceptsUndefined(types) {
2242
+ const { input, output } = types;
2243
+ const accepted = isAnyOrUnknown(input) ? output : input;
2244
+ if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
2245
+ return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
2246
+ }
2247
+
2248
+ // src/discovery/zod-ast-to-ts.ts
2249
+ var import_ts_morph8 = require("ts-morph");
2250
+ function zodAstToTs(node) {
2251
+ if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
2252
+ const expr = node.getExpression();
2253
+ if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
2254
+ const methodName = expr.getName();
2255
+ const receiver = expr.getExpression();
2256
+ if (methodName === "optional") {
2257
+ return `${zodAstToTs(receiver)} | undefined`;
2258
+ }
2259
+ if (methodName === "nullable") {
2260
+ return `${zodAstToTs(receiver)} | null`;
2261
+ }
2262
+ const args = node.getArguments();
2263
+ switch (methodName) {
2264
+ case "string":
2265
+ return "string";
2266
+ case "number":
2267
+ return "number";
2268
+ case "boolean":
2269
+ return "boolean";
2270
+ case "unknown":
2271
+ return "unknown";
2272
+ case "any":
2273
+ return "unknown";
2274
+ case "literal": {
2275
+ const lit = args[0];
2276
+ if (!lit) return "unknown";
2277
+ if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2278
+ if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2279
+ if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
2280
+ if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
2281
+ return "unknown";
2282
+ }
2283
+ case "enum": {
2284
+ const arrArg = args[0];
2285
+ if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
2286
+ const members = arrArg.getElements().map(
2287
+ (el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2288
+ );
2289
+ return members.join(" | ");
2290
+ }
2291
+ case "array": {
2292
+ const inner = args[0];
2293
+ if (!inner) return "unknown";
2294
+ return `Array<${zodAstToTs(inner)}>`;
2295
+ }
2296
+ case "object": {
2297
+ const objArg = args[0];
2298
+ if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
2299
+ const lines = [];
2300
+ for (const prop of objArg.getProperties()) {
2301
+ if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
2302
+ const key = prop.getName();
2303
+ const valNode = prop.getInitializer();
2304
+ if (!valNode) continue;
2305
+ const tsType = zodAstToTs(valNode);
2306
+ const isOpt = isOptionalChain(valNode);
2307
+ lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
2308
+ }
2309
+ return `{ ${lines.join("; ")} }`;
2310
+ }
2311
+ case "union": {
2312
+ const arrArg = args[0];
2313
+ if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
2314
+ return arrArg.getElements().map(zodAstToTs).join(" | ");
2315
+ }
2316
+ case "record": {
2317
+ const valArg = args.length === 1 ? args[0] : args[1];
2318
+ if (!valArg) return "unknown";
2319
+ return `Record<string, ${zodAstToTs(valArg)}>`;
2320
+ }
2321
+ case "tuple": {
2322
+ const arrArg = args[0];
2323
+ if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
2324
+ return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2325
+ }
2326
+ default:
2327
+ return "unknown";
2328
+ }
2329
+ }
2330
+ return "unknown";
2331
+ }
2332
+ function isOptionalChain(node) {
2333
+ if (!import_ts_morph8.Node.isCallExpression(node)) return false;
2334
+ const expr = node.getExpression();
2335
+ return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2336
+ }
2337
+ function memberType(val, position, project) {
2338
+ if (project) {
2339
+ const inferred = inferSchema(val, project);
2340
+ if (inferred.kind === "schema") {
2341
+ return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
2342
+ }
2343
+ }
2344
+ return zodAstToTs(val);
2345
+ }
2346
+ function parseDefineContractCall(callExpr, project) {
2347
+ if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
2348
+ const callee = callExpr.getExpression();
2349
+ const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
2350
+ if (calleeName !== "defineContract") return null;
2351
+ const args = callExpr.getArguments();
2352
+ const optsArg = args[0];
2353
+ if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
2354
+ let query = null;
2355
+ let body = null;
2356
+ let response = "unknown";
2357
+ let error = null;
2358
+ let bodyZodText = null;
2359
+ let queryZodText = null;
2360
+ for (const prop of optsArg.getProperties()) {
2361
+ if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
2362
+ const propName = prop.getName();
2363
+ const val = prop.getInitializer();
2364
+ if (!val) continue;
2365
+ if (propName === "query") {
2366
+ query = memberType(val, "request", project);
2367
+ queryZodText = val.getText();
2368
+ } else if (propName === "body") {
2369
+ body = memberType(val, "request", project);
2370
+ bodyZodText = val.getText();
2371
+ } else if (propName === "response") {
2372
+ response = memberType(val, "response", project);
2373
+ } else if (propName === "error") {
2374
+ error = memberType(val, "response", project);
2375
+ }
2376
+ }
2377
+ return { query, body, response, error, bodyZodText, queryZodText };
2378
+ }
2379
+
2380
+ // src/discovery/pipe-schema.ts
2381
+ function pipeSchemaOf(decorator, project) {
2382
+ for (const pipe of decorator.getArguments()) {
2383
+ if (!import_ts_morph9.Node.isNewExpression(pipe) && !import_ts_morph9.Node.isCallExpression(pipe)) continue;
2384
+ for (const arg of pipe.getArguments()) {
2385
+ if (!couldBeSchema(arg)) continue;
2386
+ const inferred = inferSchema(arg, project);
2387
+ if (inferred.kind === "schema") {
2388
+ return {
2389
+ type: requestTypeOf(inferred.types),
2390
+ optional: acceptsUndefined(inferred.types),
2391
+ fields: requestFieldsOf(inferred.types)
2392
+ };
2393
+ }
2394
+ if (inferred.kind === "unresolved") {
2395
+ const fallback = syntacticZodType(arg, project);
2396
+ if (fallback) {
2397
+ return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
2398
+ }
2399
+ }
2400
+ }
2401
+ }
2402
+ return null;
2403
+ }
2404
+ function couldBeSchema(arg) {
2405
+ return import_ts_morph9.Node.isIdentifier(arg) || import_ts_morph9.Node.isPropertyAccessExpression(arg) || import_ts_morph9.Node.isCallExpression(arg) || import_ts_morph9.Node.isAsExpression(arg) || import_ts_morph9.Node.isSatisfiesExpression(arg) || import_ts_morph9.Node.isParenthesizedExpression(arg);
2406
+ }
2407
+ function syntacticZodType(arg, project) {
2408
+ let expr = arg;
2409
+ if (import_ts_morph9.Node.isIdentifier(arg)) {
2410
+ const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
2411
+ expr = resolved?.decl.getInitializer();
2412
+ }
2413
+ if (!expr || !import_ts_morph9.Node.isCallExpression(expr)) return null;
2414
+ const type = zodAstToTs(expr);
2415
+ return type === "unknown" ? null : type;
2416
+ }
2417
+
1721
2418
  // src/discovery/dto-type-resolver.ts
1722
2419
  var WRAPPER_TYPES = {
1723
2420
  // MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
@@ -1749,22 +2446,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
1749
2446
  ]);
1750
2447
  function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
1751
2448
  if (depth <= 0) return "unknown";
1752
- if (import_ts_morph6.Node.isArrayTypeNode(typeNode)) {
2449
+ if (import_ts_morph10.Node.isArrayTypeNode(typeNode)) {
1753
2450
  const elementType = typeNode.getElementTypeNode();
1754
2451
  return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
1755
2452
  }
1756
- if (import_ts_morph6.Node.isUnionTypeNode(typeNode)) {
2453
+ if (import_ts_morph10.Node.isUnionTypeNode(typeNode)) {
1757
2454
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
1758
2455
  }
1759
- if (import_ts_morph6.Node.isIntersectionTypeNode(typeNode)) {
2456
+ if (import_ts_morph10.Node.isIntersectionTypeNode(typeNode)) {
1760
2457
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
1761
2458
  }
1762
- if (import_ts_morph6.Node.isParenthesizedTypeNode(typeNode)) {
2459
+ if (import_ts_morph10.Node.isParenthesizedTypeNode(typeNode)) {
1763
2460
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
1764
2461
  }
1765
- if (import_ts_morph6.Node.isTypeReference(typeNode)) {
2462
+ if (import_ts_morph10.Node.isTypeReference(typeNode)) {
1766
2463
  const typeName = typeNode.getTypeName();
1767
- const name = import_ts_morph6.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2464
+ const name = import_ts_morph10.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
1768
2465
  const bound = subst.get(name);
1769
2466
  if (bound !== void 0) return bound;
1770
2467
  if (name === "string" || name === "number" || name === "boolean") return name;
@@ -1787,13 +2484,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
1787
2484
  dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1788
2485
  return "unknown";
1789
2486
  }
1790
- if (import_ts_morph6.Node.isTypeLiteral(typeNode)) {
2487
+ if (import_ts_morph10.Node.isTypeLiteral(typeNode)) {
1791
2488
  const members = [];
1792
2489
  for (const member of typeNode.getMembers()) {
1793
- if (import_ts_morph6.Node.isPropertySignature(member)) {
2490
+ if (import_ts_morph10.Node.isPropertySignature(member)) {
1794
2491
  const memberTypeNode = member.getTypeNode();
1795
- const memberType = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
1796
- members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType}`);
2492
+ const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
2493
+ members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
1797
2494
  } else {
1798
2495
  members.push(member.getText());
1799
2496
  }
@@ -1801,11 +2498,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
1801
2498
  return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
1802
2499
  }
1803
2500
  const kind = typeNode.getKind();
1804
- if (kind === import_ts_morph6.SyntaxKind.StringKeyword) return "string";
1805
- if (kind === import_ts_morph6.SyntaxKind.NumberKeyword) return "number";
1806
- if (kind === import_ts_morph6.SyntaxKind.BooleanKeyword) return "boolean";
1807
- if (kind === import_ts_morph6.SyntaxKind.UnknownKeyword) return "unknown";
1808
- if (kind === import_ts_morph6.SyntaxKind.AnyKeyword) return "unknown";
2501
+ if (kind === import_ts_morph10.SyntaxKind.StringKeyword) return "string";
2502
+ if (kind === import_ts_morph10.SyntaxKind.NumberKeyword) return "number";
2503
+ if (kind === import_ts_morph10.SyntaxKind.BooleanKeyword) return "boolean";
2504
+ if (kind === import_ts_morph10.SyntaxKind.UnknownKeyword) return "unknown";
2505
+ if (kind === import_ts_morph10.SyntaxKind.AnyKeyword) return "unknown";
1809
2506
  return typeNode.getText();
1810
2507
  }
1811
2508
  function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
@@ -1853,20 +2550,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
1853
2550
  const propName = prop.getName();
1854
2551
  const isOptional = prop.hasQuestionToken();
1855
2552
  const propTypeNode = prop.getTypeNode();
1856
- let propType = "unknown";
2553
+ let propType2 = "unknown";
1857
2554
  if (propTypeNode) {
1858
- propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
2555
+ propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
1859
2556
  }
1860
- lines.push(`${propName}${isOptional ? "?" : ""}: ${propType}`);
2557
+ lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
1861
2558
  }
1862
2559
  return `{ ${lines.join("; ")} }`;
1863
2560
  }
2561
+ function namedField(decorator) {
2562
+ const first = decorator.getArguments()[0];
2563
+ return first && import_ts_morph10.Node.isStringLiteral(first) ? first.getLiteralValue() : null;
2564
+ }
1864
2565
  function extractBodyType(method, sourceFile, project) {
1865
2566
  for (const param of method.getParameters()) {
1866
2567
  const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
1867
- if (!bodyDecorator) continue;
1868
- const bodyArgs = bodyDecorator.getArguments();
1869
- if (bodyArgs.length > 0) continue;
2568
+ if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
2569
+ const piped = pipeSchemaOf(bodyDecorator, project);
2570
+ if (piped) return piped.type;
1870
2571
  const typeNode = param.getTypeNode();
1871
2572
  if (typeNode) {
1872
2573
  return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
@@ -1874,11 +2575,16 @@ function extractBodyType(method, sourceFile, project) {
1874
2575
  }
1875
2576
  return null;
1876
2577
  }
2578
+ function hasPipeSchema(param, decoratorName, project) {
2579
+ const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
2580
+ return !!decorator && pipeSchemaOf(decorator, project) !== null;
2581
+ }
1877
2582
  function extractQueryType(method, sourceFile, project) {
1878
2583
  for (const param of method.getParameters()) {
1879
2584
  const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
1880
- if (!queryDecorator) continue;
1881
- if (queryDecorator.getArguments().length > 0) continue;
2585
+ if (!queryDecorator || namedField(queryDecorator) !== null) continue;
2586
+ const piped = pipeSchemaOf(queryDecorator, project);
2587
+ if (piped) return piped.type;
1882
2588
  const typeNode = param.getTypeNode();
1883
2589
  if (typeNode) {
1884
2590
  return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
@@ -1888,10 +2594,13 @@ function extractQueryType(method, sourceFile, project) {
1888
2594
  for (const param of method.getParameters()) {
1889
2595
  const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
1890
2596
  if (!queryDecorator) continue;
1891
- const queryArgs = queryDecorator.getArguments();
1892
- const nameArg = queryArgs[0];
1893
- if (!nameArg || !import_ts_morph6.Node.isStringLiteral(nameArg)) continue;
1894
- const queryName = nameArg.getLiteralValue();
2597
+ const queryName = namedField(queryDecorator);
2598
+ if (queryName === null) continue;
2599
+ const piped = pipeSchemaOf(queryDecorator, project);
2600
+ if (piped) {
2601
+ entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
2602
+ continue;
2603
+ }
1895
2604
  const typeNode = param.getTypeNode();
1896
2605
  const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
1897
2606
  entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
@@ -1901,8 +2610,8 @@ function extractQueryType(method, sourceFile, project) {
1901
2610
  function isParamOptional(param) {
1902
2611
  if (param.hasQuestionToken() || param.hasInitializer()) return true;
1903
2612
  const typeNode = param.getTypeNode();
1904
- if (typeNode && import_ts_morph6.Node.isUnionTypeNode(typeNode)) {
1905
- return typeNode.getTypeNodes().some((t) => t.getKind() === import_ts_morph6.SyntaxKind.UndefinedKeyword);
2613
+ if (typeNode && import_ts_morph10.Node.isUnionTypeNode(typeNode)) {
2614
+ return typeNode.getTypeNodes().some((t) => t.getKind() === import_ts_morph10.SyntaxKind.UndefinedKeyword);
1906
2615
  }
1907
2616
  return false;
1908
2617
  }
@@ -1914,7 +2623,7 @@ function extractParamsType(method, sourceFile, project) {
1914
2623
  const paramArgs = paramDecorator.getArguments();
1915
2624
  if (paramArgs.length === 0) continue;
1916
2625
  const nameArg = paramArgs[0];
1917
- if (!import_ts_morph6.Node.isStringLiteral(nameArg)) continue;
2626
+ if (!import_ts_morph10.Node.isStringLiteral(nameArg)) continue;
1918
2627
  const paramName = nameArg.getLiteralValue();
1919
2628
  const typeNode = param.getTypeNode();
1920
2629
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -1922,6 +2631,22 @@ function extractParamsType(method, sourceFile, project) {
1922
2631
  }
1923
2632
  return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
1924
2633
  }
2634
+ function extractPipedParamTypes(method, project) {
2635
+ const types = {};
2636
+ for (const param of method.getParameters()) {
2637
+ const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
2638
+ if (!paramDecorator) continue;
2639
+ const piped = pipeSchemaOf(paramDecorator, project);
2640
+ if (!piped) continue;
2641
+ const name = namedField(paramDecorator);
2642
+ if (name !== null) {
2643
+ types[name] = piped.type;
2644
+ } else {
2645
+ for (const field of piped.fields ?? []) types[field.name] = field.type;
2646
+ }
2647
+ }
2648
+ return Object.keys(types).length > 0 ? types : null;
2649
+ }
1925
2650
  function extractUploadedFiles(method) {
1926
2651
  const FILE = "File | Blob";
1927
2652
  const entries = [];
@@ -1935,28 +2660,28 @@ function extractUploadedFiles(method) {
1935
2660
  for (const decorator of method.getDecorators()) {
1936
2661
  if (decorator.getName() !== "UseInterceptors") continue;
1937
2662
  for (const arg of decorator.getArguments()) {
1938
- if (!import_ts_morph6.Node.isCallExpression(arg)) continue;
2663
+ if (!import_ts_morph10.Node.isCallExpression(arg)) continue;
1939
2664
  const interceptor = arg.getExpression().getText();
1940
2665
  const callArgs = arg.getArguments();
1941
2666
  const firstArg2 = callArgs[0];
1942
2667
  if (interceptor === "FileInterceptor") {
1943
- if (firstArg2 && import_ts_morph6.Node.isStringLiteral(firstArg2)) {
2668
+ if (firstArg2 && import_ts_morph10.Node.isStringLiteral(firstArg2)) {
1944
2669
  entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
1945
2670
  multipart = true;
1946
2671
  }
1947
2672
  } else if (interceptor === "FilesInterceptor") {
1948
- if (firstArg2 && import_ts_morph6.Node.isStringLiteral(firstArg2)) {
2673
+ if (firstArg2 && import_ts_morph10.Node.isStringLiteral(firstArg2)) {
1949
2674
  entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
1950
2675
  multipart = true;
1951
2676
  }
1952
2677
  } else if (interceptor === "FileFieldsInterceptor") {
1953
- if (firstArg2 && import_ts_morph6.Node.isArrayLiteralExpression(firstArg2)) {
2678
+ if (firstArg2 && import_ts_morph10.Node.isArrayLiteralExpression(firstArg2)) {
1954
2679
  for (const el of firstArg2.getElements()) {
1955
- if (!import_ts_morph6.Node.isObjectLiteralExpression(el)) continue;
2680
+ if (!import_ts_morph10.Node.isObjectLiteralExpression(el)) continue;
1956
2681
  const nameProp = el.getProperty("name");
1957
- if (nameProp && import_ts_morph6.Node.isPropertyAssignment(nameProp)) {
2682
+ if (nameProp && import_ts_morph10.Node.isPropertyAssignment(nameProp)) {
1958
2683
  const init = nameProp.getInitializer();
1959
- if (init && import_ts_morph6.Node.isStringLiteral(init)) {
2684
+ if (init && import_ts_morph10.Node.isStringLiteral(init)) {
1960
2685
  entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
1961
2686
  }
1962
2687
  }
@@ -1976,13 +2701,13 @@ function extractResponseType(method, sourceFile, project) {
1976
2701
  if (apiResponseDecorator) {
1977
2702
  const args = apiResponseDecorator.getArguments();
1978
2703
  const optsArg = args[0];
1979
- if (optsArg && import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) {
2704
+ if (optsArg && import_ts_morph10.Node.isObjectLiteralExpression(optsArg)) {
1980
2705
  for (const prop of optsArg.getProperties()) {
1981
- if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
2706
+ if (!import_ts_morph10.Node.isPropertyAssignment(prop)) continue;
1982
2707
  if (prop.getName() !== "type") continue;
1983
2708
  const val = prop.getInitializer();
1984
2709
  if (!val) continue;
1985
- if (import_ts_morph6.Node.isArrayLiteralExpression(val)) {
2710
+ if (import_ts_morph10.Node.isArrayLiteralExpression(val)) {
1986
2711
  const elements = val.getElements();
1987
2712
  const firstEl = elements[0];
1988
2713
  if (elements.length > 0 && firstEl !== void 0) {
@@ -2003,24 +2728,24 @@ function extractResponseType(method, sourceFile, project) {
2003
2728
  }
2004
2729
  function apiResponseStatus(decorator) {
2005
2730
  const optsArg = decorator.getArguments()[0];
2006
- if (!optsArg || !import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) return null;
2731
+ if (!optsArg || !import_ts_morph10.Node.isObjectLiteralExpression(optsArg)) return null;
2007
2732
  for (const prop of optsArg.getProperties()) {
2008
- if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
2733
+ if (!import_ts_morph10.Node.isPropertyAssignment(prop)) continue;
2009
2734
  if (prop.getName() !== "status") continue;
2010
2735
  const val = prop.getInitializer();
2011
- if (val && import_ts_morph6.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
2736
+ if (val && import_ts_morph10.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
2012
2737
  }
2013
2738
  return null;
2014
2739
  }
2015
2740
  function apiResponseTypeNode(decorator) {
2016
2741
  const optsArg = decorator.getArguments()[0];
2017
- if (!optsArg || !import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) return null;
2742
+ if (!optsArg || !import_ts_morph10.Node.isObjectLiteralExpression(optsArg)) return null;
2018
2743
  for (const prop of optsArg.getProperties()) {
2019
- if (!import_ts_morph6.Node.isPropertyAssignment(prop)) continue;
2744
+ if (!import_ts_morph10.Node.isPropertyAssignment(prop)) continue;
2020
2745
  if (prop.getName() !== "type") continue;
2021
2746
  const val = prop.getInitializer();
2022
2747
  if (!val) return null;
2023
- if (import_ts_morph6.Node.isArrayLiteralExpression(val)) {
2748
+ if (import_ts_morph10.Node.isArrayLiteralExpression(val)) {
2024
2749
  const first = val.getElements()[0];
2025
2750
  return first ? { node: first, isArray: true } : null;
2026
2751
  }
@@ -2038,7 +2763,7 @@ function extractErrorType(method, sourceFile, project) {
2038
2763
  const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
2039
2764
  const type = typeInfo.isArray ? `Array<${inner}>` : inner;
2040
2765
  let ref = null;
2041
- if (import_ts_morph6.Node.isIdentifier(typeInfo.node)) {
2766
+ if (import_ts_morph10.Node.isIdentifier(typeInfo.node)) {
2042
2767
  const name = typeInfo.node.getText();
2043
2768
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
2044
2769
  if (localDecl?.isExported()) {
@@ -2055,7 +2780,7 @@ function extractErrorType(method, sourceFile, project) {
2055
2780
  return null;
2056
2781
  }
2057
2782
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
2058
- if (!import_ts_morph6.Node.isIdentifier(node)) return "unknown";
2783
+ if (!import_ts_morph10.Node.isIdentifier(node)) return "unknown";
2059
2784
  const name = node.getText();
2060
2785
  const resolved = findType(name, sourceFile, project);
2061
2786
  if (resolved) {
@@ -2075,9 +2800,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
2075
2800
  var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
2076
2801
  function detectBinaryResponse(method) {
2077
2802
  const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
2078
- if (!node || !import_ts_morph6.Node.isTypeReference(node)) return false;
2803
+ if (!node || !import_ts_morph10.Node.isTypeReference(node)) return false;
2079
2804
  const typeName = node.getTypeName();
2080
- const name = import_ts_morph6.Node.isIdentifier(typeName) ? typeName.getText() : "";
2805
+ const name = import_ts_morph10.Node.isIdentifier(typeName) ? typeName.getText() : "";
2081
2806
  return BINARY_RESPONSE_TYPES.has(name);
2082
2807
  }
2083
2808
  function detectStreamElement(method) {
@@ -2092,9 +2817,9 @@ function detectStreamElement(method) {
2092
2817
  return null;
2093
2818
  }
2094
2819
  function streamContainerElement(node) {
2095
- if (!node || !import_ts_morph6.Node.isTypeReference(node)) return null;
2820
+ if (!node || !import_ts_morph10.Node.isTypeReference(node)) return null;
2096
2821
  const typeName = node.getTypeName();
2097
- const name = import_ts_morph6.Node.isIdentifier(typeName) ? typeName.getText() : "";
2822
+ const name = import_ts_morph10.Node.isIdentifier(typeName) ? typeName.getText() : "";
2098
2823
  if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
2099
2824
  return node.getTypeArguments()[0] ?? null;
2100
2825
  }
@@ -2104,9 +2829,9 @@ function hasAsQueryDecorator(method) {
2104
2829
  return method.getDecorators().some((d) => d.getName() === "AsQuery");
2105
2830
  }
2106
2831
  function unwrapNamedContainer(node, names) {
2107
- if (!node || !import_ts_morph6.Node.isTypeReference(node)) return node;
2832
+ if (!node || !import_ts_morph10.Node.isTypeReference(node)) return node;
2108
2833
  const typeName = node.getTypeName();
2109
- const name = import_ts_morph6.Node.isIdentifier(typeName) ? typeName.getText() : "";
2834
+ const name = import_ts_morph10.Node.isIdentifier(typeName) ? typeName.getText() : "";
2110
2835
  if (names.has(name)) {
2111
2836
  return node.getTypeArguments()[0] ?? node;
2112
2837
  }
@@ -2127,19 +2852,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2127
2852
  body = body ?? bodyType;
2128
2853
  }
2129
2854
  const paramsType = extractParamsType(method, sourceFile, project);
2855
+ const paramTypes = extractPipedParamTypes(method, project);
2130
2856
  const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
2131
2857
  const errorInfo = extractErrorType(method, sourceFile, project);
2132
- if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
2858
+ if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
2133
2859
  return null;
2134
2860
  }
2135
2861
  let bodyRef = null;
2136
2862
  let queryRef = null;
2137
2863
  let responseRef = null;
2138
2864
  for (const param of method.getParameters()) {
2139
- if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
2865
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
2140
2866
  bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
2141
2867
  }
2142
- if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
2868
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
2143
2869
  queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
2144
2870
  }
2145
2871
  }
@@ -2152,11 +2878,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2152
2878
  if (apiResp) {
2153
2879
  const args = apiResp.getArguments();
2154
2880
  const optsArg = args[0];
2155
- if (optsArg && import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) {
2881
+ if (optsArg && import_ts_morph10.Node.isObjectLiteralExpression(optsArg)) {
2156
2882
  for (const prop of optsArg.getProperties()) {
2157
- if (import_ts_morph6.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
2883
+ if (import_ts_morph10.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
2158
2884
  const val = prop.getInitializer();
2159
- if (val && import_ts_morph6.Node.isIdentifier(val)) {
2885
+ if (val && import_ts_morph10.Node.isIdentifier(val)) {
2160
2886
  const name = val.getText();
2161
2887
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
2162
2888
  if (localDecl?.isExported()) {
@@ -2192,6 +2918,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2192
2918
  response,
2193
2919
  error: errorInfo?.type ?? null,
2194
2920
  params: paramsType,
2921
+ paramTypes,
2195
2922
  queryRef,
2196
2923
  bodyRef,
2197
2924
  responseRef,
@@ -2212,6 +2939,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2212
2939
  function resolveParamClass(method, decoratorName, sourceFile, project) {
2213
2940
  for (const param of method.getParameters()) {
2214
2941
  if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
2942
+ if (hasPipeSchema(param, decoratorName, project)) continue;
2215
2943
  const typeNode = param.getTypeNode();
2216
2944
  if (!typeNode) continue;
2217
2945
  const text = typeNode.getText().replace(/\[\]$/, "");
@@ -2224,129 +2952,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
2224
2952
  return null;
2225
2953
  }
2226
2954
 
2227
- // src/discovery/zod-ast-to-ts.ts
2228
- var import_ts_morph7 = require("ts-morph");
2229
- function zodAstToTs(node) {
2230
- if (!import_ts_morph7.Node.isCallExpression(node)) return "unknown";
2231
- const expr = node.getExpression();
2232
- if (import_ts_morph7.Node.isPropertyAccessExpression(expr)) {
2233
- const methodName = expr.getName();
2234
- const receiver = expr.getExpression();
2235
- if (methodName === "optional") {
2236
- return `${zodAstToTs(receiver)} | undefined`;
2237
- }
2238
- if (methodName === "nullable") {
2239
- return `${zodAstToTs(receiver)} | null`;
2240
- }
2241
- const args = node.getArguments();
2242
- switch (methodName) {
2243
- case "string":
2244
- return "string";
2245
- case "number":
2246
- return "number";
2247
- case "boolean":
2248
- return "boolean";
2249
- case "unknown":
2250
- return "unknown";
2251
- case "any":
2252
- return "unknown";
2253
- case "literal": {
2254
- const lit = args[0];
2255
- if (!lit) return "unknown";
2256
- if (import_ts_morph7.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2257
- if (import_ts_morph7.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2258
- if (lit.getKind() === import_ts_morph7.SyntaxKind.TrueKeyword) return "true";
2259
- if (lit.getKind() === import_ts_morph7.SyntaxKind.FalseKeyword) return "false";
2260
- return "unknown";
2261
- }
2262
- case "enum": {
2263
- const arrArg = args[0];
2264
- if (!arrArg || !import_ts_morph7.Node.isArrayLiteralExpression(arrArg)) return "unknown";
2265
- const members = arrArg.getElements().map(
2266
- (el) => import_ts_morph7.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2267
- );
2268
- return members.join(" | ");
2269
- }
2270
- case "array": {
2271
- const inner = args[0];
2272
- if (!inner) return "unknown";
2273
- return `Array<${zodAstToTs(inner)}>`;
2274
- }
2275
- case "object": {
2276
- const objArg = args[0];
2277
- if (!objArg || !import_ts_morph7.Node.isObjectLiteralExpression(objArg)) return "unknown";
2278
- const lines = [];
2279
- for (const prop of objArg.getProperties()) {
2280
- if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
2281
- const key = prop.getName();
2282
- const valNode = prop.getInitializer();
2283
- if (!valNode) continue;
2284
- const tsType = zodAstToTs(valNode);
2285
- const isOpt = isOptionalChain(valNode);
2286
- lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
2287
- }
2288
- return `{ ${lines.join("; ")} }`;
2289
- }
2290
- case "union": {
2291
- const arrArg = args[0];
2292
- if (!arrArg || !import_ts_morph7.Node.isArrayLiteralExpression(arrArg)) return "unknown";
2293
- return arrArg.getElements().map(zodAstToTs).join(" | ");
2294
- }
2295
- case "record": {
2296
- const valArg = args.length === 1 ? args[0] : args[1];
2297
- if (!valArg) return "unknown";
2298
- return `Record<string, ${zodAstToTs(valArg)}>`;
2299
- }
2300
- case "tuple": {
2301
- const arrArg = args[0];
2302
- if (!arrArg || !import_ts_morph7.Node.isArrayLiteralExpression(arrArg)) return "unknown";
2303
- return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2304
- }
2305
- default:
2306
- return "unknown";
2307
- }
2308
- }
2309
- return "unknown";
2310
- }
2311
- function isOptionalChain(node) {
2312
- if (!import_ts_morph7.Node.isCallExpression(node)) return false;
2313
- const expr = node.getExpression();
2314
- return import_ts_morph7.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2315
- }
2316
- function parseDefineContractCall(callExpr) {
2317
- if (!import_ts_morph7.Node.isCallExpression(callExpr)) return null;
2318
- const callee = callExpr.getExpression();
2319
- const calleeName = import_ts_morph7.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph7.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
2320
- if (calleeName !== "defineContract") return null;
2321
- const args = callExpr.getArguments();
2322
- const optsArg = args[0];
2323
- if (!optsArg || !import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) return null;
2324
- let query = null;
2325
- let body = null;
2326
- let response = "unknown";
2327
- let error = null;
2328
- let bodyZodText = null;
2329
- let queryZodText = null;
2330
- for (const prop of optsArg.getProperties()) {
2331
- if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
2332
- const propName = prop.getName();
2333
- const val = prop.getInitializer();
2334
- if (!val) continue;
2335
- if (propName === "query") {
2336
- query = zodAstToTs(val);
2337
- queryZodText = val.getText();
2338
- } else if (propName === "body") {
2339
- body = zodAstToTs(val);
2340
- bodyZodText = val.getText();
2341
- } else if (propName === "response") {
2342
- response = zodAstToTs(val);
2343
- } else if (propName === "error") {
2344
- error = zodAstToTs(val);
2345
- }
2346
- }
2347
- return { query, body, response, error, bodyZodText, queryZodText };
2348
- }
2349
-
2350
2955
  // src/discovery/contracts-fast.ts
2351
2956
  function resolveTsconfigPath(cwd, tsconfig) {
2352
2957
  return tsconfig ? (0, import_node_path3.resolve)(tsconfig) : (0, import_node_path3.join)(cwd, "tsconfig.json");
@@ -2358,25 +2963,25 @@ var NO_TSCONFIG_OPTIONS = {
2358
2963
  };
2359
2964
  function loadDiscoveryTsconfig(tsconfigPath) {
2360
2965
  if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
2361
- const read = import_ts_morph8.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph8.ts.sys.readFile(path));
2966
+ const read = import_ts_morph11.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph11.ts.sys.readFile(path));
2362
2967
  if (read.error) {
2363
2968
  return {
2364
2969
  options: null,
2365
2970
  files: [tsconfigPath],
2366
- error: import_ts_morph8.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
2971
+ error: import_ts_morph11.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
2367
2972
  };
2368
2973
  }
2369
2974
  const host = {
2370
- useCaseSensitiveFileNames: import_ts_morph8.ts.sys.useCaseSensitiveFileNames,
2975
+ useCaseSensitiveFileNames: import_ts_morph11.ts.sys.useCaseSensitiveFileNames,
2371
2976
  // The one line that keeps a tsconfig from being a filesystem walk.
2372
2977
  readDirectory: () => [],
2373
- fileExists: (path) => import_ts_morph8.ts.sys.fileExists(path),
2978
+ fileExists: (path) => import_ts_morph11.ts.sys.fileExists(path),
2374
2979
  // `extends` is resolved through the host, so a consumer whose `paths` live
2375
2980
  // in a base tsconfig keeps working.
2376
- readFile: (path) => import_ts_morph8.ts.sys.readFile(path)
2981
+ readFile: (path) => import_ts_morph11.ts.sys.readFile(path)
2377
2982
  };
2378
- const sourceFile = import_ts_morph8.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph8.ts.sys.readFile(path));
2379
- const parsed = import_ts_morph8.ts.parseJsonSourceFileConfigFileContent(
2983
+ const sourceFile = import_ts_morph11.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph11.ts.sys.readFile(path));
2984
+ const parsed = import_ts_morph11.ts.parseJsonSourceFileConfigFileContent(
2380
2985
  sourceFile,
2381
2986
  host,
2382
2987
  (0, import_node_path3.dirname)(tsconfigPath),
@@ -2399,7 +3004,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
2399
3004
  `[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.`
2400
3005
  );
2401
3006
  }
2402
- return new import_ts_morph8.Project({
3007
+ return new import_ts_morph11.Project({
2403
3008
  compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
2404
3009
  skipAddingFilesFromTsConfig: true,
2405
3010
  skipLoadingLibFiles: true,
@@ -2504,15 +3109,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
2504
3109
  clearTypeResolutionCaches(this.project);
2505
3110
  clearEnumCache(this.project);
2506
3111
  clearMixinTypeProject();
3112
+ clearSchemaTypeProject(this.project);
2507
3113
  return extractRoutesFrom(this.project, this.controllerPaths);
2508
3114
  }
2509
3115
  };
2510
3116
  function decoratorStringArg(decoratorExpr) {
2511
3117
  if (!decoratorExpr) return void 0;
2512
- if (import_ts_morph8.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
2513
- if (import_ts_morph8.Node.isArrayLiteralExpression(decoratorExpr)) {
3118
+ if (import_ts_morph11.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3119
+ if (import_ts_morph11.Node.isArrayLiteralExpression(decoratorExpr)) {
2514
3120
  const first = decoratorExpr.getElements()[0];
2515
- if (first && import_ts_morph8.Node.isStringLiteral(first)) return first.getLiteralValue();
3121
+ if (first && import_ts_morph11.Node.isStringLiteral(first)) return first.getLiteralValue();
2516
3122
  }
2517
3123
  return void 0;
2518
3124
  }
@@ -2633,9 +3239,9 @@ function extractContractRoute(args) {
2633
3239
  let contractDef = null;
2634
3240
  let bodyZodRef = null;
2635
3241
  let queryZodRef = null;
2636
- if (import_ts_morph8.Node.isCallExpression(firstDecoratorArg)) {
2637
- contractDef = parseDefineContractCall(firstDecoratorArg);
2638
- } else if (import_ts_morph8.Node.isIdentifier(firstDecoratorArg)) {
3242
+ if (import_ts_morph11.Node.isCallExpression(firstDecoratorArg)) {
3243
+ contractDef = parseDefineContractCall(firstDecoratorArg, project);
3244
+ } else if (import_ts_morph11.Node.isIdentifier(firstDecoratorArg)) {
2639
3245
  const identName = firstDecoratorArg.getText();
2640
3246
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
2641
3247
  if (!resolvedVar) {
@@ -2647,7 +3253,7 @@ function extractContractRoute(args) {
2647
3253
  const { decl: varDecl, file: declFile } = resolvedVar;
2648
3254
  const initializer = varDecl.getInitializer();
2649
3255
  if (!initializer) return null;
2650
- contractDef = parseDefineContractCall(initializer);
3256
+ contractDef = parseDefineContractCall(initializer, project);
2651
3257
  if (contractDef && varDecl.isExported()) {
2652
3258
  const filePath = declFile.getFilePath();
2653
3259
  if (contractDef.body !== null) {
@@ -2718,6 +3324,7 @@ function extractDtoRoute(args) {
2718
3324
  body: dtoContract?.body ?? null,
2719
3325
  response: mixinResponse ?? dtoContract?.response ?? "unknown",
2720
3326
  error: dtoContract?.error ?? null,
3327
+ paramTypes: dtoContract?.paramTypes ?? null,
2721
3328
  queryRef: dtoContract?.queryRef ?? null,
2722
3329
  bodyRef: dtoContract?.bodyRef ?? null,
2723
3330
  responseRef: dtoContract?.responseRef ?? null,
@@ -2789,32 +3396,86 @@ function extractFromSourceFile(sourceFile, project) {
2789
3396
  return routes;
2790
3397
  }
2791
3398
 
2792
- // src/generate.ts
2793
- var import_promises11 = require("fs/promises");
2794
- var import_node_path15 = require("path");
2795
-
2796
3399
  // src/discovery/pages.ts
2797
3400
  var import_promises2 = require("fs/promises");
2798
3401
  var import_node_path4 = require("path");
2799
3402
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2800
3403
  var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
3404
+ var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
3405
+ function globStaticBase(glob) {
3406
+ const segments = glob.split("/");
3407
+ const staticSegments = [];
3408
+ for (const segment of segments.slice(0, -1)) {
3409
+ if (MAGIC_SEGMENT_RE.test(segment)) break;
3410
+ staticSegments.push(segment);
3411
+ }
3412
+ return staticSegments.join("/");
3413
+ }
2801
3414
  async function discoverPages(opts) {
2802
- const allFiles = await (0, import_fast_glob2.default)(opts.glob, { cwd: opts.cwd, absolute: true });
2803
- const files = allFiles.filter((f) => !NON_PAGE_FILE_RE.test(f));
2804
- files.sort();
2805
- const globStatic = opts.glob.split("*")[0]?.replace(/\/$/, "") ?? "";
2806
- const pagesBase = (0, import_node_path4.join)(opts.cwd, globStatic);
3415
+ const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
3416
+ const prefix = opts.prefix ?? "";
3417
+ const scope = opts.scope ?? "default";
3418
+ const seenFiles = /* @__PURE__ */ new Set();
2807
3419
  const out = [];
2808
- for (const file of files) {
2809
- const rel = (0, import_node_path4.relative)(opts.cwd, file);
2810
- const nameRel = (0, import_node_path4.relative)(pagesBase, file);
2811
- const name = computeName(nameRel, opts.componentNameStrategy);
2812
- const source = await (0, import_promises2.readFile)(file, "utf8");
2813
- const propsSource = extractPropsSource(source, opts.propsExport);
2814
- out.push({ name, absolutePath: file, relativePath: rel, propsSource });
3420
+ for (const glob of globs) {
3421
+ const files = (await (0, import_fast_glob2.default)(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
3422
+ const pagesBase = (0, import_node_path4.join)(opts.cwd, globStaticBase(glob));
3423
+ for (const file of files) {
3424
+ seenFiles.add(file);
3425
+ const rel = (0, import_node_path4.relative)(opts.cwd, file);
3426
+ const nameRel = (0, import_node_path4.relative)(pagesBase, file).split(import_node_path4.sep).join("/");
3427
+ const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
3428
+ const source = await (0, import_promises2.readFile)(file, "utf8");
3429
+ const propsSource = extractPropsSource(source, opts.propsExport);
3430
+ out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
3431
+ }
3432
+ }
3433
+ assertUniqueNames(out);
3434
+ return out;
3435
+ }
3436
+ async function discoverPageScopes(config) {
3437
+ const pages = config.pages;
3438
+ if (!pages) return [];
3439
+ const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
3440
+ const out = await discoverPages({
3441
+ ...common,
3442
+ glob: pages.glob,
3443
+ componentNameStrategy: pages.componentNameStrategy
3444
+ });
3445
+ for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
3446
+ out.push(
3447
+ ...await discoverPages({
3448
+ ...common,
3449
+ glob: scopeConfig.glob,
3450
+ componentNameStrategy: scopeConfig.componentNameStrategy,
3451
+ prefix: scopeConfig.prefix,
3452
+ scope
3453
+ })
3454
+ );
2815
3455
  }
3456
+ assertUniqueNames(out);
2816
3457
  return out;
2817
3458
  }
3459
+ function allPageGlobs(config) {
3460
+ if (!config.pages) return [];
3461
+ const asList = (glob) => typeof glob === "string" ? [glob] : glob;
3462
+ return [
3463
+ ...asList(config.pages.glob),
3464
+ ...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
3465
+ ];
3466
+ }
3467
+ function assertUniqueNames(pages) {
3468
+ const byName = /* @__PURE__ */ new Map();
3469
+ for (const page of pages) {
3470
+ const previous = byName.get(page.name);
3471
+ if (previous) {
3472
+ throw new Error(
3473
+ `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.`
3474
+ );
3475
+ }
3476
+ byName.set(page.name, page);
3477
+ }
3478
+ }
2818
3479
  function computeName(rel, strat) {
2819
3480
  if (typeof strat === "function") return strat(rel);
2820
3481
  const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
@@ -2845,9 +3506,13 @@ function extractPropsSource(source, exportName) {
2845
3506
  return source.slice(start);
2846
3507
  }
2847
3508
 
3509
+ // src/generate.ts
3510
+ var import_promises11 = require("fs/promises");
3511
+ var import_node_path15 = require("path");
3512
+
2848
3513
  // src/discovery/shared-props.ts
2849
3514
  var import_node_path5 = require("path");
2850
- var import_ts_morph9 = require("ts-morph");
3515
+ var import_ts_morph12 = require("ts-morph");
2851
3516
  function discoverSharedProps(project, moduleEntry) {
2852
3517
  try {
2853
3518
  let sourceFile = project.getSourceFile(moduleEntry);
@@ -2873,14 +3538,14 @@ function discoverSharedPropsFromConfig(config) {
2873
3538
  const tsconfigPath = config.app.tsconfig ?? (0, import_node_path5.join)(config.codegen.cwd, "tsconfig.json");
2874
3539
  let project;
2875
3540
  try {
2876
- project = new import_ts_morph9.Project({
3541
+ project = new import_ts_morph12.Project({
2877
3542
  tsConfigFilePath: tsconfigPath,
2878
3543
  skipAddingFilesFromTsConfig: true,
2879
3544
  skipLoadingLibFiles: true,
2880
3545
  skipFileDependencyResolution: true
2881
3546
  });
2882
3547
  } catch {
2883
- project = new import_ts_morph9.Project({
3548
+ project = new import_ts_morph12.Project({
2884
3549
  skipAddingFilesFromTsConfig: true,
2885
3550
  skipLoadingLibFiles: true,
2886
3551
  skipFileDependencyResolution: true,
@@ -2893,13 +3558,13 @@ function discoverSharedPropsFromConfig(config) {
2893
3558
  }
2894
3559
  }
2895
3560
  function findForRootCall(sourceFile) {
2896
- const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph9.SyntaxKind.CallExpression);
3561
+ const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph12.SyntaxKind.CallExpression);
2897
3562
  for (const call of callExpressions) {
2898
3563
  const expr = call.getExpression();
2899
- if (!import_ts_morph9.Node.isPropertyAccessExpression(expr)) continue;
3564
+ if (!import_ts_morph12.Node.isPropertyAccessExpression(expr)) continue;
2900
3565
  const methodName = expr.getName();
2901
3566
  const objectExpr = expr.getExpression();
2902
- if (methodName === "forRoot" && import_ts_morph9.Node.isIdentifier(objectExpr)) {
3567
+ if (methodName === "forRoot" && import_ts_morph12.Node.isIdentifier(objectExpr)) {
2903
3568
  const name = objectExpr.getText();
2904
3569
  if (name === "InertiaModule") {
2905
3570
  return call;
@@ -2909,19 +3574,19 @@ function findForRootCall(sourceFile) {
2909
3574
  return null;
2910
3575
  }
2911
3576
  function findShareInitializer(forRootCall) {
2912
- if (!import_ts_morph9.Node.isCallExpression(forRootCall)) return null;
3577
+ if (!import_ts_morph12.Node.isCallExpression(forRootCall)) return null;
2913
3578
  const args = forRootCall.getArguments();
2914
3579
  const firstArg2 = args[0];
2915
- if (!firstArg2 || !import_ts_morph9.Node.isObjectLiteralExpression(firstArg2)) return null;
3580
+ if (!firstArg2 || !import_ts_morph12.Node.isObjectLiteralExpression(firstArg2)) return null;
2916
3581
  for (const prop of firstArg2.getProperties()) {
2917
- if (import_ts_morph9.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
3582
+ if (import_ts_morph12.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
2918
3583
  return prop.getInitializer() ?? null;
2919
3584
  }
2920
3585
  }
2921
3586
  return null;
2922
3587
  }
2923
3588
  function extractShareType(node, sourceFile, project) {
2924
- if (import_ts_morph9.Node.isIdentifier(node)) {
3589
+ if (import_ts_morph12.Node.isIdentifier(node)) {
2925
3590
  const ref = resolveIdentifierToImportRef(node, sourceFile, project);
2926
3591
  if (ref) {
2927
3592
  return {
@@ -2931,22 +3596,22 @@ function extractShareType(node, sourceFile, project) {
2931
3596
  };
2932
3597
  }
2933
3598
  }
2934
- if (import_ts_morph9.Node.isArrowFunction(node)) {
3599
+ if (import_ts_morph12.Node.isArrowFunction(node)) {
2935
3600
  const result = extractFromFunctionLike(node, sourceFile);
2936
3601
  return result ? { ...result, isImportRef: false } : null;
2937
3602
  }
2938
- if (import_ts_morph9.Node.isFunctionExpression(node)) {
3603
+ if (import_ts_morph12.Node.isFunctionExpression(node)) {
2939
3604
  const result = extractFromFunctionLike(node, sourceFile);
2940
3605
  return result ? { ...result, isImportRef: false } : null;
2941
3606
  }
2942
- if (import_ts_morph9.Node.isObjectLiteralExpression(node)) {
3607
+ if (import_ts_morph12.Node.isObjectLiteralExpression(node)) {
2943
3608
  const result = extractFromObjectLiteral(node);
2944
3609
  return result ? { ...result, isImportRef: false } : null;
2945
3610
  }
2946
3611
  return null;
2947
3612
  }
2948
3613
  function resolveIdentifierToImportRef(id, sourceFile, project) {
2949
- if (!import_ts_morph9.Node.isIdentifier(id)) return null;
3614
+ if (!import_ts_morph12.Node.isIdentifier(id)) return null;
2950
3615
  const name = id.getText();
2951
3616
  const localFunc = sourceFile.getFunction(name);
2952
3617
  if (localFunc?.isExported()) {
@@ -2980,37 +3645,37 @@ function resolveIdentifierToImportRef(id, sourceFile, project) {
2980
3645
  return null;
2981
3646
  }
2982
3647
  function extractFromFunctionLike(node, _sourceFile) {
2983
- const returnTypeNode = import_ts_morph9.Node.isArrowFunction(node) || import_ts_morph9.Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
3648
+ const returnTypeNode = import_ts_morph12.Node.isArrowFunction(node) || import_ts_morph12.Node.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
2984
3649
  if (returnTypeNode) {
2985
3650
  return extractFromReturnTypeAnnotation(returnTypeNode);
2986
3651
  }
2987
- if (import_ts_morph9.Node.isArrowFunction(node)) {
3652
+ if (import_ts_morph12.Node.isArrowFunction(node)) {
2988
3653
  const body = node.getBody();
2989
- if (import_ts_morph9.Node.isParenthesizedExpression(body)) {
3654
+ if (import_ts_morph12.Node.isParenthesizedExpression(body)) {
2990
3655
  const inner = body.getExpression();
2991
- if (import_ts_morph9.Node.isObjectLiteralExpression(inner)) {
3656
+ if (import_ts_morph12.Node.isObjectLiteralExpression(inner)) {
2992
3657
  return extractFromObjectLiteral(inner);
2993
3658
  }
2994
3659
  }
2995
- if (import_ts_morph9.Node.isObjectLiteralExpression(body)) {
3660
+ if (import_ts_morph12.Node.isObjectLiteralExpression(body)) {
2996
3661
  return extractFromObjectLiteral(body);
2997
3662
  }
2998
- if (import_ts_morph9.Node.isBlock(body)) {
3663
+ if (import_ts_morph12.Node.isBlock(body)) {
2999
3664
  return extractFromBlockReturn(body);
3000
3665
  }
3001
3666
  }
3002
- if (import_ts_morph9.Node.isFunctionExpression(node)) {
3667
+ if (import_ts_morph12.Node.isFunctionExpression(node)) {
3003
3668
  const body = node.getBody();
3004
- if (import_ts_morph9.Node.isBlock(body)) {
3669
+ if (import_ts_morph12.Node.isBlock(body)) {
3005
3670
  return extractFromBlockReturn(body);
3006
3671
  }
3007
3672
  }
3008
3673
  return null;
3009
3674
  }
3010
3675
  function extractFromReturnTypeAnnotation(typeNode) {
3011
- if (import_ts_morph9.Node.isTypeReference(typeNode)) {
3676
+ if (import_ts_morph12.Node.isTypeReference(typeNode)) {
3012
3677
  const typeName = typeNode.getTypeName();
3013
- if (import_ts_morph9.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
3678
+ if (import_ts_morph12.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
3014
3679
  const typeArgs = typeNode.getTypeArguments();
3015
3680
  const firstArg2 = typeArgs[0];
3016
3681
  if (firstArg2) {
@@ -3019,10 +3684,10 @@ function extractFromReturnTypeAnnotation(typeNode) {
3019
3684
  return null;
3020
3685
  }
3021
3686
  }
3022
- if (import_ts_morph9.Node.isTypeLiteral(typeNode)) {
3687
+ if (import_ts_morph12.Node.isTypeLiteral(typeNode)) {
3023
3688
  const properties = [];
3024
3689
  for (const member of typeNode.getMembers()) {
3025
- if (import_ts_morph9.Node.isPropertySignature(member)) {
3690
+ if (import_ts_morph12.Node.isPropertySignature(member)) {
3026
3691
  const name = member.getName();
3027
3692
  const memberTypeNode = member.getTypeNode();
3028
3693
  const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
@@ -3036,19 +3701,19 @@ function extractFromReturnTypeAnnotation(typeNode) {
3036
3701
  return null;
3037
3702
  }
3038
3703
  function extractFromBlockReturn(block) {
3039
- if (!import_ts_morph9.Node.isBlock(block)) return null;
3704
+ if (!import_ts_morph12.Node.isBlock(block)) return null;
3040
3705
  const statements = block.getStatements();
3041
3706
  for (let i = statements.length - 1; i >= 0; i--) {
3042
3707
  const stmt = statements[i];
3043
- if (!import_ts_morph9.Node.isReturnStatement(stmt)) continue;
3708
+ if (!import_ts_morph12.Node.isReturnStatement(stmt)) continue;
3044
3709
  const expr = stmt.getExpression();
3045
3710
  if (!expr) continue;
3046
- if (import_ts_morph9.Node.isObjectLiteralExpression(expr)) {
3711
+ if (import_ts_morph12.Node.isObjectLiteralExpression(expr)) {
3047
3712
  return extractFromObjectLiteral(expr);
3048
3713
  }
3049
- if (import_ts_morph9.Node.isParenthesizedExpression(expr)) {
3714
+ if (import_ts_morph12.Node.isParenthesizedExpression(expr)) {
3050
3715
  const inner = expr.getExpression();
3051
- if (import_ts_morph9.Node.isObjectLiteralExpression(inner)) {
3716
+ if (import_ts_morph12.Node.isObjectLiteralExpression(inner)) {
3052
3717
  return extractFromObjectLiteral(inner);
3053
3718
  }
3054
3719
  }
@@ -3057,10 +3722,10 @@ function extractFromBlockReturn(block) {
3057
3722
  return null;
3058
3723
  }
3059
3724
  function extractFromObjectLiteral(objLiteral) {
3060
- if (!import_ts_morph9.Node.isObjectLiteralExpression(objLiteral)) return null;
3725
+ if (!import_ts_morph12.Node.isObjectLiteralExpression(objLiteral)) return null;
3061
3726
  const properties = [];
3062
3727
  for (const prop of objLiteral.getProperties()) {
3063
- if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
3728
+ if (!import_ts_morph12.Node.isPropertyAssignment(prop)) continue;
3064
3729
  const name = prop.getName();
3065
3730
  const initializer = prop.getInitializer();
3066
3731
  if (!initializer) continue;
@@ -3072,21 +3737,21 @@ function extractFromObjectLiteral(objLiteral) {
3072
3737
  return { typeString, properties, isImportRef: false };
3073
3738
  }
3074
3739
  function inferExpressionType(node) {
3075
- if (import_ts_morph9.Node.isStringLiteral(node)) return "string";
3076
- if (import_ts_morph9.Node.isTemplateExpression(node) || import_ts_morph9.Node.isNoSubstitutionTemplateLiteral(node))
3740
+ if (import_ts_morph12.Node.isStringLiteral(node)) return "string";
3741
+ if (import_ts_morph12.Node.isTemplateExpression(node) || import_ts_morph12.Node.isNoSubstitutionTemplateLiteral(node))
3077
3742
  return "string";
3078
- if (import_ts_morph9.Node.isNumericLiteral(node)) return "number";
3079
- if (node.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword || node.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) {
3743
+ if (import_ts_morph12.Node.isNumericLiteral(node)) return "number";
3744
+ if (node.getKind() === import_ts_morph12.SyntaxKind.TrueKeyword || node.getKind() === import_ts_morph12.SyntaxKind.FalseKeyword) {
3080
3745
  return "boolean";
3081
3746
  }
3082
- if (node.getKind() === import_ts_morph9.SyntaxKind.NullKeyword) return "null";
3083
- if (import_ts_morph9.Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
3084
- if (import_ts_morph9.Node.isObjectLiteralExpression(node)) {
3747
+ if (node.getKind() === import_ts_morph12.SyntaxKind.NullKeyword) return "null";
3748
+ if (import_ts_morph12.Node.isIdentifier(node) && node.getText() === "undefined") return "undefined";
3749
+ if (import_ts_morph12.Node.isObjectLiteralExpression(node)) {
3085
3750
  const props = node.getProperties();
3086
3751
  if (props.length === 0) return "Record<string, unknown>";
3087
3752
  const entries = [];
3088
3753
  for (const prop of props) {
3089
- if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
3754
+ if (!import_ts_morph12.Node.isPropertyAssignment(prop)) continue;
3090
3755
  const key = prop.getName();
3091
3756
  const init = prop.getInitializer();
3092
3757
  if (!init) continue;
@@ -3095,23 +3760,23 @@ function inferExpressionType(node) {
3095
3760
  if (entries.length === 0) return "Record<string, unknown>";
3096
3761
  return `{ ${entries.join("; ")} }`;
3097
3762
  }
3098
- if (import_ts_morph9.Node.isArrayLiteralExpression(node)) {
3763
+ if (import_ts_morph12.Node.isArrayLiteralExpression(node)) {
3099
3764
  const elements = node.getElements();
3100
3765
  if (elements.length === 0) return "Array<unknown>";
3101
3766
  const first = elements[0];
3102
3767
  if (first) return `Array<${inferExpressionType(first)}>`;
3103
3768
  return "Array<unknown>";
3104
3769
  }
3105
- if (import_ts_morph9.Node.isConditionalExpression(node)) {
3770
+ if (import_ts_morph12.Node.isConditionalExpression(node)) {
3106
3771
  const whenTrue = inferExpressionType(node.getWhenTrue());
3107
3772
  const whenFalse = inferExpressionType(node.getWhenFalse());
3108
3773
  if (whenTrue === whenFalse) return whenTrue;
3109
3774
  return `${whenTrue} | ${whenFalse}`;
3110
3775
  }
3111
- if (import_ts_morph9.Node.isParenthesizedExpression(node)) {
3776
+ if (import_ts_morph12.Node.isParenthesizedExpression(node)) {
3112
3777
  return inferExpressionType(node.getExpression());
3113
3778
  }
3114
- if (import_ts_morph9.Node.isAsExpression(node)) {
3779
+ if (import_ts_morph12.Node.isAsExpression(node)) {
3115
3780
  const typeNode = node.getTypeNode();
3116
3781
  if (typeNode) return typeNode.getText();
3117
3782
  }
@@ -3124,7 +3789,7 @@ var import_node_path7 = require("path");
3124
3789
 
3125
3790
  // src/extension/registry.ts
3126
3791
  var import_node_path6 = require("path");
3127
- var import_ts_morph10 = require("ts-morph");
3792
+ var import_ts_morph13 = require("ts-morph");
3128
3793
  function resolveApiSlots(extensions) {
3129
3794
  let layer;
3130
3795
  let layerOwner;
@@ -3175,7 +3840,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
3175
3840
  },
3176
3841
  project() {
3177
3842
  if (!project) {
3178
- project = new import_ts_morph10.Project({
3843
+ project = new import_ts_morph13.Project({
3179
3844
  skipAddingFilesFromTsConfig: true,
3180
3845
  skipLoadingLibFiles: true,
3181
3846
  skipFileDependencyResolution: true,
@@ -3293,10 +3958,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
3293
3958
  insertIntoTree(branch.children, rest, leaf, fullName);
3294
3959
  }
3295
3960
  }
3296
- function buildParamsType(params) {
3961
+ function buildParamsType(params, paramTypes) {
3297
3962
  const pathParams = params.filter((p) => p.source === "path");
3298
3963
  if (pathParams.length === 0) return "never";
3299
- return `{ ${pathParams.map((p) => `${p.name}: string`).join("; ")} }`;
3964
+ const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
3965
+ return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
3300
3966
  }
3301
3967
  function hasPathParams(params) {
3302
3968
  return params.some((p) => p.source === "path");
@@ -3390,7 +4056,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
3390
4056
  }
3391
4057
  const response = buildResponseType(c, outDir, serialization);
3392
4058
  const error = buildErrorType(c);
3393
- const params = buildParamsType(c.params);
4059
+ const params = buildParamsType(c.params, c.contractSource.paramTypes);
3394
4060
  const safeMethod = JSON.stringify(method);
3395
4061
  const safeUrl = JSON.stringify(c.path);
3396
4062
  const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
@@ -3478,28 +4144,28 @@ function renderFetcherRequest(req, binaryResponse) {
3478
4144
  }
3479
4145
  function emitReqHelper() {
3480
4146
  return [
3481
- "/** Awaitable request handle. `await api.x.y({...})` runs the fetch; extensions add query/mutation helpers. */",
3482
- "type __Req<R> = {",
3483
- " then<T1 = R, T2 = never>(",
4147
+ "/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
4148
+ "export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
4149
+ "class __Request<R> extends Promise<R> {",
4150
+ " static override get [Symbol.species](): PromiseConstructor {",
4151
+ " return Promise;",
4152
+ " }",
4153
+ " private __run: () => Promise<R>;",
4154
+ " private __p: Promise<R> | undefined;",
4155
+ " constructor(run: () => Promise<R>) {",
4156
+ " super(() => {});",
4157
+ " this.__run = run;",
4158
+ " }",
4159
+ " override then<T1 = R, T2 = never>(",
3484
4160
  " onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
3485
4161
  " onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
3486
- " ): Promise<T1 | T2>;",
3487
- " catch<T = never>(onrejected?: ((reason: unknown) => T | PromiseLike<T>) | null): Promise<R | T>;",
3488
- " finally(onfinally?: (() => void) | null): Promise<R>;",
3489
- " fetch(): Promise<R>;",
3490
- "};",
3491
- "function __req<R>(run: () => Promise<R>): __Req<R> {",
3492
- " let __p: Promise<R> | undefined;",
3493
- " const __promise = () => {",
3494
- " __p ??= run();",
3495
- " return __p;",
3496
- " };",
3497
- " return {",
3498
- " then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
3499
- " catch: (onrejected) => __promise().catch(onrejected),",
3500
- " finally: (onfinally) => __promise().finally(onfinally),",
3501
- " fetch: run,",
3502
- " };",
4162
+ " ): Promise<T1 | T2> {",
4163
+ " this.__p ??= this.__run();",
4164
+ " return this.__p.then(onfulfilled, onrejected);",
4165
+ " }",
4166
+ "}",
4167
+ "function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
4168
+ " return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
3503
4169
  "}",
3504
4170
  ""
3505
4171
  ];
@@ -3517,18 +4183,26 @@ function emitFilterFieldGuard() {
3517
4183
  ];
3518
4184
  }
3519
4185
  function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
3520
- const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
3521
- lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
4186
+ const memberLines = [];
3522
4187
  if (req.filterFieldsExpr) {
3523
- lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
4188
+ memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
3524
4189
  }
3525
4190
  if (streamExpr) {
3526
- lines.push(`${pad} stream: () => ${streamExpr},`);
4191
+ memberLines.push(`${pad} stream: () => ${streamExpr},`);
3527
4192
  }
3528
4193
  for (const [name, value] of Object.entries(members)) {
3529
- lines.push(`${pad} ${name}: ${value},`);
3530
- }
3531
- lines.push(`${pad}}),`);
4194
+ memberLines.push(`${pad} ${name}: ${value},`);
4195
+ }
4196
+ const body = [requestExpr, ...memberLines].join("\n");
4197
+ const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
4198
+ const lines = [`${pad}${objKey}: (${param}) =>`];
4199
+ if (memberLines.length === 0) {
4200
+ lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
4201
+ return lines;
4202
+ }
4203
+ lines.push(`${pad} __req(() => ${requestExpr}, {`);
4204
+ lines.push(...memberLines);
4205
+ lines.push(`${pad} }),`);
3532
4206
  return lines;
3533
4207
  }
3534
4208
  function renderStreamExpr(req) {
@@ -3659,6 +4333,26 @@ var EMPTY_PATH_NAMESPACE = [
3659
4333
  "}",
3660
4334
  ""
3661
4335
  ];
4336
+ function isClientCallable(route) {
4337
+ return route.method.toUpperCase() !== "ALL";
4338
+ }
4339
+ var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
4340
+ var ROUTES_EXPORTS = [
4341
+ // Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
4342
+ { name: "route", typeOnly: false },
4343
+ { name: "ROUTES", typeOnly: false },
4344
+ { name: "RouteName", typeOnly: true },
4345
+ { name: "ExtractParams", typeOnly: true },
4346
+ { name: "RouteParams", typeOnly: true }
4347
+ ];
4348
+ function finishRoutesImport(lines) {
4349
+ const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
4350
+ const used = ROUTES_EXPORTS.filter(
4351
+ ({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
4352
+ ).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
4353
+ const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
4354
+ return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
4355
+ }
3662
4356
  function buildApiFile(routes, outDir, opts = {}) {
3663
4357
  const fetcherImportPath = opts.fetcherImportPath;
3664
4358
  const serialization = opts.serialization ?? "json";
@@ -3666,7 +4360,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3666
4360
  const { layer } = resolveApiSlots(extensions);
3667
4361
  const memberExts = extensions.filter((e) => e.apiMembers);
3668
4362
  const headerExts = extensions.filter((e) => e.apiHeader);
3669
- const contracted = routes.filter((r) => r.contract);
4363
+ const contracted = routes.filter((r) => r.contract && isClientCallable(r));
3670
4364
  const ctx = opts.ctx ?? {
3671
4365
  cwd: outDir ?? "",
3672
4366
  outDir: outDir ?? "",
@@ -3718,9 +4412,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3718
4412
  for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
3719
4413
  }
3720
4414
  lines.push(...extImports);
3721
- lines.push(
3722
- "import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
3723
- );
4415
+ lines.push(ROUTES_IMPORT_PLACEHOLDER);
3724
4416
  const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
3725
4417
  lines.push(`import type { Fetcher } from '${runtimeImport}';`);
3726
4418
  if (serialization === "json" && contracted.length > 0) {
@@ -3771,7 +4463,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3771
4463
  lines.push(...statements, "");
3772
4464
  }
3773
4465
  }
3774
- return lines.join("\n");
4466
+ return finishRoutesImport(lines);
3775
4467
  }
3776
4468
  const tree = /* @__PURE__ */ new Map();
3777
4469
  for (const r of contracted) {
@@ -3824,7 +4516,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3824
4516
  lines.push(...statements, "");
3825
4517
  }
3826
4518
  }
3827
- return lines.join("\n");
4519
+ return finishRoutesImport(lines);
3828
4520
  }
3829
4521
 
3830
4522
  // src/emit/emit-cache.ts
@@ -3837,6 +4529,7 @@ async function emitCache(pages, outDir) {
3837
4529
  const s = await (0, import_promises4.stat)(p.absolutePath);
3838
4530
  return {
3839
4531
  name: p.name,
4532
+ scope: p.scope ?? "default",
3840
4533
  relativePath: p.relativePath,
3841
4534
  mtime: s.mtime.toISOString()
3842
4535
  };
@@ -4584,6 +5277,7 @@ function buildOpenApiSpec(routes, opts = {}) {
4584
5277
  const paths = {};
4585
5278
  for (const route of routes) {
4586
5279
  if (!route.contract) continue;
5280
+ if (route.method.toUpperCase() === "ALL") continue;
4587
5281
  const oaPath = toOpenApiPath(route.path);
4588
5282
  const method = route.method.toLowerCase();
4589
5283
  let pathItem = paths[oaPath];
@@ -4627,10 +5321,11 @@ async function emitPages(pages, outDir, _options = {}) {
4627
5321
  }).join("\n");
4628
5322
  const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
4629
5323
  const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
5324
+ const scopesBlock = buildScopesBlock(pages);
4630
5325
  const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
4631
5326
 
4632
5327
  export type InertiaPageName = ${pageNameUnion};
4633
- ${propsHelper}
5328
+ ${propsHelper}${scopesBlock}
4634
5329
  declare module '@dudousxd/nestjs-inertia' {
4635
5330
  interface InertiaPages {
4636
5331
  ${augBody}
@@ -4639,6 +5334,31 @@ ${sharedPropsBlock}}
4639
5334
  `;
4640
5335
  await (0, import_promises9.writeFile)((0, import_node_path13.join)(outDir, "pages.d.ts"), content, "utf8");
4641
5336
  }
5337
+ function buildScopesBlock(pages) {
5338
+ const byScope = /* @__PURE__ */ new Map();
5339
+ for (const page of pages) {
5340
+ const scope = page.scope ?? "default";
5341
+ const names = byScope.get(scope) ?? [];
5342
+ names.push(JSON.stringify(page.name));
5343
+ byScope.set(scope, names);
5344
+ }
5345
+ if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
5346
+ if (!byScope.has("default")) byScope.set("default", []);
5347
+ const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
5348
+ const body = scopes.map((scope) => {
5349
+ const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
5350
+ const names = byScope.get(scope) ?? [];
5351
+ return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
5352
+ }).join("\n");
5353
+ return `
5354
+ /** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
5355
+ export interface InertiaScopePages {
5356
+ ${body}
5357
+ }
5358
+
5359
+ export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
5360
+ `;
5361
+ }
4642
5362
  function buildSharedPropsBlock(sharedProps) {
4643
5363
  if (!sharedProps) return "";
4644
5364
  if (sharedProps.isImportRef) {
@@ -4820,12 +5540,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
4820
5540
  }
4821
5541
  if (config.pages) {
4822
5542
  const pagesConfig = config.pages;
4823
- const pages = await discoverPages({
4824
- glob: pagesConfig.glob,
4825
- cwd: config.codegen.cwd,
4826
- propsExport: pagesConfig.propsExport,
4827
- componentNameStrategy: pagesConfig.componentNameStrategy
4828
- });
5543
+ const pages = await discoverPageScopes(config);
4829
5544
  const sharedProps = discoverSharedPropsFromConfig(config);
4830
5545
  await emitPages(pages, config.codegen.outDir, {
4831
5546
  propsExport: pagesConfig.propsExport,
@@ -5000,12 +5715,17 @@ async function watch(config, onChange, options = {}) {
5000
5715
  await runInitialPass();
5001
5716
  }
5002
5717
  let pagesDebounceTimer;
5003
- const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
5004
- const pagesWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, pagesGlob), {
5005
- ignoreInitial: true,
5006
- persistent: true,
5007
- awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
5008
- });
5718
+ const pageGlobs = allPageGlobs(config);
5719
+ const pagesWatcher = import_chokidar.default.watch(
5720
+ (pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
5721
+ (glob) => (0, import_node_path17.join)(config.codegen.cwd, glob)
5722
+ ),
5723
+ {
5724
+ ignoreInitial: true,
5725
+ persistent: true,
5726
+ awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
5727
+ }
5728
+ );
5009
5729
  function schedulePagesRegenerate() {
5010
5730
  if (pagesDebounceTimer !== void 0) {
5011
5731
  clearTimeout(pagesDebounceTimer);
@@ -5085,7 +5805,7 @@ async function watch(config, onChange, options = {}) {
5085
5805
  }
5086
5806
 
5087
5807
  // src/index.ts
5088
- var VERSION = "0.25.2";
5808
+ var VERSION = "0.27.0";
5089
5809
 
5090
5810
  // src/generate-manifest.ts
5091
5811
  var MANIFEST_FILE = ".codegen-manifest.json";
@@ -5145,8 +5865,7 @@ function diffConfigKeyHashes(previous, current) {
5145
5865
  return [...keys].filter((key) => previous[key] !== current[key]).sort();
5146
5866
  }
5147
5867
  async function discoverInputFiles(config) {
5148
- const globs = [config.contracts.glob, config.forms.watch];
5149
- if (config.pages) globs.push(config.pages.glob);
5868
+ const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
5150
5869
  const cwd = config.codegen.cwd;
5151
5870
  const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
5152
5871
  return [...new Set(matched)].sort();