@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.
@@ -70,11 +70,32 @@ function validateUserConfig(userConfig) {
70
70
  "validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
71
71
  );
72
72
  }
73
- if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
73
+ if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
74
74
  throw new ConfigError(
75
- "Config validation failed: `pages.glob` must be a string when `pages` is set"
75
+ "Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
76
76
  );
77
77
  }
78
+ for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
79
+ if (scope === "default") {
80
+ throw new ConfigError(
81
+ 'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
82
+ );
83
+ }
84
+ if (!isGlobInput(scopeConfig?.glob)) {
85
+ throw new ConfigError(
86
+ `Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
87
+ );
88
+ }
89
+ }
90
+ if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
91
+ console.warn(
92
+ "[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)."
93
+ );
94
+ }
95
+ }
96
+ function isGlobInput(value) {
97
+ if (typeof value === "string") return true;
98
+ return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
78
99
  }
79
100
  function applyDefaults(userConfig, cwd) {
80
101
  validateUserConfig(userConfig);
@@ -102,7 +123,17 @@ function applyDefaults(userConfig, cwd) {
102
123
  pages: userConfig.pages ? {
103
124
  glob: userConfig.pages.glob,
104
125
  propsExport: userConfig.pages.propsExport ?? "ComponentProps",
105
- componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
126
+ componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
127
+ scopes: Object.fromEntries(
128
+ Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
129
+ scope,
130
+ {
131
+ glob: scopeConfig.glob,
132
+ prefix: scopeConfig.prefix ?? `${scope}/`,
133
+ componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
134
+ }
135
+ ])
136
+ )
106
137
  } : null,
107
138
  contracts: {
108
139
  glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
@@ -149,15 +180,15 @@ import { existsSync } from "fs";
149
180
  import { dirname as dirname2, join as join2, resolve as resolve3 } from "path";
150
181
  import fg from "fast-glob";
151
182
  import {
152
- Node as Node8,
153
- Project as Project2,
154
- ts
183
+ Node as Node10,
184
+ Project as Project3,
185
+ ts as ts3
155
186
  } from "ts-morph";
156
187
 
157
188
  // src/discovery/dto-type-resolver.ts
158
189
  import {
159
- Node as Node6,
160
- SyntaxKind as SyntaxKind2
190
+ Node as Node9,
191
+ SyntaxKind as SyntaxKind3
161
192
  } from "ts-morph";
162
193
 
163
194
  // src/discovery/dto-to-ir.ts
@@ -1695,6 +1726,674 @@ function extractFilterableEntityFields(filterClass, project, entityOverride, pre
1695
1726
  return fields;
1696
1727
  }
1697
1728
 
1729
+ // src/discovery/pipe-schema.ts
1730
+ import { Node as Node8 } from "ts-morph";
1731
+
1732
+ // src/discovery/schema-type-inference.ts
1733
+ import {
1734
+ ts as ts2
1735
+ } from "ts-morph";
1736
+
1737
+ // src/discovery/schema-type-program.ts
1738
+ import { Node as Node6, Project as Project2, ts } from "ts-morph";
1739
+ var PARAM_DECORATORS = /* @__PURE__ */ new Set(["Body", "Query", "Param"]);
1740
+ var SchemaTypeProgram = class {
1741
+ constructor(discovery) {
1742
+ this.discovery = discovery;
1743
+ const base = discovery.getCompilerOptions();
1744
+ this.options = {
1745
+ ...base,
1746
+ // A tsconfig-less consumer gets ES5 by default, whose lib has no `Map`/`Set`/
1747
+ // `Promise` for the schema libraries' declarations to lean on.
1748
+ target: base.target ?? ts.ScriptTarget.ES2022,
1749
+ // With neither set, resolve the way a bundler does: package `exports`
1750
+ // (`zod/v4`) and extensionless or `.js` relative imports all resolve.
1751
+ ...base.module === void 0 && base.moduleResolution === void 0 ? { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler } : {},
1752
+ // Without it `.nullable()` and `.optional()` would erase to the bare type.
1753
+ strictNullChecks: true,
1754
+ // No ambient `@types/*` (a tsconfig's `types: ['node']`): a schema does not
1755
+ // need them, and they are a hundred-odd files of program to build.
1756
+ types: [],
1757
+ noEmit: true
1758
+ };
1759
+ this.project = new Project2({
1760
+ skipAddingFilesFromTsConfig: true,
1761
+ compilerOptions: this.options
1762
+ });
1763
+ for (const sf of discovery.getSourceFiles()) this.request(sf.getFilePath(), { schema: true });
1764
+ this.flush();
1765
+ }
1766
+ project;
1767
+ options;
1768
+ /** Parses a file that is not in discovery's Project, without adding it there. */
1769
+ scratch = new Project2({
1770
+ skipAddingFilesFromTsConfig: true,
1771
+ skipLoadingLibFiles: true,
1772
+ skipFileDependencyResolution: true
1773
+ });
1774
+ needs = /* @__PURE__ */ new Map();
1775
+ /** Pruned text per file, as last written into {@link project}. */
1776
+ written = /* @__PURE__ */ new Map();
1777
+ /**
1778
+ * The typed copy of a discovery file, with its schema positions kept. Adding one
1779
+ * that was not there yet (a contract file discovery reached lazily) rebuilds the
1780
+ * program once.
1781
+ */
1782
+ sourceFileFor(sf) {
1783
+ this.request(sf.getFilePath(), { schema: true });
1784
+ this.flush();
1785
+ return this.project.getSourceFile(sf.getFilePath());
1786
+ }
1787
+ // -------------------------------------------------------------------------
1788
+ queue = [];
1789
+ request(path, want) {
1790
+ let needs = this.needs.get(path);
1791
+ const isNew = !needs;
1792
+ needs ??= { schema: false, all: false, names: /* @__PURE__ */ new Set() };
1793
+ let grew = isNew;
1794
+ if (want.schema && !needs.schema) {
1795
+ needs.schema = true;
1796
+ grew = true;
1797
+ }
1798
+ if (want.all && !needs.all) {
1799
+ needs.all = true;
1800
+ grew = true;
1801
+ }
1802
+ for (const name of want.names ?? []) {
1803
+ if (!needs.names.has(name)) {
1804
+ needs.names.add(name);
1805
+ grew = true;
1806
+ }
1807
+ }
1808
+ this.needs.set(path, needs);
1809
+ if (grew) this.queue.push(path);
1810
+ }
1811
+ /** Run the worklist to a fixpoint, then write every changed file into the program. */
1812
+ flush() {
1813
+ const changed = /* @__PURE__ */ new Map();
1814
+ while (this.queue.length > 0) {
1815
+ const path = this.queue.pop();
1816
+ const original = this.original(path);
1817
+ if (!original) continue;
1818
+ const needs = this.needs.get(path);
1819
+ const text = this.prune(original, needs);
1820
+ if (this.written.get(path) !== text) changed.set(path, text);
1821
+ }
1822
+ for (const [path, text] of changed) {
1823
+ this.project.createSourceFile(path, text, { overwrite: true });
1824
+ this.written.set(path, text);
1825
+ }
1826
+ }
1827
+ /** The file as discovery sees it (its in-memory text), else as it is on disk. */
1828
+ original(path) {
1829
+ return this.discovery.getSourceFile(path) ?? this.scratch.getSourceFile(path) ?? this.scratch.addSourceFileAtPathIfExists(path);
1830
+ }
1831
+ /**
1832
+ * `sf`'s text with every import/re-export nothing in `needs` references blanked,
1833
+ * requesting from each kept one's target the names it brings in.
1834
+ */
1835
+ prune(sf, needs) {
1836
+ const locals = topLevelDeclarations(sf);
1837
+ const roots = [];
1838
+ if (needs.all) roots.push(...sf.getStatements());
1839
+ if (needs.schema) roots.push(...schemaPositions(sf));
1840
+ const reexported = /* @__PURE__ */ new Map();
1841
+ for (const exp of sf.getExportDeclarations()) {
1842
+ const spec = exp.getModuleSpecifierValue();
1843
+ if (spec === void 0) {
1844
+ for (const named of exp.getNamedExports()) {
1845
+ const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
1846
+ if (needs.all || needs.names.has(exposed)) roots.push(named.getNameNode());
1847
+ }
1848
+ continue;
1849
+ }
1850
+ if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) continue;
1851
+ for (const named of exp.getNamedExports()) {
1852
+ const exposed = (named.getAliasNode() ?? named.getNameNode()).getText();
1853
+ const list = reexported.get(exposed) ?? [];
1854
+ list.push({ from: spec, name: named.getNameNode().getText() });
1855
+ reexported.set(exposed, list);
1856
+ }
1857
+ }
1858
+ const unresolvedNames = [];
1859
+ for (const name of needs.names) {
1860
+ const decls = name === "default" ? defaultExport(sf) : locals.get(name) ?? [];
1861
+ if (decls.length > 0) roots.push(...decls);
1862
+ else if (!reexported.has(name)) unresolvedNames.push(name);
1863
+ }
1864
+ const referenced = /* @__PURE__ */ new Set();
1865
+ const queue = [...roots];
1866
+ const seen = new Set(queue);
1867
+ while (queue.length > 0) {
1868
+ const node = queue.pop();
1869
+ const ids = Node6.isIdentifier(node) ? [node] : node.getDescendantsOfKind(ts.SyntaxKind.Identifier);
1870
+ for (const id of ids) {
1871
+ const name = id.getText();
1872
+ if (referenced.has(name)) continue;
1873
+ referenced.add(name);
1874
+ for (const local of locals.get(name) ?? []) {
1875
+ if (seen.has(local)) continue;
1876
+ seen.add(local);
1877
+ queue.push(local);
1878
+ }
1879
+ }
1880
+ }
1881
+ const chars = sf.getFullText().split("");
1882
+ const blank = (node) => {
1883
+ for (let i = node.getStart(); i < node.getEnd(); i++) {
1884
+ if (chars[i] !== "\n" && chars[i] !== "\r") chars[i] = " ";
1885
+ }
1886
+ };
1887
+ for (const decl of sf.getImportDeclarations()) {
1888
+ const target = this.resolve(decl.getModuleSpecifierValue(), sf);
1889
+ const namespace = decl.getNamespaceImport()?.getText();
1890
+ const def = decl.getDefaultImport()?.getText();
1891
+ const named = decl.getNamedImports().map((n) => ({
1892
+ local: (n.getAliasNode() ?? n.getNameNode()).getText(),
1893
+ imported: n.getName()
1894
+ })).filter((n) => referenced.has(n.local));
1895
+ const wantsNamespace = namespace !== void 0 && referenced.has(namespace);
1896
+ const wantsDefault = def !== void 0 && referenced.has(def);
1897
+ if (!wantsNamespace && !wantsDefault && named.length === 0) {
1898
+ blank(decl);
1899
+ continue;
1900
+ }
1901
+ if (target) {
1902
+ this.request(target, {
1903
+ all: wantsNamespace,
1904
+ names: [...wantsDefault ? ["default"] : [], ...named.map((n) => n.imported)]
1905
+ });
1906
+ }
1907
+ }
1908
+ for (const exp of sf.getExportDeclarations()) {
1909
+ const spec = exp.getModuleSpecifierValue();
1910
+ if (spec === void 0) continue;
1911
+ const target = this.resolve(spec, sf);
1912
+ if (exp.isNamespaceExport() || exp.getNamedExports().length === 0) {
1913
+ if (!needs.all && unresolvedNames.length === 0) {
1914
+ blank(exp);
1915
+ } else if (target) {
1916
+ this.request(target, { all: needs.all, names: unresolvedNames });
1917
+ }
1918
+ continue;
1919
+ }
1920
+ const wanted = exp.getNamedExports().filter(
1921
+ (n) => needs.all || needs.names.has((n.getAliasNode() ?? n.getNameNode()).getText())
1922
+ ).map((n) => n.getNameNode().getText());
1923
+ if (wanted.length === 0) blank(exp);
1924
+ else if (target) this.request(target, { names: wanted });
1925
+ }
1926
+ return chars.join("");
1927
+ }
1928
+ /**
1929
+ * The project source file a module specifier resolves to, or undefined for a
1930
+ * package (read unpruned by the program itself) or an unresolvable one.
1931
+ */
1932
+ resolve(specifier, from) {
1933
+ const resolved = ts.resolveModuleName(
1934
+ specifier,
1935
+ from.getFilePath(),
1936
+ this.options,
1937
+ ts.sys
1938
+ ).resolvedModule;
1939
+ if (!resolved || resolved.isExternalLibraryImport) return void 0;
1940
+ const file = resolved.resolvedFileName;
1941
+ if (file.endsWith(".d.ts") || !/\.(m|c)?tsx?$/.test(file)) return void 0;
1942
+ return file;
1943
+ }
1944
+ };
1945
+ function schemaPositions(sf) {
1946
+ const roots = [];
1947
+ sf.forEachDescendant((node) => {
1948
+ if (Node6.isDecorator(node)) {
1949
+ const name = node.getName();
1950
+ if (name === "ApplyContract") {
1951
+ roots.push(...node.getArguments());
1952
+ } else if (PARAM_DECORATORS.has(name)) {
1953
+ for (const arg of node.getArguments()) {
1954
+ if (Node6.isNewExpression(arg) || Node6.isCallExpression(arg)) {
1955
+ roots.push(...arg.getArguments());
1956
+ }
1957
+ }
1958
+ }
1959
+ } else if (Node6.isCallExpression(node)) {
1960
+ const callee = node.getExpression();
1961
+ const name = Node6.isIdentifier(callee) ? callee.getText() : Node6.isPropertyAccessExpression(callee) ? callee.getName() : void 0;
1962
+ if (name === "defineContract") roots.push(...node.getArguments());
1963
+ }
1964
+ });
1965
+ return roots;
1966
+ }
1967
+ function topLevelDeclarations(sf) {
1968
+ const byName = /* @__PURE__ */ new Map();
1969
+ const add = (name, node) => {
1970
+ if (!name) return;
1971
+ const list = byName.get(name);
1972
+ if (list) list.push(node);
1973
+ else byName.set(name, [node]);
1974
+ };
1975
+ for (const decl of sf.getVariableDeclarations()) add(decl.getName(), decl);
1976
+ for (const decl of sf.getFunctions()) add(decl.getName(), decl);
1977
+ for (const decl of sf.getClasses()) add(decl.getName(), decl);
1978
+ for (const decl of sf.getEnums()) add(decl.getName(), decl);
1979
+ for (const decl of sf.getInterfaces()) add(decl.getName(), decl);
1980
+ for (const decl of sf.getTypeAliases()) add(decl.getName(), decl);
1981
+ for (const decl of sf.getModules()) add(decl.getName(), decl);
1982
+ return byName;
1983
+ }
1984
+ function defaultExport(sf) {
1985
+ const nodes = [];
1986
+ for (const assignment of sf.getExportAssignments()) nodes.push(assignment);
1987
+ for (const fn of sf.getFunctions()) if (fn.isDefaultExport()) nodes.push(fn);
1988
+ for (const cls of sf.getClasses()) if (cls.isDefaultExport()) nodes.push(cls);
1989
+ return nodes;
1990
+ }
1991
+
1992
+ // src/discovery/schema-type-inference.ts
1993
+ var programs = /* @__PURE__ */ new WeakMap();
1994
+ function programFor(discovery) {
1995
+ let program = programs.get(discovery);
1996
+ if (!program) {
1997
+ program = new SchemaTypeProgram(discovery);
1998
+ programs.set(discovery, program);
1999
+ }
2000
+ return program;
2001
+ }
2002
+ function clearSchemaTypeProject(discovery) {
2003
+ programs.delete(discovery);
2004
+ }
2005
+ function counterpart(typedSf, node) {
2006
+ const start = node.getStart();
2007
+ const end = node.getEnd();
2008
+ const kind = node.getKind();
2009
+ let candidate = typedSf.getDescendantAtPos(start);
2010
+ while (candidate) {
2011
+ if (candidate.getStart() === start && candidate.getEnd() === end && candidate.getKind() === kind) {
2012
+ return candidate;
2013
+ }
2014
+ if (candidate.getStart() < start || candidate.getEnd() > end) return void 0;
2015
+ candidate = candidate.getParent();
2016
+ }
2017
+ return void 0;
2018
+ }
2019
+ function propType(type, name, at) {
2020
+ return type.getProperty(name)?.getTypeAtLocation(at);
2021
+ }
2022
+ function isAnyOrUnknown(type) {
2023
+ return type.isAny() || type.isUnknown();
2024
+ }
2025
+ function inferSchema(expr, discovery) {
2026
+ try {
2027
+ const typedSf = programFor(discovery).sourceFileFor(expr.getSourceFile());
2028
+ const at = typedSf && counterpart(typedSf, expr);
2029
+ if (!at) return { kind: "unresolved" };
2030
+ const schemaType = at.getType();
2031
+ if (isAnyOrUnknown(schemaType)) return { kind: "unresolved" };
2032
+ const standard = propType(schemaType, "~standard", at);
2033
+ const stdTypes = standard ? propType(standard, "types", at)?.getNonNullableType() : void 0;
2034
+ const stdInput = stdTypes ? propType(stdTypes, "input", at) : void 0;
2035
+ const stdOutput = stdTypes ? propType(stdTypes, "output", at) : void 0;
2036
+ if (stdInput && stdOutput) {
2037
+ return { kind: "schema", types: { input: stdInput, output: stdOutput, at } };
2038
+ }
2039
+ const zodInput = propType(schemaType, "_input", at);
2040
+ const zodOutput = propType(schemaType, "_output", at);
2041
+ if (zodInput && zodOutput) {
2042
+ return { kind: "schema", types: { input: zodInput, output: zodOutput, at } };
2043
+ }
2044
+ return { kind: "not-schema" };
2045
+ } catch (err) {
2046
+ dbg("schema inference failed:", err);
2047
+ return { kind: "unresolved" };
2048
+ }
2049
+ }
2050
+ var GLOBAL_NAMED_TYPES = /* @__PURE__ */ new Set([
2051
+ "Date",
2052
+ "File",
2053
+ "Blob",
2054
+ "FormData",
2055
+ "URL",
2056
+ "RegExp",
2057
+ "Map",
2058
+ "Set",
2059
+ "ArrayBuffer",
2060
+ "Uint8Array"
2061
+ ]);
2062
+ var MAX_DEPTH = 12;
2063
+ function objectKey(name) {
2064
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2065
+ }
2066
+ function isOptionalSymbol(sym) {
2067
+ return (sym.getFlags() & ts2.SymbolFlags.Optional) !== 0;
2068
+ }
2069
+ function isSymbolKeyed(sym) {
2070
+ return sym.getName().startsWith("__@");
2071
+ }
2072
+ function orderedProperties(type) {
2073
+ const position = (sym) => {
2074
+ const decl = sym.getDeclarations()[0];
2075
+ return decl ? [decl.getSourceFile().getFilePath(), decl.getStart()] : void 0;
2076
+ };
2077
+ return type.getProperties().map((sym, index) => ({ sym, index, at: position(sym) })).sort((a, b) => {
2078
+ if (a.at && b.at) {
2079
+ if (a.at[0] !== b.at[0]) return a.at[0] < b.at[0] ? -1 : 1;
2080
+ if (a.at[1] !== b.at[1]) return a.at[1] - b.at[1];
2081
+ } else if (a.at || b.at) {
2082
+ return a.at ? -1 : 1;
2083
+ }
2084
+ return a.index - b.index;
2085
+ }).map((entry) => entry.sym);
2086
+ }
2087
+ function isFromDeclarationFile(sym) {
2088
+ const decl = sym?.getDeclarations()[0];
2089
+ return !!decl && decl.getSourceFile().isDeclarationFile();
2090
+ }
2091
+ function render(type, state, paired) {
2092
+ if (state.depth > MAX_DEPTH) return "unknown";
2093
+ if (isAnyOrUnknown(type)) {
2094
+ return paired && !isAnyOrUnknown(paired) ? render(paired, state) : "unknown";
2095
+ }
2096
+ if (type.isNever()) return "never";
2097
+ if (type.isString() || type.isTemplateLiteral()) return "string";
2098
+ if (type.isNumber()) return "number";
2099
+ if (type.isBoolean()) return "boolean";
2100
+ if (type.isBigInt()) return "bigint";
2101
+ if (type.isNull()) return "null";
2102
+ if (type.isUndefined() || type.isVoid()) return "undefined";
2103
+ if (type.isBooleanLiteral()) return type.getText();
2104
+ if (type.isStringLiteral() || type.isNumberLiteral() || type.isEnumLiteral()) {
2105
+ const value = type.getLiteralValue();
2106
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
2107
+ }
2108
+ if (type.isBigIntLiteral()) return type.getText();
2109
+ if (type.isTypeParameter()) return "unknown";
2110
+ if (type.isUnion()) return renderUnion(type, state, paired);
2111
+ if (type.isIntersection()) return renderIntersection(type, state);
2112
+ const next = { ...state, depth: state.depth + 1 };
2113
+ if (type.isArray() || type.isReadonlyArray()) {
2114
+ const element = type.getArrayElementType() ?? type.getTypeArguments()[0];
2115
+ const pairedElement = paired?.getArrayElementType() ?? paired?.getTypeArguments()[0];
2116
+ return element ? `Array<${render(element, next, pairedElement)}>` : "Array<unknown>";
2117
+ }
2118
+ if (type.isTuple()) {
2119
+ const elements = type.getTupleElements();
2120
+ const pairedElements = paired?.isTuple() ? paired.getTupleElements() : [];
2121
+ return `[${elements.map((e, i) => render(e, next, pairedElements[i])).join(", ")}]`;
2122
+ }
2123
+ if (type.isObject()) return renderObject(type, next, paired);
2124
+ return "unknown";
2125
+ }
2126
+ function renderUnion(type, state, paired) {
2127
+ const members = type.getUnionTypes();
2128
+ const hasTrue = members.some((m) => m.isBooleanLiteral() && m.getText() === "true");
2129
+ const hasFalse = members.some((m) => m.isBooleanLiteral() && m.getText() === "false");
2130
+ const pairedNonNull = paired?.getNonNullableType();
2131
+ const arms = [];
2132
+ let sawBoolean = false;
2133
+ let hasNull = false;
2134
+ let hasUndefined = false;
2135
+ for (const member of members) {
2136
+ if (member.isNull()) {
2137
+ hasNull = true;
2138
+ continue;
2139
+ }
2140
+ if (member.isUndefined()) {
2141
+ hasUndefined = true;
2142
+ continue;
2143
+ }
2144
+ if (member.isBooleanLiteral() && hasTrue && hasFalse) {
2145
+ if (!sawBoolean) arms.push("boolean");
2146
+ sawBoolean = true;
2147
+ continue;
2148
+ }
2149
+ arms.push(render(member, state, members.length === 1 ? paired : pairedNonNull));
2150
+ }
2151
+ if (hasNull) arms.push("null");
2152
+ if (hasUndefined) arms.push("undefined");
2153
+ const unique = [...new Set(arms)];
2154
+ if (unique.includes("unknown")) return "unknown";
2155
+ return unique.join(" | ");
2156
+ }
2157
+ function renderIntersection(type, state) {
2158
+ const members = type.getIntersectionTypes();
2159
+ const primitive = members.find((m) => !m.isObject());
2160
+ if (primitive) return render(primitive, state);
2161
+ return renderObject(type, { ...state, depth: state.depth + 1 }, void 0);
2162
+ }
2163
+ function renderObject(type, state, paired) {
2164
+ const symbol = type.getSymbol() ?? type.getAliasSymbol();
2165
+ const name = symbol?.getName();
2166
+ if (name && GLOBAL_NAMED_TYPES.has(name) && isFromDeclarationFile(symbol)) {
2167
+ const args = type.getTypeArguments();
2168
+ return args.length ? `${name}<${args.map((a) => render(a, state)).join(", ")}>` : name;
2169
+ }
2170
+ if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) {
2171
+ return "unknown";
2172
+ }
2173
+ if (type.isClassOrInterface() && isFromDeclarationFile(symbol)) return "unknown";
2174
+ if (state.stack.has(type.compilerType)) return "unknown";
2175
+ const stack = new Set(state.stack).add(type.compilerType);
2176
+ const inner = { ...state, stack };
2177
+ const fields = [];
2178
+ for (const prop of orderedProperties(type)) {
2179
+ if (isSymbolKeyed(prop)) continue;
2180
+ const propName = prop.getName();
2181
+ const propT = prop.getTypeAtLocation(state.at);
2182
+ if (propT.getCallSignatures().length > 0 && !propT.isUnion()) continue;
2183
+ const pairedProp = paired?.getProperty(propName)?.getTypeAtLocation(state.at);
2184
+ const optional = isOptionalSymbol(prop) ? "?" : "";
2185
+ fields.push(`${objectKey(propName)}${optional}: ${render(propT, inner, pairedProp)}`);
2186
+ }
2187
+ const stringIndex = type.getStringIndexType();
2188
+ if (stringIndex) {
2189
+ const pairedIndex = paired?.getStringIndexType();
2190
+ const value = render(stringIndex, inner, pairedIndex);
2191
+ if (fields.length === 0) return `Record<string, ${value}>`;
2192
+ fields.push(`[key: string]: ${value}`);
2193
+ }
2194
+ return fields.length ? `{ ${fields.join("; ")} }` : "{}";
2195
+ }
2196
+ function stateAt(at) {
2197
+ return { at, stack: /* @__PURE__ */ new Set(), depth: 0 };
2198
+ }
2199
+ function requestTypeOf(types) {
2200
+ return render(types.input, stateAt(types.at), types.output);
2201
+ }
2202
+ function responseTypeOf(types) {
2203
+ return render(types.output, stateAt(types.at));
2204
+ }
2205
+ function requestFieldsOf(types) {
2206
+ const { input, output, at } = types;
2207
+ const objectInput = input.getNonNullableType();
2208
+ if (!objectInput.isObject() || objectInput.isArray() || objectInput.isTuple()) return null;
2209
+ const state = stateAt(at);
2210
+ return orderedProperties(objectInput).filter((prop) => !isSymbolKeyed(prop)).map((prop) => ({
2211
+ name: prop.getName(),
2212
+ type: render(
2213
+ prop.getTypeAtLocation(at),
2214
+ state,
2215
+ output.getProperty(prop.getName())?.getTypeAtLocation(at)
2216
+ ),
2217
+ optional: isOptionalSymbol(prop)
2218
+ }));
2219
+ }
2220
+ function acceptsUndefined(types) {
2221
+ const { input, output } = types;
2222
+ const accepted = isAnyOrUnknown(input) ? output : input;
2223
+ if (isAnyOrUnknown(accepted) || accepted.isUndefined()) return true;
2224
+ return accepted.isUnion() && accepted.getUnionTypes().some((t) => t.isUndefined());
2225
+ }
2226
+
2227
+ // src/discovery/zod-ast-to-ts.ts
2228
+ import { Node as Node7, SyntaxKind as SyntaxKind2 } from "ts-morph";
2229
+ function zodAstToTs(node) {
2230
+ if (!Node7.isCallExpression(node)) return "unknown";
2231
+ const expr = node.getExpression();
2232
+ if (Node7.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 (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2257
+ if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2258
+ if (lit.getKind() === SyntaxKind2.TrueKeyword) return "true";
2259
+ if (lit.getKind() === SyntaxKind2.FalseKeyword) return "false";
2260
+ return "unknown";
2261
+ }
2262
+ case "enum": {
2263
+ const arrArg = args[0];
2264
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2265
+ const members = arrArg.getElements().map(
2266
+ (el) => Node7.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 || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
2278
+ const lines = [];
2279
+ for (const prop of objArg.getProperties()) {
2280
+ if (!Node7.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 || !Node7.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 || !Node7.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 (!Node7.isCallExpression(node)) return false;
2313
+ const expr = node.getExpression();
2314
+ return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2315
+ }
2316
+ function memberType(val, position, project) {
2317
+ if (project) {
2318
+ const inferred = inferSchema(val, project);
2319
+ if (inferred.kind === "schema") {
2320
+ return position === "request" ? requestTypeOf(inferred.types) : responseTypeOf(inferred.types);
2321
+ }
2322
+ }
2323
+ return zodAstToTs(val);
2324
+ }
2325
+ function parseDefineContractCall(callExpr, project) {
2326
+ if (!Node7.isCallExpression(callExpr)) return null;
2327
+ const callee = callExpr.getExpression();
2328
+ const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
2329
+ if (calleeName !== "defineContract") return null;
2330
+ const args = callExpr.getArguments();
2331
+ const optsArg = args[0];
2332
+ if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
2333
+ let query = null;
2334
+ let body = null;
2335
+ let response = "unknown";
2336
+ let error = null;
2337
+ let bodyZodText = null;
2338
+ let queryZodText = null;
2339
+ for (const prop of optsArg.getProperties()) {
2340
+ if (!Node7.isPropertyAssignment(prop)) continue;
2341
+ const propName = prop.getName();
2342
+ const val = prop.getInitializer();
2343
+ if (!val) continue;
2344
+ if (propName === "query") {
2345
+ query = memberType(val, "request", project);
2346
+ queryZodText = val.getText();
2347
+ } else if (propName === "body") {
2348
+ body = memberType(val, "request", project);
2349
+ bodyZodText = val.getText();
2350
+ } else if (propName === "response") {
2351
+ response = memberType(val, "response", project);
2352
+ } else if (propName === "error") {
2353
+ error = memberType(val, "response", project);
2354
+ }
2355
+ }
2356
+ return { query, body, response, error, bodyZodText, queryZodText };
2357
+ }
2358
+
2359
+ // src/discovery/pipe-schema.ts
2360
+ function pipeSchemaOf(decorator, project) {
2361
+ for (const pipe of decorator.getArguments()) {
2362
+ if (!Node8.isNewExpression(pipe) && !Node8.isCallExpression(pipe)) continue;
2363
+ for (const arg of pipe.getArguments()) {
2364
+ if (!couldBeSchema(arg)) continue;
2365
+ const inferred = inferSchema(arg, project);
2366
+ if (inferred.kind === "schema") {
2367
+ return {
2368
+ type: requestTypeOf(inferred.types),
2369
+ optional: acceptsUndefined(inferred.types),
2370
+ fields: requestFieldsOf(inferred.types)
2371
+ };
2372
+ }
2373
+ if (inferred.kind === "unresolved") {
2374
+ const fallback = syntacticZodType(arg, project);
2375
+ if (fallback) {
2376
+ return { type: fallback, optional: fallback.endsWith("| undefined"), fields: null };
2377
+ }
2378
+ }
2379
+ }
2380
+ }
2381
+ return null;
2382
+ }
2383
+ function couldBeSchema(arg) {
2384
+ return Node8.isIdentifier(arg) || Node8.isPropertyAccessExpression(arg) || Node8.isCallExpression(arg) || Node8.isAsExpression(arg) || Node8.isSatisfiesExpression(arg) || Node8.isParenthesizedExpression(arg);
2385
+ }
2386
+ function syntacticZodType(arg, project) {
2387
+ let expr = arg;
2388
+ if (Node8.isIdentifier(arg)) {
2389
+ const resolved = resolveImportedVariable(arg.getText(), arg.getSourceFile(), project);
2390
+ expr = resolved?.decl.getInitializer();
2391
+ }
2392
+ if (!expr || !Node8.isCallExpression(expr)) return null;
2393
+ const type = zodAstToTs(expr);
2394
+ return type === "unknown" ? null : type;
2395
+ }
2396
+
1698
2397
  // src/discovery/dto-type-resolver.ts
1699
2398
  var WRAPPER_TYPES = {
1700
2399
  // MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
@@ -1726,22 +2425,22 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
1726
2425
  ]);
1727
2426
  function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
1728
2427
  if (depth <= 0) return "unknown";
1729
- if (Node6.isArrayTypeNode(typeNode)) {
2428
+ if (Node9.isArrayTypeNode(typeNode)) {
1730
2429
  const elementType = typeNode.getElementTypeNode();
1731
2430
  return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
1732
2431
  }
1733
- if (Node6.isUnionTypeNode(typeNode)) {
2432
+ if (Node9.isUnionTypeNode(typeNode)) {
1734
2433
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
1735
2434
  }
1736
- if (Node6.isIntersectionTypeNode(typeNode)) {
2435
+ if (Node9.isIntersectionTypeNode(typeNode)) {
1737
2436
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
1738
2437
  }
1739
- if (Node6.isParenthesizedTypeNode(typeNode)) {
2438
+ if (Node9.isParenthesizedTypeNode(typeNode)) {
1740
2439
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
1741
2440
  }
1742
- if (Node6.isTypeReference(typeNode)) {
2441
+ if (Node9.isTypeReference(typeNode)) {
1743
2442
  const typeName = typeNode.getTypeName();
1744
- const name = Node6.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2443
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
1745
2444
  const bound = subst.get(name);
1746
2445
  if (bound !== void 0) return bound;
1747
2446
  if (name === "string" || name === "number" || name === "boolean") return name;
@@ -1764,13 +2463,13 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
1764
2463
  dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1765
2464
  return "unknown";
1766
2465
  }
1767
- if (Node6.isTypeLiteral(typeNode)) {
2466
+ if (Node9.isTypeLiteral(typeNode)) {
1768
2467
  const members = [];
1769
2468
  for (const member of typeNode.getMembers()) {
1770
- if (Node6.isPropertySignature(member)) {
2469
+ if (Node9.isPropertySignature(member)) {
1771
2470
  const memberTypeNode = member.getTypeNode();
1772
- const memberType = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
1773
- members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType}`);
2471
+ const memberType2 = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
2472
+ members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType2}`);
1774
2473
  } else {
1775
2474
  members.push(member.getText());
1776
2475
  }
@@ -1778,11 +2477,11 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
1778
2477
  return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
1779
2478
  }
1780
2479
  const kind = typeNode.getKind();
1781
- if (kind === SyntaxKind2.StringKeyword) return "string";
1782
- if (kind === SyntaxKind2.NumberKeyword) return "number";
1783
- if (kind === SyntaxKind2.BooleanKeyword) return "boolean";
1784
- if (kind === SyntaxKind2.UnknownKeyword) return "unknown";
1785
- if (kind === SyntaxKind2.AnyKeyword) return "unknown";
2480
+ if (kind === SyntaxKind3.StringKeyword) return "string";
2481
+ if (kind === SyntaxKind3.NumberKeyword) return "number";
2482
+ if (kind === SyntaxKind3.BooleanKeyword) return "boolean";
2483
+ if (kind === SyntaxKind3.UnknownKeyword) return "unknown";
2484
+ if (kind === SyntaxKind3.AnyKeyword) return "unknown";
1786
2485
  return typeNode.getText();
1787
2486
  }
1788
2487
  function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
@@ -1830,20 +2529,24 @@ function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE_
1830
2529
  const propName = prop.getName();
1831
2530
  const isOptional = prop.hasQuestionToken();
1832
2531
  const propTypeNode = prop.getTypeNode();
1833
- let propType = "unknown";
2532
+ let propType2 = "unknown";
1834
2533
  if (propTypeNode) {
1835
- propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
2534
+ propType2 = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
1836
2535
  }
1837
- lines.push(`${propName}${isOptional ? "?" : ""}: ${propType}`);
2536
+ lines.push(`${propName}${isOptional ? "?" : ""}: ${propType2}`);
1838
2537
  }
1839
2538
  return `{ ${lines.join("; ")} }`;
1840
2539
  }
2540
+ function namedField(decorator) {
2541
+ const first = decorator.getArguments()[0];
2542
+ return first && Node9.isStringLiteral(first) ? first.getLiteralValue() : null;
2543
+ }
1841
2544
  function extractBodyType(method, sourceFile, project) {
1842
2545
  for (const param of method.getParameters()) {
1843
2546
  const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
1844
- if (!bodyDecorator) continue;
1845
- const bodyArgs = bodyDecorator.getArguments();
1846
- if (bodyArgs.length > 0) continue;
2547
+ if (!bodyDecorator || namedField(bodyDecorator) !== null) continue;
2548
+ const piped = pipeSchemaOf(bodyDecorator, project);
2549
+ if (piped) return piped.type;
1847
2550
  const typeNode = param.getTypeNode();
1848
2551
  if (typeNode) {
1849
2552
  return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
@@ -1851,11 +2554,16 @@ function extractBodyType(method, sourceFile, project) {
1851
2554
  }
1852
2555
  return null;
1853
2556
  }
2557
+ function hasPipeSchema(param, decoratorName, project) {
2558
+ const decorator = param.getDecorators().find((d) => d.getName() === decoratorName);
2559
+ return !!decorator && pipeSchemaOf(decorator, project) !== null;
2560
+ }
1854
2561
  function extractQueryType(method, sourceFile, project) {
1855
2562
  for (const param of method.getParameters()) {
1856
2563
  const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
1857
- if (!queryDecorator) continue;
1858
- if (queryDecorator.getArguments().length > 0) continue;
2564
+ if (!queryDecorator || namedField(queryDecorator) !== null) continue;
2565
+ const piped = pipeSchemaOf(queryDecorator, project);
2566
+ if (piped) return piped.type;
1859
2567
  const typeNode = param.getTypeNode();
1860
2568
  if (typeNode) {
1861
2569
  return resolveTypeNodeToString(typeNode, sourceFile, project, 3);
@@ -1865,10 +2573,13 @@ function extractQueryType(method, sourceFile, project) {
1865
2573
  for (const param of method.getParameters()) {
1866
2574
  const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
1867
2575
  if (!queryDecorator) continue;
1868
- const queryArgs = queryDecorator.getArguments();
1869
- const nameArg = queryArgs[0];
1870
- if (!nameArg || !Node6.isStringLiteral(nameArg)) continue;
1871
- const queryName = nameArg.getLiteralValue();
2576
+ const queryName = namedField(queryDecorator);
2577
+ if (queryName === null) continue;
2578
+ const piped = pipeSchemaOf(queryDecorator, project);
2579
+ if (piped) {
2580
+ entries.push(`${queryName}${piped.optional ? "?" : ""}: ${piped.type}`);
2581
+ continue;
2582
+ }
1872
2583
  const typeNode = param.getTypeNode();
1873
2584
  const queryType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
1874
2585
  entries.push(`${queryName}${isParamOptional(param) ? "?" : ""}: ${queryType}`);
@@ -1878,8 +2589,8 @@ function extractQueryType(method, sourceFile, project) {
1878
2589
  function isParamOptional(param) {
1879
2590
  if (param.hasQuestionToken() || param.hasInitializer()) return true;
1880
2591
  const typeNode = param.getTypeNode();
1881
- if (typeNode && Node6.isUnionTypeNode(typeNode)) {
1882
- return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind2.UndefinedKeyword);
2592
+ if (typeNode && Node9.isUnionTypeNode(typeNode)) {
2593
+ return typeNode.getTypeNodes().some((t) => t.getKind() === SyntaxKind3.UndefinedKeyword);
1883
2594
  }
1884
2595
  return false;
1885
2596
  }
@@ -1891,7 +2602,7 @@ function extractParamsType(method, sourceFile, project) {
1891
2602
  const paramArgs = paramDecorator.getArguments();
1892
2603
  if (paramArgs.length === 0) continue;
1893
2604
  const nameArg = paramArgs[0];
1894
- if (!Node6.isStringLiteral(nameArg)) continue;
2605
+ if (!Node9.isStringLiteral(nameArg)) continue;
1895
2606
  const paramName = nameArg.getLiteralValue();
1896
2607
  const typeNode = param.getTypeNode();
1897
2608
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -1899,6 +2610,22 @@ function extractParamsType(method, sourceFile, project) {
1899
2610
  }
1900
2611
  return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
1901
2612
  }
2613
+ function extractPipedParamTypes(method, project) {
2614
+ const types = {};
2615
+ for (const param of method.getParameters()) {
2616
+ const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
2617
+ if (!paramDecorator) continue;
2618
+ const piped = pipeSchemaOf(paramDecorator, project);
2619
+ if (!piped) continue;
2620
+ const name = namedField(paramDecorator);
2621
+ if (name !== null) {
2622
+ types[name] = piped.type;
2623
+ } else {
2624
+ for (const field of piped.fields ?? []) types[field.name] = field.type;
2625
+ }
2626
+ }
2627
+ return Object.keys(types).length > 0 ? types : null;
2628
+ }
1902
2629
  function extractUploadedFiles(method) {
1903
2630
  const FILE = "File | Blob";
1904
2631
  const entries = [];
@@ -1912,28 +2639,28 @@ function extractUploadedFiles(method) {
1912
2639
  for (const decorator of method.getDecorators()) {
1913
2640
  if (decorator.getName() !== "UseInterceptors") continue;
1914
2641
  for (const arg of decorator.getArguments()) {
1915
- if (!Node6.isCallExpression(arg)) continue;
2642
+ if (!Node9.isCallExpression(arg)) continue;
1916
2643
  const interceptor = arg.getExpression().getText();
1917
2644
  const callArgs = arg.getArguments();
1918
2645
  const firstArg2 = callArgs[0];
1919
2646
  if (interceptor === "FileInterceptor") {
1920
- if (firstArg2 && Node6.isStringLiteral(firstArg2)) {
2647
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
1921
2648
  entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
1922
2649
  multipart = true;
1923
2650
  }
1924
2651
  } else if (interceptor === "FilesInterceptor") {
1925
- if (firstArg2 && Node6.isStringLiteral(firstArg2)) {
2652
+ if (firstArg2 && Node9.isStringLiteral(firstArg2)) {
1926
2653
  entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
1927
2654
  multipart = true;
1928
2655
  }
1929
2656
  } else if (interceptor === "FileFieldsInterceptor") {
1930
- if (firstArg2 && Node6.isArrayLiteralExpression(firstArg2)) {
2657
+ if (firstArg2 && Node9.isArrayLiteralExpression(firstArg2)) {
1931
2658
  for (const el of firstArg2.getElements()) {
1932
- if (!Node6.isObjectLiteralExpression(el)) continue;
2659
+ if (!Node9.isObjectLiteralExpression(el)) continue;
1933
2660
  const nameProp = el.getProperty("name");
1934
- if (nameProp && Node6.isPropertyAssignment(nameProp)) {
2661
+ if (nameProp && Node9.isPropertyAssignment(nameProp)) {
1935
2662
  const init = nameProp.getInitializer();
1936
- if (init && Node6.isStringLiteral(init)) {
2663
+ if (init && Node9.isStringLiteral(init)) {
1937
2664
  entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
1938
2665
  }
1939
2666
  }
@@ -1953,13 +2680,13 @@ function extractResponseType(method, sourceFile, project) {
1953
2680
  if (apiResponseDecorator) {
1954
2681
  const args = apiResponseDecorator.getArguments();
1955
2682
  const optsArg = args[0];
1956
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
2683
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
1957
2684
  for (const prop of optsArg.getProperties()) {
1958
- if (!Node6.isPropertyAssignment(prop)) continue;
2685
+ if (!Node9.isPropertyAssignment(prop)) continue;
1959
2686
  if (prop.getName() !== "type") continue;
1960
2687
  const val = prop.getInitializer();
1961
2688
  if (!val) continue;
1962
- if (Node6.isArrayLiteralExpression(val)) {
2689
+ if (Node9.isArrayLiteralExpression(val)) {
1963
2690
  const elements = val.getElements();
1964
2691
  const firstEl = elements[0];
1965
2692
  if (elements.length > 0 && firstEl !== void 0) {
@@ -1980,24 +2707,24 @@ function extractResponseType(method, sourceFile, project) {
1980
2707
  }
1981
2708
  function apiResponseStatus(decorator) {
1982
2709
  const optsArg = decorator.getArguments()[0];
1983
- if (!optsArg || !Node6.isObjectLiteralExpression(optsArg)) return null;
2710
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
1984
2711
  for (const prop of optsArg.getProperties()) {
1985
- if (!Node6.isPropertyAssignment(prop)) continue;
2712
+ if (!Node9.isPropertyAssignment(prop)) continue;
1986
2713
  if (prop.getName() !== "status") continue;
1987
2714
  const val = prop.getInitializer();
1988
- if (val && Node6.isNumericLiteral(val)) return Number(val.getLiteralValue());
2715
+ if (val && Node9.isNumericLiteral(val)) return Number(val.getLiteralValue());
1989
2716
  }
1990
2717
  return null;
1991
2718
  }
1992
2719
  function apiResponseTypeNode(decorator) {
1993
2720
  const optsArg = decorator.getArguments()[0];
1994
- if (!optsArg || !Node6.isObjectLiteralExpression(optsArg)) return null;
2721
+ if (!optsArg || !Node9.isObjectLiteralExpression(optsArg)) return null;
1995
2722
  for (const prop of optsArg.getProperties()) {
1996
- if (!Node6.isPropertyAssignment(prop)) continue;
2723
+ if (!Node9.isPropertyAssignment(prop)) continue;
1997
2724
  if (prop.getName() !== "type") continue;
1998
2725
  const val = prop.getInitializer();
1999
2726
  if (!val) return null;
2000
- if (Node6.isArrayLiteralExpression(val)) {
2727
+ if (Node9.isArrayLiteralExpression(val)) {
2001
2728
  const first = val.getElements()[0];
2002
2729
  return first ? { node: first, isArray: true } : null;
2003
2730
  }
@@ -2015,7 +2742,7 @@ function extractErrorType(method, sourceFile, project) {
2015
2742
  const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
2016
2743
  const type = typeInfo.isArray ? `Array<${inner}>` : inner;
2017
2744
  let ref = null;
2018
- if (Node6.isIdentifier(typeInfo.node)) {
2745
+ if (Node9.isIdentifier(typeInfo.node)) {
2019
2746
  const name = typeInfo.node.getText();
2020
2747
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
2021
2748
  if (localDecl?.isExported()) {
@@ -2032,7 +2759,7 @@ function extractErrorType(method, sourceFile, project) {
2032
2759
  return null;
2033
2760
  }
2034
2761
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
2035
- if (!Node6.isIdentifier(node)) return "unknown";
2762
+ if (!Node9.isIdentifier(node)) return "unknown";
2036
2763
  const name = node.getText();
2037
2764
  const resolved = findType(name, sourceFile, project);
2038
2765
  if (resolved) {
@@ -2052,9 +2779,9 @@ var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLik
2052
2779
  var BINARY_RESPONSE_TYPES = /* @__PURE__ */ new Set(["StreamableFile", "Buffer"]);
2053
2780
  function detectBinaryResponse(method) {
2054
2781
  const node = unwrapNamedContainer(method.getReturnTypeNode(), /* @__PURE__ */ new Set(["Promise"]));
2055
- if (!node || !Node6.isTypeReference(node)) return false;
2782
+ if (!node || !Node9.isTypeReference(node)) return false;
2056
2783
  const typeName = node.getTypeName();
2057
- const name = Node6.isIdentifier(typeName) ? typeName.getText() : "";
2784
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
2058
2785
  return BINARY_RESPONSE_TYPES.has(name);
2059
2786
  }
2060
2787
  function detectStreamElement(method) {
@@ -2069,9 +2796,9 @@ function detectStreamElement(method) {
2069
2796
  return null;
2070
2797
  }
2071
2798
  function streamContainerElement(node) {
2072
- if (!node || !Node6.isTypeReference(node)) return null;
2799
+ if (!node || !Node9.isTypeReference(node)) return null;
2073
2800
  const typeName = node.getTypeName();
2074
- const name = Node6.isIdentifier(typeName) ? typeName.getText() : "";
2801
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
2075
2802
  if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
2076
2803
  return node.getTypeArguments()[0] ?? null;
2077
2804
  }
@@ -2081,9 +2808,9 @@ function hasAsQueryDecorator(method) {
2081
2808
  return method.getDecorators().some((d) => d.getName() === "AsQuery");
2082
2809
  }
2083
2810
  function unwrapNamedContainer(node, names) {
2084
- if (!node || !Node6.isTypeReference(node)) return node;
2811
+ if (!node || !Node9.isTypeReference(node)) return node;
2085
2812
  const typeName = node.getTypeName();
2086
- const name = Node6.isIdentifier(typeName) ? typeName.getText() : "";
2813
+ const name = Node9.isIdentifier(typeName) ? typeName.getText() : "";
2087
2814
  if (names.has(name)) {
2088
2815
  return node.getTypeArguments()[0] ?? node;
2089
2816
  }
@@ -2104,19 +2831,20 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2104
2831
  body = body ?? bodyType;
2105
2832
  }
2106
2833
  const paramsType = extractParamsType(method, sourceFile, project);
2834
+ const paramTypes = extractPipedParamTypes(method, project);
2107
2835
  const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
2108
2836
  const errorInfo = extractErrorType(method, sourceFile, project);
2109
- if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
2837
+ if (body === null && query === null && paramsType === null && paramTypes === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart && !binaryResponse && !asQuery) {
2110
2838
  return null;
2111
2839
  }
2112
2840
  let bodyRef = null;
2113
2841
  let queryRef = null;
2114
2842
  let responseRef = null;
2115
2843
  for (const param of method.getParameters()) {
2116
- if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode()) {
2844
+ if (param.getDecorators().some((d) => d.getName() === "Body") && param.getTypeNode() && !hasPipeSchema(param, "Body", project)) {
2117
2845
  bodyRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
2118
2846
  }
2119
- if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode()) {
2847
+ if (param.getDecorators().some((d) => d.getName() === "Query") && param.getTypeNode() && !hasPipeSchema(param, "Query", project)) {
2120
2848
  queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
2121
2849
  }
2122
2850
  }
@@ -2129,11 +2857,11 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2129
2857
  if (apiResp) {
2130
2858
  const args = apiResp.getArguments();
2131
2859
  const optsArg = args[0];
2132
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
2860
+ if (optsArg && Node9.isObjectLiteralExpression(optsArg)) {
2133
2861
  for (const prop of optsArg.getProperties()) {
2134
- if (Node6.isPropertyAssignment(prop) && prop.getName() === "type") {
2862
+ if (Node9.isPropertyAssignment(prop) && prop.getName() === "type") {
2135
2863
  const val = prop.getInitializer();
2136
- if (val && Node6.isIdentifier(val)) {
2864
+ if (val && Node9.isIdentifier(val)) {
2137
2865
  const name = val.getText();
2138
2866
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
2139
2867
  if (localDecl?.isExported()) {
@@ -2169,6 +2897,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2169
2897
  response,
2170
2898
  error: errorInfo?.type ?? null,
2171
2899
  params: paramsType,
2900
+ paramTypes,
2172
2901
  queryRef,
2173
2902
  bodyRef,
2174
2903
  responseRef,
@@ -2189,6 +2918,7 @@ function extractDtoContract(method, sourceFile, project, mixin, controllerClass)
2189
2918
  function resolveParamClass(method, decoratorName, sourceFile, project) {
2190
2919
  for (const param of method.getParameters()) {
2191
2920
  if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
2921
+ if (hasPipeSchema(param, decoratorName, project)) continue;
2192
2922
  const typeNode = param.getTypeNode();
2193
2923
  if (!typeNode) continue;
2194
2924
  const text = typeNode.getText().replace(/\[\]$/, "");
@@ -2201,129 +2931,6 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
2201
2931
  return null;
2202
2932
  }
2203
2933
 
2204
- // src/discovery/zod-ast-to-ts.ts
2205
- import { Node as Node7, SyntaxKind as SyntaxKind3 } from "ts-morph";
2206
- function zodAstToTs(node) {
2207
- if (!Node7.isCallExpression(node)) return "unknown";
2208
- const expr = node.getExpression();
2209
- if (Node7.isPropertyAccessExpression(expr)) {
2210
- const methodName = expr.getName();
2211
- const receiver = expr.getExpression();
2212
- if (methodName === "optional") {
2213
- return `${zodAstToTs(receiver)} | undefined`;
2214
- }
2215
- if (methodName === "nullable") {
2216
- return `${zodAstToTs(receiver)} | null`;
2217
- }
2218
- const args = node.getArguments();
2219
- switch (methodName) {
2220
- case "string":
2221
- return "string";
2222
- case "number":
2223
- return "number";
2224
- case "boolean":
2225
- return "boolean";
2226
- case "unknown":
2227
- return "unknown";
2228
- case "any":
2229
- return "unknown";
2230
- case "literal": {
2231
- const lit = args[0];
2232
- if (!lit) return "unknown";
2233
- if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2234
- if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2235
- if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
2236
- if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
2237
- return "unknown";
2238
- }
2239
- case "enum": {
2240
- const arrArg = args[0];
2241
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2242
- const members = arrArg.getElements().map(
2243
- (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2244
- );
2245
- return members.join(" | ");
2246
- }
2247
- case "array": {
2248
- const inner = args[0];
2249
- if (!inner) return "unknown";
2250
- return `Array<${zodAstToTs(inner)}>`;
2251
- }
2252
- case "object": {
2253
- const objArg = args[0];
2254
- if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
2255
- const lines = [];
2256
- for (const prop of objArg.getProperties()) {
2257
- if (!Node7.isPropertyAssignment(prop)) continue;
2258
- const key = prop.getName();
2259
- const valNode = prop.getInitializer();
2260
- if (!valNode) continue;
2261
- const tsType = zodAstToTs(valNode);
2262
- const isOpt = isOptionalChain(valNode);
2263
- lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
2264
- }
2265
- return `{ ${lines.join("; ")} }`;
2266
- }
2267
- case "union": {
2268
- const arrArg = args[0];
2269
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2270
- return arrArg.getElements().map(zodAstToTs).join(" | ");
2271
- }
2272
- case "record": {
2273
- const valArg = args.length === 1 ? args[0] : args[1];
2274
- if (!valArg) return "unknown";
2275
- return `Record<string, ${zodAstToTs(valArg)}>`;
2276
- }
2277
- case "tuple": {
2278
- const arrArg = args[0];
2279
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2280
- return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2281
- }
2282
- default:
2283
- return "unknown";
2284
- }
2285
- }
2286
- return "unknown";
2287
- }
2288
- function isOptionalChain(node) {
2289
- if (!Node7.isCallExpression(node)) return false;
2290
- const expr = node.getExpression();
2291
- return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
2292
- }
2293
- function parseDefineContractCall(callExpr) {
2294
- if (!Node7.isCallExpression(callExpr)) return null;
2295
- const callee = callExpr.getExpression();
2296
- const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
2297
- if (calleeName !== "defineContract") return null;
2298
- const args = callExpr.getArguments();
2299
- const optsArg = args[0];
2300
- if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
2301
- let query = null;
2302
- let body = null;
2303
- let response = "unknown";
2304
- let error = null;
2305
- let bodyZodText = null;
2306
- let queryZodText = null;
2307
- for (const prop of optsArg.getProperties()) {
2308
- if (!Node7.isPropertyAssignment(prop)) continue;
2309
- const propName = prop.getName();
2310
- const val = prop.getInitializer();
2311
- if (!val) continue;
2312
- if (propName === "query") {
2313
- query = zodAstToTs(val);
2314
- queryZodText = val.getText();
2315
- } else if (propName === "body") {
2316
- body = zodAstToTs(val);
2317
- bodyZodText = val.getText();
2318
- } else if (propName === "response") {
2319
- response = zodAstToTs(val);
2320
- } else if (propName === "error") {
2321
- error = zodAstToTs(val);
2322
- }
2323
- }
2324
- return { query, body, response, error, bodyZodText, queryZodText };
2325
- }
2326
-
2327
2934
  // src/discovery/contracts-fast.ts
2328
2935
  function resolveTsconfigPath(cwd, tsconfig) {
2329
2936
  return tsconfig ? resolve3(tsconfig) : join2(cwd, "tsconfig.json");
@@ -2335,25 +2942,25 @@ var NO_TSCONFIG_OPTIONS = {
2335
2942
  };
2336
2943
  function loadDiscoveryTsconfig(tsconfigPath) {
2337
2944
  if (!existsSync(tsconfigPath)) return { options: null, files: [] };
2338
- const read = ts.readConfigFile(tsconfigPath, (path) => ts.sys.readFile(path));
2945
+ const read = ts3.readConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
2339
2946
  if (read.error) {
2340
2947
  return {
2341
2948
  options: null,
2342
2949
  files: [tsconfigPath],
2343
- error: ts.flattenDiagnosticMessageText(read.error.messageText, " ")
2950
+ error: ts3.flattenDiagnosticMessageText(read.error.messageText, " ")
2344
2951
  };
2345
2952
  }
2346
2953
  const host = {
2347
- useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
2954
+ useCaseSensitiveFileNames: ts3.sys.useCaseSensitiveFileNames,
2348
2955
  // The one line that keeps a tsconfig from being a filesystem walk.
2349
2956
  readDirectory: () => [],
2350
- fileExists: (path) => ts.sys.fileExists(path),
2957
+ fileExists: (path) => ts3.sys.fileExists(path),
2351
2958
  // `extends` is resolved through the host, so a consumer whose `paths` live
2352
2959
  // in a base tsconfig keeps working.
2353
- readFile: (path) => ts.sys.readFile(path)
2960
+ readFile: (path) => ts3.sys.readFile(path)
2354
2961
  };
2355
- const sourceFile = ts.readJsonConfigFile(tsconfigPath, (path) => ts.sys.readFile(path));
2356
- const parsed = ts.parseJsonSourceFileConfigFileContent(
2962
+ const sourceFile = ts3.readJsonConfigFile(tsconfigPath, (path) => ts3.sys.readFile(path));
2963
+ const parsed = ts3.parseJsonSourceFileConfigFileContent(
2357
2964
  sourceFile,
2358
2965
  host,
2359
2966
  dirname2(tsconfigPath),
@@ -2376,7 +2983,7 @@ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(t
2376
2983
  `[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.`
2377
2984
  );
2378
2985
  }
2379
- return new Project2({
2986
+ return new Project3({
2380
2987
  compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
2381
2988
  skipAddingFilesFromTsConfig: true,
2382
2989
  skipLoadingLibFiles: true,
@@ -2481,15 +3088,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
2481
3088
  clearTypeResolutionCaches(this.project);
2482
3089
  clearEnumCache(this.project);
2483
3090
  clearMixinTypeProject();
3091
+ clearSchemaTypeProject(this.project);
2484
3092
  return extractRoutesFrom(this.project, this.controllerPaths);
2485
3093
  }
2486
3094
  };
2487
3095
  function decoratorStringArg(decoratorExpr) {
2488
3096
  if (!decoratorExpr) return void 0;
2489
- if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
2490
- if (Node8.isArrayLiteralExpression(decoratorExpr)) {
3097
+ if (Node10.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3098
+ if (Node10.isArrayLiteralExpression(decoratorExpr)) {
2491
3099
  const first = decoratorExpr.getElements()[0];
2492
- if (first && Node8.isStringLiteral(first)) return first.getLiteralValue();
3100
+ if (first && Node10.isStringLiteral(first)) return first.getLiteralValue();
2493
3101
  }
2494
3102
  return void 0;
2495
3103
  }
@@ -2610,9 +3218,9 @@ function extractContractRoute(args) {
2610
3218
  let contractDef = null;
2611
3219
  let bodyZodRef = null;
2612
3220
  let queryZodRef = null;
2613
- if (Node8.isCallExpression(firstDecoratorArg)) {
2614
- contractDef = parseDefineContractCall(firstDecoratorArg);
2615
- } else if (Node8.isIdentifier(firstDecoratorArg)) {
3221
+ if (Node10.isCallExpression(firstDecoratorArg)) {
3222
+ contractDef = parseDefineContractCall(firstDecoratorArg, project);
3223
+ } else if (Node10.isIdentifier(firstDecoratorArg)) {
2616
3224
  const identName = firstDecoratorArg.getText();
2617
3225
  const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
2618
3226
  if (!resolvedVar) {
@@ -2624,7 +3232,7 @@ function extractContractRoute(args) {
2624
3232
  const { decl: varDecl, file: declFile } = resolvedVar;
2625
3233
  const initializer = varDecl.getInitializer();
2626
3234
  if (!initializer) return null;
2627
- contractDef = parseDefineContractCall(initializer);
3235
+ contractDef = parseDefineContractCall(initializer, project);
2628
3236
  if (contractDef && varDecl.isExported()) {
2629
3237
  const filePath = declFile.getFilePath();
2630
3238
  if (contractDef.body !== null) {
@@ -2695,6 +3303,7 @@ function extractDtoRoute(args) {
2695
3303
  body: dtoContract?.body ?? null,
2696
3304
  response: mixinResponse ?? dtoContract?.response ?? "unknown",
2697
3305
  error: dtoContract?.error ?? null,
3306
+ paramTypes: dtoContract?.paramTypes ?? null,
2698
3307
  queryRef: dtoContract?.queryRef ?? null,
2699
3308
  bodyRef: dtoContract?.bodyRef ?? null,
2700
3309
  responseRef: dtoContract?.responseRef ?? null,
@@ -2766,32 +3375,86 @@ function extractFromSourceFile(sourceFile, project) {
2766
3375
  return routes;
2767
3376
  }
2768
3377
 
2769
- // src/generate.ts
2770
- import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
2771
- import { dirname as dirname4, join as join13 } from "path";
2772
-
2773
3378
  // src/discovery/pages.ts
2774
3379
  import { readFile } from "fs/promises";
2775
- import { join as join3, relative as relative2 } from "path";
3380
+ import { join as join3, relative as relative2, sep as sep2 } from "path";
2776
3381
  import fg2 from "fast-glob";
2777
3382
  var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
3383
+ var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
3384
+ function globStaticBase(glob) {
3385
+ const segments = glob.split("/");
3386
+ const staticSegments = [];
3387
+ for (const segment of segments.slice(0, -1)) {
3388
+ if (MAGIC_SEGMENT_RE.test(segment)) break;
3389
+ staticSegments.push(segment);
3390
+ }
3391
+ return staticSegments.join("/");
3392
+ }
2778
3393
  async function discoverPages(opts) {
2779
- const allFiles = await fg2(opts.glob, { cwd: opts.cwd, absolute: true });
2780
- const files = allFiles.filter((f) => !NON_PAGE_FILE_RE.test(f));
2781
- files.sort();
2782
- const globStatic = opts.glob.split("*")[0]?.replace(/\/$/, "") ?? "";
2783
- const pagesBase = join3(opts.cwd, globStatic);
3394
+ const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
3395
+ const prefix = opts.prefix ?? "";
3396
+ const scope = opts.scope ?? "default";
3397
+ const seenFiles = /* @__PURE__ */ new Set();
2784
3398
  const out = [];
2785
- for (const file of files) {
2786
- const rel = relative2(opts.cwd, file);
2787
- const nameRel = relative2(pagesBase, file);
2788
- const name = computeName(nameRel, opts.componentNameStrategy);
2789
- const source = await readFile(file, "utf8");
2790
- const propsSource = extractPropsSource(source, opts.propsExport);
2791
- out.push({ name, absolutePath: file, relativePath: rel, propsSource });
3399
+ for (const glob of globs) {
3400
+ const files = (await fg2(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
3401
+ const pagesBase = join3(opts.cwd, globStaticBase(glob));
3402
+ for (const file of files) {
3403
+ seenFiles.add(file);
3404
+ const rel = relative2(opts.cwd, file);
3405
+ const nameRel = relative2(pagesBase, file).split(sep2).join("/");
3406
+ const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
3407
+ const source = await readFile(file, "utf8");
3408
+ const propsSource = extractPropsSource(source, opts.propsExport);
3409
+ out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
3410
+ }
3411
+ }
3412
+ assertUniqueNames(out);
3413
+ return out;
3414
+ }
3415
+ async function discoverPageScopes(config) {
3416
+ const pages = config.pages;
3417
+ if (!pages) return [];
3418
+ const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
3419
+ const out = await discoverPages({
3420
+ ...common,
3421
+ glob: pages.glob,
3422
+ componentNameStrategy: pages.componentNameStrategy
3423
+ });
3424
+ for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
3425
+ out.push(
3426
+ ...await discoverPages({
3427
+ ...common,
3428
+ glob: scopeConfig.glob,
3429
+ componentNameStrategy: scopeConfig.componentNameStrategy,
3430
+ prefix: scopeConfig.prefix,
3431
+ scope
3432
+ })
3433
+ );
2792
3434
  }
3435
+ assertUniqueNames(out);
2793
3436
  return out;
2794
3437
  }
3438
+ function allPageGlobs(config) {
3439
+ if (!config.pages) return [];
3440
+ const asList = (glob) => typeof glob === "string" ? [glob] : glob;
3441
+ return [
3442
+ ...asList(config.pages.glob),
3443
+ ...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
3444
+ ];
3445
+ }
3446
+ function assertUniqueNames(pages) {
3447
+ const byName = /* @__PURE__ */ new Map();
3448
+ for (const page of pages) {
3449
+ const previous = byName.get(page.name);
3450
+ if (previous) {
3451
+ throw new Error(
3452
+ `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.`
3453
+ );
3454
+ }
3455
+ byName.set(page.name, page);
3456
+ }
3457
+ }
2795
3458
  function computeName(rel, strat) {
2796
3459
  if (typeof strat === "function") return strat(rel);
2797
3460
  const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
@@ -2822,9 +3485,13 @@ function extractPropsSource(source, exportName) {
2822
3485
  return source.slice(start);
2823
3486
  }
2824
3487
 
3488
+ // src/generate.ts
3489
+ import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
3490
+ import { dirname as dirname4, join as join13 } from "path";
3491
+
2825
3492
  // src/discovery/shared-props.ts
2826
3493
  import { join as join4 } from "path";
2827
- import { Node as Node9, Project as Project3, SyntaxKind as SyntaxKind4 } from "ts-morph";
3494
+ import { Node as Node11, Project as Project4, SyntaxKind as SyntaxKind4 } from "ts-morph";
2828
3495
  function discoverSharedProps(project, moduleEntry) {
2829
3496
  try {
2830
3497
  let sourceFile = project.getSourceFile(moduleEntry);
@@ -2850,14 +3517,14 @@ function discoverSharedPropsFromConfig(config) {
2850
3517
  const tsconfigPath = config.app.tsconfig ?? join4(config.codegen.cwd, "tsconfig.json");
2851
3518
  let project;
2852
3519
  try {
2853
- project = new Project3({
3520
+ project = new Project4({
2854
3521
  tsConfigFilePath: tsconfigPath,
2855
3522
  skipAddingFilesFromTsConfig: true,
2856
3523
  skipLoadingLibFiles: true,
2857
3524
  skipFileDependencyResolution: true
2858
3525
  });
2859
3526
  } catch {
2860
- project = new Project3({
3527
+ project = new Project4({
2861
3528
  skipAddingFilesFromTsConfig: true,
2862
3529
  skipLoadingLibFiles: true,
2863
3530
  skipFileDependencyResolution: true,
@@ -2873,10 +3540,10 @@ function findForRootCall(sourceFile) {
2873
3540
  const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
2874
3541
  for (const call of callExpressions) {
2875
3542
  const expr = call.getExpression();
2876
- if (!Node9.isPropertyAccessExpression(expr)) continue;
3543
+ if (!Node11.isPropertyAccessExpression(expr)) continue;
2877
3544
  const methodName = expr.getName();
2878
3545
  const objectExpr = expr.getExpression();
2879
- if (methodName === "forRoot" && Node9.isIdentifier(objectExpr)) {
3546
+ if (methodName === "forRoot" && Node11.isIdentifier(objectExpr)) {
2880
3547
  const name = objectExpr.getText();
2881
3548
  if (name === "InertiaModule") {
2882
3549
  return call;
@@ -2886,19 +3553,19 @@ function findForRootCall(sourceFile) {
2886
3553
  return null;
2887
3554
  }
2888
3555
  function findShareInitializer(forRootCall) {
2889
- if (!Node9.isCallExpression(forRootCall)) return null;
3556
+ if (!Node11.isCallExpression(forRootCall)) return null;
2890
3557
  const args = forRootCall.getArguments();
2891
3558
  const firstArg2 = args[0];
2892
- if (!firstArg2 || !Node9.isObjectLiteralExpression(firstArg2)) return null;
3559
+ if (!firstArg2 || !Node11.isObjectLiteralExpression(firstArg2)) return null;
2893
3560
  for (const prop of firstArg2.getProperties()) {
2894
- if (Node9.isPropertyAssignment(prop) && prop.getName() === "share") {
3561
+ if (Node11.isPropertyAssignment(prop) && prop.getName() === "share") {
2895
3562
  return prop.getInitializer() ?? null;
2896
3563
  }
2897
3564
  }
2898
3565
  return null;
2899
3566
  }
2900
3567
  function extractShareType(node, sourceFile, project) {
2901
- if (Node9.isIdentifier(node)) {
3568
+ if (Node11.isIdentifier(node)) {
2902
3569
  const ref = resolveIdentifierToImportRef(node, sourceFile, project);
2903
3570
  if (ref) {
2904
3571
  return {
@@ -2908,22 +3575,22 @@ function extractShareType(node, sourceFile, project) {
2908
3575
  };
2909
3576
  }
2910
3577
  }
2911
- if (Node9.isArrowFunction(node)) {
3578
+ if (Node11.isArrowFunction(node)) {
2912
3579
  const result = extractFromFunctionLike(node, sourceFile);
2913
3580
  return result ? { ...result, isImportRef: false } : null;
2914
3581
  }
2915
- if (Node9.isFunctionExpression(node)) {
3582
+ if (Node11.isFunctionExpression(node)) {
2916
3583
  const result = extractFromFunctionLike(node, sourceFile);
2917
3584
  return result ? { ...result, isImportRef: false } : null;
2918
3585
  }
2919
- if (Node9.isObjectLiteralExpression(node)) {
3586
+ if (Node11.isObjectLiteralExpression(node)) {
2920
3587
  const result = extractFromObjectLiteral(node);
2921
3588
  return result ? { ...result, isImportRef: false } : null;
2922
3589
  }
2923
3590
  return null;
2924
3591
  }
2925
3592
  function resolveIdentifierToImportRef(id, sourceFile, project) {
2926
- if (!Node9.isIdentifier(id)) return null;
3593
+ if (!Node11.isIdentifier(id)) return null;
2927
3594
  const name = id.getText();
2928
3595
  const localFunc = sourceFile.getFunction(name);
2929
3596
  if (localFunc?.isExported()) {
@@ -2957,37 +3624,37 @@ function resolveIdentifierToImportRef(id, sourceFile, project) {
2957
3624
  return null;
2958
3625
  }
2959
3626
  function extractFromFunctionLike(node, _sourceFile) {
2960
- const returnTypeNode = Node9.isArrowFunction(node) || Node9.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
3627
+ const returnTypeNode = Node11.isArrowFunction(node) || Node11.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
2961
3628
  if (returnTypeNode) {
2962
3629
  return extractFromReturnTypeAnnotation(returnTypeNode);
2963
3630
  }
2964
- if (Node9.isArrowFunction(node)) {
3631
+ if (Node11.isArrowFunction(node)) {
2965
3632
  const body = node.getBody();
2966
- if (Node9.isParenthesizedExpression(body)) {
3633
+ if (Node11.isParenthesizedExpression(body)) {
2967
3634
  const inner = body.getExpression();
2968
- if (Node9.isObjectLiteralExpression(inner)) {
3635
+ if (Node11.isObjectLiteralExpression(inner)) {
2969
3636
  return extractFromObjectLiteral(inner);
2970
3637
  }
2971
3638
  }
2972
- if (Node9.isObjectLiteralExpression(body)) {
3639
+ if (Node11.isObjectLiteralExpression(body)) {
2973
3640
  return extractFromObjectLiteral(body);
2974
3641
  }
2975
- if (Node9.isBlock(body)) {
3642
+ if (Node11.isBlock(body)) {
2976
3643
  return extractFromBlockReturn(body);
2977
3644
  }
2978
3645
  }
2979
- if (Node9.isFunctionExpression(node)) {
3646
+ if (Node11.isFunctionExpression(node)) {
2980
3647
  const body = node.getBody();
2981
- if (Node9.isBlock(body)) {
3648
+ if (Node11.isBlock(body)) {
2982
3649
  return extractFromBlockReturn(body);
2983
3650
  }
2984
3651
  }
2985
3652
  return null;
2986
3653
  }
2987
3654
  function extractFromReturnTypeAnnotation(typeNode) {
2988
- if (Node9.isTypeReference(typeNode)) {
3655
+ if (Node11.isTypeReference(typeNode)) {
2989
3656
  const typeName = typeNode.getTypeName();
2990
- if (Node9.isIdentifier(typeName) && typeName.getText() === "Promise") {
3657
+ if (Node11.isIdentifier(typeName) && typeName.getText() === "Promise") {
2991
3658
  const typeArgs = typeNode.getTypeArguments();
2992
3659
  const firstArg2 = typeArgs[0];
2993
3660
  if (firstArg2) {
@@ -2996,10 +3663,10 @@ function extractFromReturnTypeAnnotation(typeNode) {
2996
3663
  return null;
2997
3664
  }
2998
3665
  }
2999
- if (Node9.isTypeLiteral(typeNode)) {
3666
+ if (Node11.isTypeLiteral(typeNode)) {
3000
3667
  const properties = [];
3001
3668
  for (const member of typeNode.getMembers()) {
3002
- if (Node9.isPropertySignature(member)) {
3669
+ if (Node11.isPropertySignature(member)) {
3003
3670
  const name = member.getName();
3004
3671
  const memberTypeNode = member.getTypeNode();
3005
3672
  const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
@@ -3013,19 +3680,19 @@ function extractFromReturnTypeAnnotation(typeNode) {
3013
3680
  return null;
3014
3681
  }
3015
3682
  function extractFromBlockReturn(block) {
3016
- if (!Node9.isBlock(block)) return null;
3683
+ if (!Node11.isBlock(block)) return null;
3017
3684
  const statements = block.getStatements();
3018
3685
  for (let i = statements.length - 1; i >= 0; i--) {
3019
3686
  const stmt = statements[i];
3020
- if (!Node9.isReturnStatement(stmt)) continue;
3687
+ if (!Node11.isReturnStatement(stmt)) continue;
3021
3688
  const expr = stmt.getExpression();
3022
3689
  if (!expr) continue;
3023
- if (Node9.isObjectLiteralExpression(expr)) {
3690
+ if (Node11.isObjectLiteralExpression(expr)) {
3024
3691
  return extractFromObjectLiteral(expr);
3025
3692
  }
3026
- if (Node9.isParenthesizedExpression(expr)) {
3693
+ if (Node11.isParenthesizedExpression(expr)) {
3027
3694
  const inner = expr.getExpression();
3028
- if (Node9.isObjectLiteralExpression(inner)) {
3695
+ if (Node11.isObjectLiteralExpression(inner)) {
3029
3696
  return extractFromObjectLiteral(inner);
3030
3697
  }
3031
3698
  }
@@ -3034,10 +3701,10 @@ function extractFromBlockReturn(block) {
3034
3701
  return null;
3035
3702
  }
3036
3703
  function extractFromObjectLiteral(objLiteral) {
3037
- if (!Node9.isObjectLiteralExpression(objLiteral)) return null;
3704
+ if (!Node11.isObjectLiteralExpression(objLiteral)) return null;
3038
3705
  const properties = [];
3039
3706
  for (const prop of objLiteral.getProperties()) {
3040
- if (!Node9.isPropertyAssignment(prop)) continue;
3707
+ if (!Node11.isPropertyAssignment(prop)) continue;
3041
3708
  const name = prop.getName();
3042
3709
  const initializer = prop.getInitializer();
3043
3710
  if (!initializer) continue;
@@ -3049,21 +3716,21 @@ function extractFromObjectLiteral(objLiteral) {
3049
3716
  return { typeString, properties, isImportRef: false };
3050
3717
  }
3051
3718
  function inferExpressionType(node) {
3052
- if (Node9.isStringLiteral(node)) return "string";
3053
- if (Node9.isTemplateExpression(node) || Node9.isNoSubstitutionTemplateLiteral(node))
3719
+ if (Node11.isStringLiteral(node)) return "string";
3720
+ if (Node11.isTemplateExpression(node) || Node11.isNoSubstitutionTemplateLiteral(node))
3054
3721
  return "string";
3055
- if (Node9.isNumericLiteral(node)) return "number";
3722
+ if (Node11.isNumericLiteral(node)) return "number";
3056
3723
  if (node.getKind() === SyntaxKind4.TrueKeyword || node.getKind() === SyntaxKind4.FalseKeyword) {
3057
3724
  return "boolean";
3058
3725
  }
3059
3726
  if (node.getKind() === SyntaxKind4.NullKeyword) return "null";
3060
- if (Node9.isIdentifier(node) && node.getText() === "undefined") return "undefined";
3061
- if (Node9.isObjectLiteralExpression(node)) {
3727
+ if (Node11.isIdentifier(node) && node.getText() === "undefined") return "undefined";
3728
+ if (Node11.isObjectLiteralExpression(node)) {
3062
3729
  const props = node.getProperties();
3063
3730
  if (props.length === 0) return "Record<string, unknown>";
3064
3731
  const entries = [];
3065
3732
  for (const prop of props) {
3066
- if (!Node9.isPropertyAssignment(prop)) continue;
3733
+ if (!Node11.isPropertyAssignment(prop)) continue;
3067
3734
  const key = prop.getName();
3068
3735
  const init = prop.getInitializer();
3069
3736
  if (!init) continue;
@@ -3072,23 +3739,23 @@ function inferExpressionType(node) {
3072
3739
  if (entries.length === 0) return "Record<string, unknown>";
3073
3740
  return `{ ${entries.join("; ")} }`;
3074
3741
  }
3075
- if (Node9.isArrayLiteralExpression(node)) {
3742
+ if (Node11.isArrayLiteralExpression(node)) {
3076
3743
  const elements = node.getElements();
3077
3744
  if (elements.length === 0) return "Array<unknown>";
3078
3745
  const first = elements[0];
3079
3746
  if (first) return `Array<${inferExpressionType(first)}>`;
3080
3747
  return "Array<unknown>";
3081
3748
  }
3082
- if (Node9.isConditionalExpression(node)) {
3749
+ if (Node11.isConditionalExpression(node)) {
3083
3750
  const whenTrue = inferExpressionType(node.getWhenTrue());
3084
3751
  const whenFalse = inferExpressionType(node.getWhenFalse());
3085
3752
  if (whenTrue === whenFalse) return whenTrue;
3086
3753
  return `${whenTrue} | ${whenFalse}`;
3087
3754
  }
3088
- if (Node9.isParenthesizedExpression(node)) {
3755
+ if (Node11.isParenthesizedExpression(node)) {
3089
3756
  return inferExpressionType(node.getExpression());
3090
3757
  }
3091
- if (Node9.isAsExpression(node)) {
3758
+ if (Node11.isAsExpression(node)) {
3092
3759
  const typeNode = node.getTypeNode();
3093
3760
  if (typeNode) return typeNode.getText();
3094
3761
  }
@@ -3101,7 +3768,7 @@ import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from
3101
3768
 
3102
3769
  // src/extension/registry.ts
3103
3770
  import { relative as relative3, resolve as resolve4 } from "path";
3104
- import { Project as Project4 } from "ts-morph";
3771
+ import { Project as Project5 } from "ts-morph";
3105
3772
  function resolveApiSlots(extensions) {
3106
3773
  let layer;
3107
3774
  let layerOwner;
@@ -3152,7 +3819,7 @@ function createExtensionContext(config, getRoutes, trackedInputs) {
3152
3819
  },
3153
3820
  project() {
3154
3821
  if (!project) {
3155
- project = new Project4({
3822
+ project = new Project5({
3156
3823
  skipAddingFilesFromTsConfig: true,
3157
3824
  skipLoadingLibFiles: true,
3158
3825
  skipFileDependencyResolution: true,
@@ -3270,10 +3937,11 @@ function insertIntoTree(tree, segments, leaf, fullName) {
3270
3937
  insertIntoTree(branch.children, rest, leaf, fullName);
3271
3938
  }
3272
3939
  }
3273
- function buildParamsType(params) {
3940
+ function buildParamsType(params, paramTypes) {
3274
3941
  const pathParams = params.filter((p) => p.source === "path");
3275
3942
  if (pathParams.length === 0) return "never";
3276
- return `{ ${pathParams.map((p) => `${p.name}: string`).join("; ")} }`;
3943
+ const typeOf = (name) => paramTypes && Object.hasOwn(paramTypes, name) && paramTypes[name] || "string";
3944
+ return `{ ${pathParams.map((p) => `${p.name}: ${typeOf(p.name)}`).join("; ")} }`;
3277
3945
  }
3278
3946
  function hasPathParams(params) {
3279
3947
  return params.some((p) => p.source === "path");
@@ -3367,7 +4035,7 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
3367
4035
  }
3368
4036
  const response = buildResponseType(c, outDir, serialization);
3369
4037
  const error = buildErrorType(c);
3370
- const params = buildParamsType(c.params);
4038
+ const params = buildParamsType(c.params, c.contractSource.paramTypes);
3371
4039
  const safeMethod = JSON.stringify(method);
3372
4040
  const safeUrl = JSON.stringify(c.path);
3373
4041
  const filterLiterals = filterFieldLiterals(c.contractSource.filterFields);
@@ -3455,28 +4123,28 @@ function renderFetcherRequest(req, binaryResponse) {
3455
4123
  }
3456
4124
  function emitReqHelper() {
3457
4125
  return [
3458
- "/** Awaitable request handle. `await api.x.y({...})` runs the fetch; extensions add query/mutation helpers. */",
3459
- "type __Req<R> = {",
3460
- " then<T1 = R, T2 = never>(",
4126
+ "/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
4127
+ "export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
4128
+ "class __Request<R> extends Promise<R> {",
4129
+ " static override get [Symbol.species](): PromiseConstructor {",
4130
+ " return Promise;",
4131
+ " }",
4132
+ " private __run: () => Promise<R>;",
4133
+ " private __p: Promise<R> | undefined;",
4134
+ " constructor(run: () => Promise<R>) {",
4135
+ " super(() => {});",
4136
+ " this.__run = run;",
4137
+ " }",
4138
+ " override then<T1 = R, T2 = never>(",
3461
4139
  " onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
3462
4140
  " onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
3463
- " ): Promise<T1 | T2>;",
3464
- " catch<T = never>(onrejected?: ((reason: unknown) => T | PromiseLike<T>) | null): Promise<R | T>;",
3465
- " finally(onfinally?: (() => void) | null): Promise<R>;",
3466
- " fetch(): Promise<R>;",
3467
- "};",
3468
- "function __req<R>(run: () => Promise<R>): __Req<R> {",
3469
- " let __p: Promise<R> | undefined;",
3470
- " const __promise = () => {",
3471
- " __p ??= run();",
3472
- " return __p;",
3473
- " };",
3474
- " return {",
3475
- " then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
3476
- " catch: (onrejected) => __promise().catch(onrejected),",
3477
- " finally: (onfinally) => __promise().finally(onfinally),",
3478
- " fetch: run,",
3479
- " };",
4141
+ " ): Promise<T1 | T2> {",
4142
+ " this.__p ??= this.__run();",
4143
+ " return this.__p.then(onfulfilled, onrejected);",
4144
+ " }",
4145
+ "}",
4146
+ "function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
4147
+ " return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
3480
4148
  "}",
3481
4149
  ""
3482
4150
  ];
@@ -3494,18 +4162,26 @@ function emitFilterFieldGuard() {
3494
4162
  ];
3495
4163
  }
3496
4164
  function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
3497
- const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
3498
- lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
4165
+ const memberLines = [];
3499
4166
  if (req.filterFieldsExpr) {
3500
- lines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
4167
+ memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
3501
4168
  }
3502
4169
  if (streamExpr) {
3503
- lines.push(`${pad} stream: () => ${streamExpr},`);
4170
+ memberLines.push(`${pad} stream: () => ${streamExpr},`);
3504
4171
  }
3505
4172
  for (const [name, value] of Object.entries(members)) {
3506
- lines.push(`${pad} ${name}: ${value},`);
3507
- }
3508
- lines.push(`${pad}}),`);
4173
+ memberLines.push(`${pad} ${name}: ${value},`);
4174
+ }
4175
+ const body = [requestExpr, ...memberLines].join("\n");
4176
+ const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
4177
+ const lines = [`${pad}${objKey}: (${param}) =>`];
4178
+ if (memberLines.length === 0) {
4179
+ lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
4180
+ return lines;
4181
+ }
4182
+ lines.push(`${pad} __req(() => ${requestExpr}, {`);
4183
+ lines.push(...memberLines);
4184
+ lines.push(`${pad} }),`);
3509
4185
  return lines;
3510
4186
  }
3511
4187
  function renderStreamExpr(req) {
@@ -3636,6 +4312,26 @@ var EMPTY_PATH_NAMESPACE = [
3636
4312
  "}",
3637
4313
  ""
3638
4314
  ];
4315
+ function isClientCallable(route) {
4316
+ return route.method.toUpperCase() !== "ALL";
4317
+ }
4318
+ var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
4319
+ var ROUTES_EXPORTS = [
4320
+ // Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
4321
+ { name: "route", typeOnly: false },
4322
+ { name: "ROUTES", typeOnly: false },
4323
+ { name: "RouteName", typeOnly: true },
4324
+ { name: "ExtractParams", typeOnly: true },
4325
+ { name: "RouteParams", typeOnly: true }
4326
+ ];
4327
+ function finishRoutesImport(lines) {
4328
+ const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
4329
+ const used = ROUTES_EXPORTS.filter(
4330
+ ({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
4331
+ ).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
4332
+ const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
4333
+ return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
4334
+ }
3639
4335
  function buildApiFile(routes, outDir, opts = {}) {
3640
4336
  const fetcherImportPath = opts.fetcherImportPath;
3641
4337
  const serialization = opts.serialization ?? "json";
@@ -3643,7 +4339,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3643
4339
  const { layer } = resolveApiSlots(extensions);
3644
4340
  const memberExts = extensions.filter((e) => e.apiMembers);
3645
4341
  const headerExts = extensions.filter((e) => e.apiHeader);
3646
- const contracted = routes.filter((r) => r.contract);
4342
+ const contracted = routes.filter((r) => r.contract && isClientCallable(r));
3647
4343
  const ctx = opts.ctx ?? {
3648
4344
  cwd: outDir ?? "",
3649
4345
  outDir: outDir ?? "",
@@ -3695,9 +4391,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3695
4391
  for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
3696
4392
  }
3697
4393
  lines.push(...extImports);
3698
- lines.push(
3699
- "import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
3700
- );
4394
+ lines.push(ROUTES_IMPORT_PLACEHOLDER);
3701
4395
  const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
3702
4396
  lines.push(`import type { Fetcher } from '${runtimeImport}';`);
3703
4397
  if (serialization === "json" && contracted.length > 0) {
@@ -3748,7 +4442,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3748
4442
  lines.push(...statements, "");
3749
4443
  }
3750
4444
  }
3751
- return lines.join("\n");
4445
+ return finishRoutesImport(lines);
3752
4446
  }
3753
4447
  const tree = /* @__PURE__ */ new Map();
3754
4448
  for (const r of contracted) {
@@ -3801,7 +4495,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3801
4495
  lines.push(...statements, "");
3802
4496
  }
3803
4497
  }
3804
- return lines.join("\n");
4498
+ return finishRoutesImport(lines);
3805
4499
  }
3806
4500
 
3807
4501
  // src/emit/emit-cache.ts
@@ -3814,6 +4508,7 @@ async function emitCache(pages, outDir) {
3814
4508
  const s = await stat(p.absolutePath);
3815
4509
  return {
3816
4510
  name: p.name,
4511
+ scope: p.scope ?? "default",
3817
4512
  relativePath: p.relativePath,
3818
4513
  mtime: s.mtime.toISOString()
3819
4514
  };
@@ -4561,6 +5256,7 @@ function buildOpenApiSpec(routes, opts = {}) {
4561
5256
  const paths = {};
4562
5257
  for (const route of routes) {
4563
5258
  if (!route.contract) continue;
5259
+ if (route.method.toUpperCase() === "ALL") continue;
4564
5260
  const oaPath = toOpenApiPath(route.path);
4565
5261
  const method = route.method.toLowerCase();
4566
5262
  let pathItem = paths[oaPath];
@@ -4604,10 +5300,11 @@ async function emitPages(pages, outDir, _options = {}) {
4604
5300
  }).join("\n");
4605
5301
  const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
4606
5302
  const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
5303
+ const scopesBlock = buildScopesBlock(pages);
4607
5304
  const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
4608
5305
 
4609
5306
  export type InertiaPageName = ${pageNameUnion};
4610
- ${propsHelper}
5307
+ ${propsHelper}${scopesBlock}
4611
5308
  declare module '@dudousxd/nestjs-inertia' {
4612
5309
  interface InertiaPages {
4613
5310
  ${augBody}
@@ -4616,6 +5313,31 @@ ${sharedPropsBlock}}
4616
5313
  `;
4617
5314
  await writeFile7(join11(outDir, "pages.d.ts"), content, "utf8");
4618
5315
  }
5316
+ function buildScopesBlock(pages) {
5317
+ const byScope = /* @__PURE__ */ new Map();
5318
+ for (const page of pages) {
5319
+ const scope = page.scope ?? "default";
5320
+ const names = byScope.get(scope) ?? [];
5321
+ names.push(JSON.stringify(page.name));
5322
+ byScope.set(scope, names);
5323
+ }
5324
+ if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
5325
+ if (!byScope.has("default")) byScope.set("default", []);
5326
+ const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
5327
+ const body = scopes.map((scope) => {
5328
+ const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
5329
+ const names = byScope.get(scope) ?? [];
5330
+ return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
5331
+ }).join("\n");
5332
+ return `
5333
+ /** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
5334
+ export interface InertiaScopePages {
5335
+ ${body}
5336
+ }
5337
+
5338
+ export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
5339
+ `;
5340
+ }
4619
5341
  function buildSharedPropsBlock(sharedProps) {
4620
5342
  if (!sharedProps) return "";
4621
5343
  if (sharedProps.isImportRef) {
@@ -4797,12 +5519,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
4797
5519
  }
4798
5520
  if (config.pages) {
4799
5521
  const pagesConfig = config.pages;
4800
- const pages = await discoverPages({
4801
- glob: pagesConfig.glob,
4802
- cwd: config.codegen.cwd,
4803
- propsExport: pagesConfig.propsExport,
4804
- componentNameStrategy: pagesConfig.componentNameStrategy
4805
- });
5522
+ const pages = await discoverPageScopes(config);
4806
5523
  const sharedProps = discoverSharedPropsFromConfig(config);
4807
5524
  await emitPages(pages, config.codegen.outDir, {
4808
5525
  propsExport: pagesConfig.propsExport,
@@ -4977,12 +5694,17 @@ async function watch(config, onChange, options = {}) {
4977
5694
  await runInitialPass();
4978
5695
  }
4979
5696
  let pagesDebounceTimer;
4980
- const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
4981
- const pagesWatcher = chokidar.watch(join15(config.codegen.cwd, pagesGlob), {
4982
- ignoreInitial: true,
4983
- persistent: true,
4984
- awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
4985
- });
5697
+ const pageGlobs = allPageGlobs(config);
5698
+ const pagesWatcher = chokidar.watch(
5699
+ (pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
5700
+ (glob) => join15(config.codegen.cwd, glob)
5701
+ ),
5702
+ {
5703
+ ignoreInitial: true,
5704
+ persistent: true,
5705
+ awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
5706
+ }
5707
+ );
4986
5708
  function schedulePagesRegenerate() {
4987
5709
  if (pagesDebounceTimer !== void 0) {
4988
5710
  clearTimeout(pagesDebounceTimer);
@@ -5062,7 +5784,7 @@ async function watch(config, onChange, options = {}) {
5062
5784
  }
5063
5785
 
5064
5786
  // src/index.ts
5065
- var VERSION = "0.25.2";
5787
+ var VERSION = "0.27.0";
5066
5788
 
5067
5789
  // src/generate-manifest.ts
5068
5790
  var MANIFEST_FILE = ".codegen-manifest.json";
@@ -5122,8 +5844,7 @@ function diffConfigKeyHashes(previous, current) {
5122
5844
  return [...keys].filter((key) => previous[key] !== current[key]).sort();
5123
5845
  }
5124
5846
  async function discoverInputFiles(config) {
5125
- const globs = [config.contracts.glob, config.forms.watch];
5126
- if (config.pages) globs.push(config.pages.glob);
5847
+ const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
5127
5848
  const cwd = config.codegen.cwd;
5128
5849
  const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
5129
5850
  return [...new Set(matched)].sort();