@dudousxd/nestjs-codegen 0.2.1 → 0.3.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.
@@ -32,112 +32,9 @@ var CodegenError = class extends Error {
32
32
  }
33
33
  };
34
34
 
35
- // src/adapters/zod.ts
36
- function toObjectKey(name) {
37
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
38
- }
39
- function messageSuffix(messageRaw2) {
40
- return messageRaw2 ? `{ message: ${messageRaw2} }` : "";
41
- }
42
- function renderStringChecks(checks) {
43
- let out = "";
44
- for (const c of checks) {
45
- switch (c.check) {
46
- case "email":
47
- out += `.email(${messageSuffix(c.messageRaw)})`;
48
- break;
49
- case "url":
50
- out += `.url(${messageSuffix(c.messageRaw)})`;
51
- break;
52
- case "uuid":
53
- out += `.uuid(${messageSuffix(c.messageRaw)})`;
54
- break;
55
- case "regex":
56
- out += `.regex(${c.pattern})`;
57
- break;
58
- case "min":
59
- out += `.min(${c.value})`;
60
- break;
61
- case "max":
62
- out += `.max(${c.value})`;
63
- break;
64
- }
65
- }
66
- return out;
67
- }
68
- function render(node, ctx) {
69
- switch (node.kind) {
70
- case "string":
71
- return `z.string()${renderStringChecks(node.checks)}`;
72
- case "number": {
73
- let out = "z.number()";
74
- for (const c of node.checks) {
75
- if (c.check === "int") out += ".int()";
76
- else if (c.check === "positive") out += ".positive()";
77
- else if (c.check === "negative") out += ".negative()";
78
- else if (c.check === "min") out += `.min(${c.value})`;
79
- else if (c.check === "max") out += `.max(${c.value})`;
80
- }
81
- return out;
82
- }
83
- case "boolean":
84
- return "z.boolean()";
85
- case "date":
86
- return "z.coerce.date()";
87
- case "unknown":
88
- return node.note ? `z.unknown() /* ${node.note} */` : "z.unknown()";
89
- case "instanceof":
90
- return `z.instanceof(${node.ctor})`;
91
- case "enum":
92
- return `z.enum([${node.literals.join(", ")}])`;
93
- case "literal":
94
- return `z.literal(${node.raw})`;
95
- case "union":
96
- return `z.union([${node.options.map((o) => render(o, ctx)).join(", ")}])`;
97
- case "object": {
98
- if (node.fields.length === 0) {
99
- return node.passthrough ? "z.object({}).passthrough()" : "z.object({})";
100
- }
101
- const inner = node.fields.map((f) => `${toObjectKey(f.key)}: ${render(f.value, ctx)}`).join(", ");
102
- return `z.object({ ${inner} })${node.passthrough ? ".passthrough()" : ""}`;
103
- }
104
- case "array":
105
- return `z.array(${render(node.element, ctx)})`;
106
- case "optional":
107
- return `${render(node.inner, ctx)}.optional()`;
108
- case "ref":
109
- return node.name;
110
- case "lazyRef":
111
- return `z.lazy(() => ${node.name})`;
112
- case "annotated": {
113
- const comments = node.unmappable.map((n) => `/* @${n}: not translatable to zod (server-only) */`).join(" ");
114
- return `${render(node.inner, ctx)} ${comments}`;
115
- }
116
- }
117
- }
118
- var zodAdapter = {
119
- name: "zod",
120
- importStatements(usage) {
121
- return usage.used ? ["import { z } from 'zod';"] : [];
122
- },
123
- render,
124
- inferType(schemaConst) {
125
- return `z.infer<typeof ${schemaConst}>`;
126
- },
127
- renderModule(mod) {
128
- const ctx = { named: mod.named };
129
- const namedNestedSchemas = /* @__PURE__ */ new Map();
130
- for (const [name, node] of mod.named) {
131
- namedNestedSchemas.set(name, render(node, ctx));
132
- }
133
- return { schemaText: render(mod.root, ctx), namedNestedSchemas, warnings: mod.warnings };
134
- }
135
- };
136
-
137
35
  // src/adapters/registry.ts
138
36
  function resolveAdapter(option) {
139
37
  if (typeof option !== "string") return option;
140
- if (option === "zod") return zodAdapter;
141
38
  const pkg = `@dudousxd/nestjs-codegen-${option}`;
142
39
  const named = `${option}Adapter`;
143
40
  throw new ConfigError(
@@ -167,8 +64,21 @@ If this is intentional, move the file inside your project directory.`
167
64
  function resolveConfig(userConfig, cwd) {
168
65
  return applyDefaults(userConfig, cwd ?? process.cwd());
169
66
  }
67
+ function validateUserConfig(userConfig) {
68
+ if (userConfig.validation == null) {
69
+ throw new ConfigError(
70
+ "validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
71
+ );
72
+ }
73
+ if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
74
+ throw new ConfigError(
75
+ "Config validation failed: `pages.glob` must be a string when `pages` is set"
76
+ );
77
+ }
78
+ }
170
79
  function applyDefaults(userConfig, cwd) {
171
- const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : join(cwd, ".nestjs-inertia");
80
+ validateUserConfig(userConfig);
81
+ const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : join(cwd, ".nestjs-codegen");
172
82
  const resolvedCwd = userConfig.codegen?.cwd ? resolveAbsolute(cwd, userConfig.codegen.cwd) : cwd;
173
83
  let app = null;
174
84
  if (userConfig.app) {
@@ -186,7 +96,8 @@ function applyDefaults(userConfig, cwd) {
186
96
  }
187
97
  return {
188
98
  extensions: userConfig.extensions ?? [],
189
- validation: resolveAdapter(userConfig.validation ?? "zod"),
99
+ // Non-null: validateUserConfig() above throws when `validation` is absent.
100
+ validation: resolveAdapter(userConfig.validation),
190
101
  pages: userConfig.pages ? {
191
102
  glob: userConfig.pages.glob,
192
103
  propsExport: userConfig.pages.propsExport ?? "ComponentProps",
@@ -213,15 +124,20 @@ function applyDefaults(userConfig, cwd) {
213
124
 
214
125
  // src/watch/watcher.ts
215
126
  import { readFile as readFile3 } from "fs/promises";
216
- import { join as join12 } from "path";
127
+ import { join as join13 } from "path";
217
128
  import chokidar from "chokidar";
218
129
 
219
130
  // src/discovery/contracts-fast.ts
220
131
  import { join as join2, resolve as resolve3 } from "path";
221
132
  import fg from "fast-glob";
222
133
  import {
223
- Node as Node6,
224
- Project,
134
+ Node as Node7,
135
+ Project
136
+ } from "ts-morph";
137
+
138
+ // src/discovery/dto-type-resolver.ts
139
+ import {
140
+ Node as Node5,
225
141
  SyntaxKind as SyntaxKind2
226
142
  } from "ts-morph";
227
143
 
@@ -236,20 +152,13 @@ import { dirname, resolve as resolve2 } from "path";
236
152
  import {
237
153
  Node
238
154
  } from "ts-morph";
239
- var _ctx = { projectRoot: "", tsconfigPaths: null };
240
- function setDiscoveryContext(ctx) {
241
- const prev = _ctx;
242
- _ctx = ctx;
243
- return prev;
244
- }
245
- function restoreDiscoveryContext(ctx) {
246
- _ctx = ctx;
155
+ var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
156
+ var _ctxByProject = /* @__PURE__ */ new WeakMap();
157
+ function setDiscoveryContext(project, ctx) {
158
+ _ctxByProject.set(project, ctx);
247
159
  }
248
- function _projectRoot() {
249
- return _ctx.projectRoot;
250
- }
251
- function _tsconfigPaths() {
252
- return _ctx.tsconfigPaths;
160
+ function _ctxFor(project) {
161
+ return _ctxByProject.get(project) ?? _EMPTY_CTX;
253
162
  }
254
163
  var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
255
164
  function dbg(...args) {
@@ -291,7 +200,7 @@ function findTypeInFile(name, file) {
291
200
  }
292
201
  return null;
293
202
  }
294
- function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
203
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
295
204
  if (moduleSpecifier.startsWith(".")) {
296
205
  const dir = dirname(sourceFile.getFilePath());
297
206
  const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
@@ -301,8 +210,9 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
301
210
  resolve2(dir, moduleSpecifier, "index.ts")
302
211
  ];
303
212
  }
304
- const baseUrl = _projectRoot();
305
- const tsconfigPaths = _tsconfigPaths();
213
+ const ctx = _ctxFor(project);
214
+ const baseUrl = ctx.projectRoot;
215
+ const tsconfigPaths = ctx.tsconfigPaths;
306
216
  dbg(
307
217
  "resolveModuleSpecifier",
308
218
  moduleSpecifier,
@@ -448,7 +358,7 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
448
358
  if (!namedImport) continue;
449
359
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
450
360
  if (opts.allowBareSpecifier && !moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
451
- const tsconfigPaths = _tsconfigPaths();
361
+ const tsconfigPaths = _ctxFor(project).tsconfigPaths;
452
362
  const isAlias = tsconfigPaths != null && Object.keys(tsconfigPaths).some((p) => {
453
363
  const prefix = p.replace("*", "");
454
364
  return moduleSpecifier.startsWith(prefix);
@@ -801,344 +711,14 @@ function inSchemaFromDecorator(decorator) {
801
711
  return null;
802
712
  }
803
713
 
804
- // src/discovery/dto-to-zod.ts
805
- import {
806
- Node as Node3
807
- } from "ts-morph";
808
- var KNOWN_DECORATORS2 = /* @__PURE__ */ new Set([
809
- "IsString",
810
- "IsNumber",
811
- "IsInt",
812
- "IsBoolean",
813
- "IsDate",
814
- "IsEmail",
815
- "IsUrl",
816
- "IsUUID",
817
- "MinLength",
818
- "MaxLength",
819
- "Length",
820
- "Min",
821
- "Max",
822
- "IsPositive",
823
- "IsNegative",
824
- "Matches",
825
- "IsEnum",
826
- "IsIn",
827
- "IsOptional",
828
- "IsNotEmpty",
829
- "IsArray",
830
- "ValidateNested",
831
- "Type",
832
- "IsObject",
833
- "Allow",
834
- "IsDefined"
835
- ]);
836
- function extractZodFromDto(classDecl, sourceFile, project) {
837
- const ctx = {
838
- sourceFile,
839
- project,
840
- namedNestedSchemas: /* @__PURE__ */ new Map(),
841
- warnings: [],
842
- warnedDecorators: /* @__PURE__ */ new Set(),
843
- emittedClasses: /* @__PURE__ */ new Map(),
844
- visiting: /* @__PURE__ */ new Set(),
845
- recursiveSchemas: /* @__PURE__ */ new Set(),
846
- depth: 0
847
- };
848
- const schemaText = buildObjectSchema(classDecl, sourceFile, ctx);
849
- for (const schemaName of ctx.recursiveSchemas) {
850
- ctx.namedNestedSchemas.set(schemaName, "z.unknown() /* recursive type \u2014 not expanded */");
851
- }
852
- return {
853
- schemaText,
854
- namedNestedSchemas: ctx.namedNestedSchemas,
855
- warnings: ctx.warnings
856
- };
857
- }
858
- function buildObjectSchema(classDecl, classFile, ctx) {
859
- const props = classDecl.getProperties();
860
- if (props.length === 0) {
861
- return "z.object({}).passthrough()";
862
- }
863
- const fields = [];
864
- for (const prop of props) {
865
- const name = prop.getName();
866
- const expr = buildPropertySchema(prop, classFile, ctx);
867
- fields.push(`${toObjectKey2(name)}: ${expr}`);
868
- }
869
- return `z.object({ ${fields.join(", ")} })`;
870
- }
871
- function toObjectKey2(name) {
872
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
873
- }
874
- function buildPropertySchema(prop, classFile, ctx) {
875
- const decorators = /* @__PURE__ */ new Map();
876
- for (const d of prop.getDecorators()) decorators.set(d.getName(), d);
877
- const has = (n) => decorators.has(n);
878
- const dec = (n) => decorators.get(n);
879
- const typeNode = prop.getTypeNode();
880
- const typeText = typeNode?.getText() ?? "unknown";
881
- const isArrayType = !!typeNode && typeNode.getText().endsWith("[]");
882
- const comments = [];
883
- const typeRefName = resolveTypeFactoryName2(dec("Type"));
884
- if (has("ValidateNested") || typeRefName) {
885
- const childName = typeRefName ?? singularClassName2(typeText);
886
- if (childName) {
887
- const childExpr = buildNestedReference2(childName, classFile, ctx);
888
- const wrapArray = has("IsArray") || isArrayType;
889
- let expr2 = wrapArray ? `z.array(${childExpr})` : childExpr;
890
- expr2 = applyPresence2(expr2, decorators);
891
- return expr2;
892
- }
893
- }
894
- let base = baseFromType2(typeText, isArrayType, ctx, classFile);
895
- const refinements = [];
896
- if (has("IsString")) base = "z.string()";
897
- if (has("IsBoolean")) base = "z.boolean()";
898
- if (has("IsDate")) base = "z.coerce.date()";
899
- if (has("IsNumber")) base = "z.number()";
900
- if (has("IsInt")) base = "z.number().int()";
901
- if (has("IsObject") && !has("ValidateNested")) base = "z.object({}).passthrough()";
902
- if (has("Allow")) base = "z.unknown()";
903
- if (has("IsEmail")) {
904
- base = ensureStringBase(base);
905
- refinements.push(`.email(${messageArg(dec("IsEmail"))})`);
906
- }
907
- if (has("IsUrl")) {
908
- base = ensureStringBase(base);
909
- refinements.push(`.url(${messageArg(dec("IsUrl"))})`);
910
- }
911
- if (has("IsUUID")) {
912
- base = ensureStringBase(base);
913
- refinements.push(`.uuid(${messageArg(dec("IsUUID"))})`);
914
- }
915
- if (has("Matches")) {
916
- const re = firstArgText2(dec("Matches"));
917
- if (re) {
918
- base = ensureStringBase(base);
919
- refinements.push(`.regex(${re})`);
920
- }
921
- }
922
- if (has("MinLength")) {
923
- const n = numericArg2(dec("MinLength"));
924
- if (n !== null) refinements.push(`.min(${n})`);
925
- }
926
- if (has("MaxLength")) {
927
- const n = numericArg2(dec("MaxLength"));
928
- if (n !== null) refinements.push(`.max(${n})`);
929
- }
930
- if (has("Length")) {
931
- const [min, max] = numericArgs2(dec("Length"));
932
- if (min !== null) refinements.push(`.min(${min})`);
933
- if (max !== null) refinements.push(`.max(${max})`);
934
- }
935
- if (has("Min")) {
936
- const n = numericArg2(dec("Min"));
937
- if (n !== null) refinements.push(`.min(${n})`);
938
- }
939
- if (has("Max")) {
940
- const n = numericArg2(dec("Max"));
941
- if (n !== null) refinements.push(`.max(${n})`);
942
- }
943
- if (has("IsPositive")) refinements.push(".positive()");
944
- if (has("IsNegative")) refinements.push(".negative()");
945
- if (has("IsNotEmpty") && isStringBase(base)) refinements.push(".min(1)");
946
- if (has("IsEnum")) {
947
- const enumExpr = enumSchemaFromDecorator2(dec("IsEnum"), classFile, ctx);
948
- if (enumExpr) base = enumExpr;
949
- }
950
- if (has("IsIn")) {
951
- const inExpr = inSchemaFromDecorator2(dec("IsIn"));
952
- if (inExpr) base = inExpr;
953
- }
954
- for (const name of decorators.keys()) {
955
- if (!KNOWN_DECORATORS2.has(name)) {
956
- comments.push(`/* @${name}: not translatable to zod (server-only) */`);
957
- if (!ctx.warnedDecorators.has(name)) {
958
- ctx.warnedDecorators.add(name);
959
- const msg = `@${name} is not translatable to zod and was skipped (server-only validation).`;
960
- ctx.warnings.push(msg);
961
- console.warn(`[nestjs-codegen/forms] ${msg}`);
962
- }
963
- }
964
- }
965
- let expr = base + refinements.join("");
966
- if (isArrayType && !expr.startsWith("z.array(")) {
967
- expr = `z.array(${expr})`;
968
- }
969
- expr = applyPresence2(expr, decorators);
970
- if (comments.length > 0) {
971
- expr = `${expr} ${comments.join(" ")}`;
972
- }
973
- return expr;
974
- }
975
- function applyPresence2(expr, decorators) {
976
- if (decorators.has("IsDefined")) return expr;
977
- if (decorators.has("IsOptional")) return `${expr}.optional()`;
978
- return expr;
979
- }
980
- function baseFromType2(typeText, isArrayType, ctx, classFile) {
981
- const inner = isArrayType ? typeText.slice(0, -2).trim() : typeText;
982
- switch (inner) {
983
- case "string":
984
- return "z.string()";
985
- case "number":
986
- return "z.number()";
987
- case "boolean":
988
- return "z.boolean()";
989
- case "Date":
990
- return "z.coerce.date()";
991
- case "File":
992
- case "Express.Multer.File":
993
- return "z.instanceof(File)";
994
- default:
995
- return "z.unknown()";
996
- }
997
- }
998
- function ensureStringBase(base) {
999
- return isStringBase(base) ? base : "z.string()";
1000
- }
1001
- function isStringBase(base) {
1002
- return base.startsWith("z.string(");
1003
- }
1004
- function buildNestedReference2(className, fromFile, ctx) {
1005
- if (ctx.visiting.has(className) || ctx.depth >= 8) {
1006
- const reserved = ctx.emittedClasses.get(className) ?? aliasFor2(className, ctx);
1007
- ctx.emittedClasses.set(className, reserved);
1008
- ctx.recursiveSchemas.add(reserved);
1009
- if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
1010
- ctx.warnedDecorators.add(`recursive:${reserved}`);
1011
- const msg = `${className} is a recursive type and was not expanded; the generated form schema uses z.unknown() for it.`;
1012
- ctx.warnings.push(msg);
1013
- console.warn(`[nestjs-codegen/forms] ${msg}`);
1014
- }
1015
- return `z.lazy(() => ${reserved})`;
1016
- }
1017
- const existing = ctx.emittedClasses.get(className);
1018
- if (existing) return existing;
1019
- const schemaName = aliasFor2(className, ctx);
1020
- const resolved = findType(className, fromFile, ctx.project);
1021
- if (!resolved || resolved.kind !== "class") {
1022
- return "z.object({}).passthrough()";
1023
- }
1024
- ctx.emittedClasses.set(className, schemaName);
1025
- ctx.visiting.add(className);
1026
- ctx.depth += 1;
1027
- const childText = buildObjectSchema(resolved.decl, resolved.file, ctx);
1028
- ctx.depth -= 1;
1029
- ctx.visiting.delete(className);
1030
- ctx.namedNestedSchemas.set(schemaName, childText);
1031
- return schemaName;
1032
- }
1033
- function aliasFor2(className, ctx) {
1034
- const baseName = `${className}Schema`;
1035
- let candidate = baseName;
1036
- let i = 1;
1037
- const used = new Set(ctx.namedNestedSchemas.keys());
1038
- for (const v of ctx.emittedClasses.values()) used.add(v);
1039
- while (used.has(candidate)) {
1040
- candidate = `${baseName}_${i}`;
1041
- i += 1;
1042
- }
1043
- return candidate;
1044
- }
1045
- function firstArg2(decorator) {
1046
- return decorator?.getArguments()[0];
1047
- }
1048
- function firstArgText2(decorator) {
1049
- const arg = firstArg2(decorator);
1050
- return arg ? arg.getText() : null;
1051
- }
1052
- function numericArg2(decorator) {
1053
- const arg = firstArg2(decorator);
1054
- if (arg && Node3.isNumericLiteral(arg)) return arg.getText();
1055
- return null;
1056
- }
1057
- function numericArgs2(decorator) {
1058
- const args = decorator?.getArguments() ?? [];
1059
- const num = (n) => n && Node3.isNumericLiteral(n) ? n.getText() : null;
1060
- return [num(args[0]), num(args[1])];
1061
- }
1062
- function messageArg(decorator) {
1063
- const args = decorator?.getArguments() ?? [];
1064
- for (const arg of args) {
1065
- if (Node3.isObjectLiteralExpression(arg)) {
1066
- for (const prop of arg.getProperties()) {
1067
- if (Node3.isPropertyAssignment(prop) && prop.getName() === "message") {
1068
- const init = prop.getInitializer();
1069
- if (init && Node3.isStringLiteral(init)) {
1070
- return `{ message: ${init.getText()} }`;
1071
- }
1072
- }
1073
- }
1074
- }
1075
- }
1076
- return "";
1077
- }
1078
- function resolveTypeFactoryName2(decorator) {
1079
- const arg = firstArg2(decorator);
1080
- if (!arg) return null;
1081
- if (Node3.isArrowFunction(arg)) {
1082
- const body = arg.getBody();
1083
- if (Node3.isIdentifier(body)) return body.getText();
1084
- }
1085
- return null;
1086
- }
1087
- function singularClassName2(typeText) {
1088
- const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
1089
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
1090
- }
1091
- function enumSchemaFromDecorator2(decorator, classFile, ctx) {
1092
- const arg = firstArg2(decorator);
1093
- if (!arg) return null;
1094
- if (Node3.isIdentifier(arg)) {
1095
- const name = arg.getText();
1096
- const resolved = findType(name, classFile, ctx.project);
1097
- if (resolved && resolved.kind === "enum") {
1098
- return `z.enum([${resolved.members.join(", ")}])`;
1099
- }
1100
- const msg = `@IsEnum(${name}): enum could not be resolved to literal members and is not importable into the generated form schema; falling back to z.unknown().`;
1101
- if (!ctx.warnedDecorators.has(`IsEnum:${name}`)) {
1102
- ctx.warnedDecorators.add(`IsEnum:${name}`);
1103
- ctx.warnings.push(msg);
1104
- console.warn(`[nestjs-codegen/forms] ${msg}`);
1105
- }
1106
- return `z.unknown() /* @IsEnum(${name}): enum not resolvable to literals */`;
1107
- }
1108
- if (Node3.isObjectLiteralExpression(arg)) {
1109
- const values = [];
1110
- for (const p of arg.getProperties()) {
1111
- if (!Node3.isPropertyAssignment(p)) continue;
1112
- const init = p.getInitializer();
1113
- if (init && Node3.isStringLiteral(init)) values.push(init.getText());
1114
- }
1115
- if (values.length > 0) return `z.enum([${values.join(", ")}])`;
1116
- }
1117
- return null;
1118
- }
1119
- function inSchemaFromDecorator2(decorator) {
1120
- const arg = firstArg2(decorator);
1121
- if (arg && Node3.isArrayLiteralExpression(arg)) {
1122
- const elements = arg.getElements();
1123
- const allStrings = elements.every((e) => Node3.isStringLiteral(e));
1124
- if (allStrings && elements.length > 0) {
1125
- return `z.enum([${elements.map((e) => e.getText()).join(", ")}])`;
1126
- }
1127
- if (elements.length > 0) {
1128
- return `z.union([${elements.map((e) => `z.literal(${e.getText()})`).join(", ")}])`;
1129
- }
1130
- }
1131
- return null;
1132
- }
1133
-
1134
714
  // src/discovery/filter-for.ts
1135
715
  import {
1136
- Node as Node5
716
+ Node as Node4
1137
717
  } from "ts-morph";
1138
718
 
1139
719
  // src/discovery/filter-field-types.ts
1140
720
  import {
1141
- Node as Node4,
721
+ Node as Node3,
1142
722
  SyntaxKind
1143
723
  } from "ts-morph";
1144
724
 
@@ -1175,7 +755,7 @@ function markNullable(r, nullable) {
1175
755
  return nullable ? { ...r, nullable: true } : r;
1176
756
  }
1177
757
  function classifyTypeNode(typeNode, sourceFile, project, opts) {
1178
- if (Node4.isUnionTypeNode(typeNode)) {
758
+ if (Node3.isUnionTypeNode(typeNode)) {
1179
759
  let nullable = false;
1180
760
  const stringLits = [];
1181
761
  const numberLits = [];
@@ -1186,13 +766,13 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1186
766
  nullable = true;
1187
767
  continue;
1188
768
  }
1189
- if (Node4.isLiteralTypeNode(member)) {
769
+ if (Node3.isLiteralTypeNode(member)) {
1190
770
  const lit = member.getLiteral();
1191
- if (Node4.isStringLiteral(lit)) {
771
+ if (Node3.isStringLiteral(lit)) {
1192
772
  stringLits.push(lit.getLiteralValue());
1193
773
  continue;
1194
774
  }
1195
- if (Node4.isNumericLiteral(lit)) {
775
+ if (Node3.isNumericLiteral(lit)) {
1196
776
  numberLits.push(lit.getText());
1197
777
  continue;
1198
778
  }
@@ -1228,7 +808,7 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1228
808
  default:
1229
809
  break;
1230
810
  }
1231
- if (Node4.isTypeReference(typeNode)) {
811
+ if (Node3.isTypeReference(typeNode)) {
1232
812
  const refName = typeNode.getTypeName().getText();
1233
813
  if (refName === "Date") return { kind: "date" };
1234
814
  if (refName === "Record" || refName === "Object") return { kind: "json" };
@@ -1241,25 +821,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
1241
821
  if (typeRef) return { kind: "unknown", typeRef };
1242
822
  return { kind: "unknown" };
1243
823
  }
1244
- if (Node4.isTypeLiteral(typeNode)) return { kind: "json" };
824
+ if (Node3.isTypeLiteral(typeNode)) return { kind: "json" };
1245
825
  return { kind: "unknown" };
1246
826
  }
1247
827
  function enumFromDecoratorArgs(args, sourceFile, project) {
1248
828
  for (const arg of args) {
1249
- if (Node4.isArrowFunction(arg)) {
829
+ if (Node3.isArrowFunction(arg)) {
1250
830
  const body = arg.getBody();
1251
- if (Node4.isIdentifier(body)) {
831
+ if (Node3.isIdentifier(body)) {
1252
832
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1253
833
  if (en) return en;
1254
834
  }
1255
835
  }
1256
- if (Node4.isObjectLiteralExpression(arg)) {
836
+ if (Node3.isObjectLiteralExpression(arg)) {
1257
837
  const itemsProp = arg.getProperty("items");
1258
- if (itemsProp && Node4.isPropertyAssignment(itemsProp)) {
838
+ if (itemsProp && Node3.isPropertyAssignment(itemsProp)) {
1259
839
  const init = itemsProp.getInitializer();
1260
- if (init && Node4.isArrowFunction(init)) {
840
+ if (init && Node3.isArrowFunction(init)) {
1261
841
  const body = init.getBody();
1262
- if (Node4.isIdentifier(body)) {
842
+ if (Node3.isIdentifier(body)) {
1263
843
  const en = resolveEnumValues(body.getText(), sourceFile, project);
1264
844
  if (en) return en;
1265
845
  }
@@ -1282,7 +862,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1282
862
  return { kind: "string" };
1283
863
  }
1284
864
  for (const arg of args) {
1285
- if (Node4.isStringLiteral(arg)) {
865
+ if (Node3.isStringLiteral(arg)) {
1286
866
  const raw = arg.getLiteralValue();
1287
867
  const kind = classifyTypeKeyword(raw);
1288
868
  if (kind) {
@@ -1290,11 +870,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1290
870
  return { kind };
1291
871
  }
1292
872
  }
1293
- if (Node4.isObjectLiteralExpression(arg)) {
873
+ if (Node3.isObjectLiteralExpression(arg)) {
1294
874
  const enumProp = arg.getProperty("enum");
1295
- if (enumProp && Node4.isPropertyAssignment(enumProp)) {
875
+ if (enumProp && Node3.isPropertyAssignment(enumProp)) {
1296
876
  const init = enumProp.getInitializer();
1297
- if (init && Node4.isIdentifier(init)) {
877
+ if (init && Node3.isIdentifier(init)) {
1298
878
  const en = resolveEnumValues(init.getText(), sourceFile, project);
1299
879
  if (en) {
1300
880
  return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
@@ -1303,9 +883,9 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
1303
883
  }
1304
884
  }
1305
885
  const typeProp = arg.getProperty("type");
1306
- if (typeProp && Node4.isPropertyAssignment(typeProp)) {
886
+ if (typeProp && Node3.isPropertyAssignment(typeProp)) {
1307
887
  const init = typeProp.getInitializer();
1308
- if (init && Node4.isStringLiteral(init)) {
888
+ if (init && Node3.isStringLiteral(init)) {
1309
889
  const kind = classifyTypeKeyword(init.getLiteralValue());
1310
890
  if (kind) return { kind };
1311
891
  }
@@ -1341,7 +921,7 @@ function toFilterFieldType(name, r) {
1341
921
 
1342
922
  // src/discovery/filter-for.ts
1343
923
  function classifyFilterForHint(typeInit) {
1344
- if (Node5.isStringLiteral(typeInit)) {
924
+ if (Node4.isStringLiteral(typeInit)) {
1345
925
  switch (typeInit.getLiteralValue()) {
1346
926
  case "string":
1347
927
  return { kind: "string" };
@@ -1355,10 +935,10 @@ function classifyFilterForHint(typeInit) {
1355
935
  return null;
1356
936
  }
1357
937
  }
1358
- if (Node5.isArrayLiteralExpression(typeInit)) {
938
+ if (Node4.isArrayLiteralExpression(typeInit)) {
1359
939
  const values = [];
1360
940
  for (const el of typeInit.getElements()) {
1361
- if (!Node5.isStringLiteral(el)) return null;
941
+ if (!Node4.isStringLiteral(el)) return null;
1362
942
  values.push(el.getLiteralValue());
1363
943
  }
1364
944
  if (values.length === 0) return null;
@@ -1393,11 +973,11 @@ function extractFilterForHints(classDecl, project) {
1393
973
  if (!filterForDec) continue;
1394
974
  const args = filterForDec.getArguments();
1395
975
  const keyArg = args[0];
1396
- const inputKey = keyArg && Node5.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
976
+ const inputKey = keyArg && Node4.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
1397
977
  const optsArg = args[1];
1398
- if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
978
+ if (optsArg && Node4.isObjectLiteralExpression(optsArg)) {
1399
979
  const typeProp = optsArg.getProperty("type");
1400
- if (typeProp && Node5.isPropertyAssignment(typeProp)) {
980
+ if (typeProp && Node4.isPropertyAssignment(typeProp)) {
1401
981
  const typeInit = typeProp.getInitializer();
1402
982
  if (typeInit) {
1403
983
  const classified = classifyFilterForHint(typeInit);
@@ -1420,14 +1000,14 @@ function extractApplyFilterInfo(method, sourceFile, project) {
1420
1000
  const args = filterDecorator.getArguments();
1421
1001
  if (args.length === 0) continue;
1422
1002
  const filterClassArg = args[0];
1423
- if (!filterClassArg || !Node5.isIdentifier(filterClassArg)) continue;
1003
+ if (!filterClassArg || !Node4.isIdentifier(filterClassArg)) continue;
1424
1004
  let source = "query";
1425
1005
  const optionsArg = args[1];
1426
- if (optionsArg && Node5.isObjectLiteralExpression(optionsArg)) {
1006
+ if (optionsArg && Node4.isObjectLiteralExpression(optionsArg)) {
1427
1007
  const sourceProp = optionsArg.getProperty("source");
1428
- if (sourceProp && Node5.isPropertyAssignment(sourceProp)) {
1008
+ if (sourceProp && Node4.isPropertyAssignment(sourceProp)) {
1429
1009
  const init = sourceProp.getInitializer();
1430
- if (init && Node5.isStringLiteral(init) && init.getLiteralValue() === "body") {
1010
+ if (init && Node4.isStringLiteral(init) && init.getLiteralValue() === "body") {
1431
1011
  source = "body";
1432
1012
  }
1433
1013
  }
@@ -1490,22 +1070,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
1490
1070
  const args = dec.getArguments();
1491
1071
  if (args.length === 0) continue;
1492
1072
  const arg = args[0];
1493
- if (Node5.isObjectLiteralExpression(arg)) {
1073
+ if (Node4.isObjectLiteralExpression(arg)) {
1494
1074
  const entityProp = arg.getProperty("entity");
1495
- if (entityProp && Node5.isPropertyAssignment(entityProp)) {
1075
+ if (entityProp && Node4.isPropertyAssignment(entityProp)) {
1496
1076
  const init = entityProp.getInitializer();
1497
- if (init && Node5.isArrowFunction(init)) {
1077
+ if (init && Node4.isArrowFunction(init)) {
1498
1078
  const body = init.getBody();
1499
- if (Node5.isIdentifier(body)) {
1079
+ if (Node4.isIdentifier(body)) {
1500
1080
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
1501
1081
  if (resolved?.kind === "class") return resolved.decl;
1502
1082
  }
1503
1083
  }
1504
1084
  }
1505
1085
  }
1506
- if (Node5.isArrowFunction(arg)) {
1086
+ if (Node4.isArrowFunction(arg)) {
1507
1087
  const body = arg.getBody();
1508
- if (Node5.isIdentifier(body)) {
1088
+ if (Node4.isIdentifier(body)) {
1509
1089
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
1510
1090
  if (resolved?.kind === "class") return resolved.decl;
1511
1091
  }
@@ -1529,11 +1109,11 @@ function extractFilterableEntityFields(filterClass, project) {
1529
1109
  const args = filterableDecorator.getArguments();
1530
1110
  if (args.length === 0) return [];
1531
1111
  const optionsArg = args[0];
1532
- if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
1112
+ if (!Node4.isObjectLiteralExpression(optionsArg)) return [];
1533
1113
  const entityProp = optionsArg.getProperty("entity");
1534
- if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return [];
1114
+ if (!entityProp || !Node4.isPropertyAssignment(entityProp)) return [];
1535
1115
  const entityInit = entityProp.getInitializer();
1536
- if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
1116
+ if (!entityInit || !Node4.isIdentifier(entityInit)) return [];
1537
1117
  const entityName = entityInit.getText();
1538
1118
  const filterSourceFile = filterClass.getSourceFile();
1539
1119
  const resolvedEntity = findType(entityName, filterSourceFile, project);
@@ -1549,17 +1129,17 @@ function extractFilterableEntityFields(filterClass, project) {
1549
1129
  const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
1550
1130
  if (relationsDecorator) {
1551
1131
  const relArgs = relationsDecorator.getArguments();
1552
- if (relArgs.length > 0 && Node5.isObjectLiteralExpression(relArgs[0])) {
1132
+ if (relArgs.length > 0 && Node4.isObjectLiteralExpression(relArgs[0])) {
1553
1133
  for (const relProp of relArgs[0].getProperties()) {
1554
- if (!Node5.isPropertyAssignment(relProp)) continue;
1134
+ if (!Node4.isPropertyAssignment(relProp)) continue;
1555
1135
  const relInit = relProp.getInitializer();
1556
- if (!relInit || !Node5.isObjectLiteralExpression(relInit)) continue;
1136
+ if (!relInit || !Node4.isObjectLiteralExpression(relInit)) continue;
1557
1137
  const keysProp = relInit.getProperty("keys");
1558
- if (!keysProp || !Node5.isPropertyAssignment(keysProp)) continue;
1138
+ if (!keysProp || !Node4.isPropertyAssignment(keysProp)) continue;
1559
1139
  const keysInit = keysProp.getInitializer();
1560
- if (!keysInit || !Node5.isArrayLiteralExpression(keysInit)) continue;
1140
+ if (!keysInit || !Node4.isArrayLiteralExpression(keysInit)) continue;
1561
1141
  for (const el of keysInit.getElements()) {
1562
- if (Node5.isStringLiteral(el)) {
1142
+ if (Node4.isStringLiteral(el)) {
1563
1143
  fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
1564
1144
  }
1565
1145
  }
@@ -1569,267 +1149,65 @@ function extractFilterableEntityFields(filterClass, project) {
1569
1149
  return fields;
1570
1150
  }
1571
1151
 
1572
- // src/discovery/contracts-fast.ts
1573
- async function discoverContractsFast(opts) {
1574
- const { cwd, glob, tsconfig } = opts;
1575
- const tsconfigPath = tsconfig ? resolve3(tsconfig) : join2(cwd, "tsconfig.json");
1576
- let project;
1577
- try {
1578
- project = new Project({
1579
- tsConfigFilePath: tsconfigPath,
1580
- skipAddingFilesFromTsConfig: true,
1581
- skipLoadingLibFiles: true,
1582
- skipFileDependencyResolution: true
1583
- });
1584
- } catch {
1585
- project = new Project({
1586
- skipAddingFilesFromTsConfig: true,
1587
- skipLoadingLibFiles: true,
1588
- skipFileDependencyResolution: true,
1589
- compilerOptions: {
1590
- allowJs: true,
1591
- resolveJsonModule: false,
1592
- strict: false
1593
- }
1594
- });
1595
- }
1596
- const files = await fg(glob, { cwd, absolute: true, onlyFiles: true });
1597
- for (const f of files) {
1598
- project.addSourceFileAtPath(f);
1599
- }
1600
- const routes = [];
1601
- const prevCtx = setDiscoveryContext({
1602
- projectRoot: cwd,
1603
- tsconfigPaths: loadTsconfigPaths(tsconfigPath)
1604
- });
1605
- try {
1606
- for (const sourceFile of project.getSourceFiles()) {
1607
- routes.push(...extractFromSourceFile(sourceFile, project));
1608
- }
1609
- } finally {
1610
- restoreDiscoveryContext(prevCtx);
1611
- }
1612
- return routes;
1613
- }
1614
- function zodAstToTs(node) {
1615
- if (!Node6.isCallExpression(node)) return "unknown";
1616
- const expr = node.getExpression();
1617
- if (Node6.isPropertyAccessExpression(expr)) {
1618
- const methodName = expr.getName();
1619
- const receiver = expr.getExpression();
1620
- if (methodName === "optional") {
1621
- return `${zodAstToTs(receiver)} | undefined`;
1622
- }
1623
- if (methodName === "nullable") {
1624
- return `${zodAstToTs(receiver)} | null`;
1625
- }
1626
- const args = node.getArguments();
1627
- switch (methodName) {
1628
- case "string":
1629
- return "string";
1630
- case "number":
1631
- return "number";
1632
- case "boolean":
1633
- return "boolean";
1634
- case "unknown":
1635
- return "unknown";
1636
- case "any":
1637
- return "unknown";
1638
- case "literal": {
1639
- const lit = args[0];
1640
- if (!lit) return "unknown";
1641
- if (Node6.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
1642
- if (Node6.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
1643
- if (lit.getKind() === SyntaxKind2.TrueKeyword) return "true";
1644
- if (lit.getKind() === SyntaxKind2.FalseKeyword) return "false";
1645
- return "unknown";
1646
- }
1647
- case "enum": {
1648
- const arrArg = args[0];
1649
- if (!arrArg || !Node6.isArrayLiteralExpression(arrArg)) return "unknown";
1650
- const members = arrArg.getElements().map(
1651
- (el) => Node6.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
1652
- );
1653
- return members.join(" | ");
1654
- }
1655
- case "array": {
1656
- const inner = args[0];
1657
- if (!inner) return "unknown";
1658
- return `Array<${zodAstToTs(inner)}>`;
1659
- }
1660
- case "object": {
1661
- const objArg = args[0];
1662
- if (!objArg || !Node6.isObjectLiteralExpression(objArg)) return "unknown";
1663
- const lines = [];
1664
- for (const prop of objArg.getProperties()) {
1665
- if (!Node6.isPropertyAssignment(prop)) continue;
1666
- const key = prop.getName();
1667
- const valNode = prop.getInitializer();
1668
- if (!valNode) continue;
1669
- const tsType = zodAstToTs(valNode);
1670
- const isOpt = isOptionalChain(valNode);
1671
- lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
1672
- }
1673
- return `{ ${lines.join("; ")} }`;
1674
- }
1675
- case "union": {
1676
- const arrArg = args[0];
1677
- if (!arrArg || !Node6.isArrayLiteralExpression(arrArg)) return "unknown";
1678
- return arrArg.getElements().map(zodAstToTs).join(" | ");
1679
- }
1680
- case "record": {
1681
- const valArg = args.length === 1 ? args[0] : args[1];
1682
- if (!valArg) return "unknown";
1683
- return `Record<string, ${zodAstToTs(valArg)}>`;
1684
- }
1685
- case "tuple": {
1686
- const arrArg = args[0];
1687
- if (!arrArg || !Node6.isArrayLiteralExpression(arrArg)) return "unknown";
1688
- return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
1689
- }
1690
- default:
1691
- return "unknown";
1692
- }
1693
- }
1694
- return "unknown";
1695
- }
1696
- function isOptionalChain(node) {
1697
- if (!Node6.isCallExpression(node)) return false;
1698
- const expr = node.getExpression();
1699
- return Node6.isPropertyAccessExpression(expr) && expr.getName() === "optional";
1700
- }
1701
- function decoratorStringArg(decoratorExpr) {
1702
- if (!decoratorExpr) return void 0;
1703
- if (Node6.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
1704
- if (Node6.isArrayLiteralExpression(decoratorExpr)) {
1705
- const first = decoratorExpr.getElements()[0];
1706
- if (first && Node6.isStringLiteral(first)) return first.getLiteralValue();
1707
- }
1708
- return void 0;
1709
- }
1710
- function parseDefineContractCall(callExpr) {
1711
- if (!Node6.isCallExpression(callExpr)) return null;
1712
- const callee = callExpr.getExpression();
1713
- const calleeName = Node6.isIdentifier(callee) ? callee.getText() : Node6.isPropertyAccessExpression(callee) ? callee.getName() : "";
1714
- if (calleeName !== "defineContract") return null;
1715
- const args = callExpr.getArguments();
1716
- const optsArg = args[0];
1717
- if (!optsArg || !Node6.isObjectLiteralExpression(optsArg)) return null;
1718
- let query = null;
1719
- let body = null;
1720
- let response = "unknown";
1721
- let bodyZodText = null;
1722
- let queryZodText = null;
1723
- for (const prop of optsArg.getProperties()) {
1724
- if (!Node6.isPropertyAssignment(prop)) continue;
1725
- const propName = prop.getName();
1726
- const val = prop.getInitializer();
1727
- if (!val) continue;
1728
- if (propName === "query") {
1729
- query = zodAstToTs(val);
1730
- queryZodText = val.getText();
1731
- } else if (propName === "body") {
1732
- body = zodAstToTs(val);
1733
- bodyZodText = val.getText();
1734
- } else if (propName === "response") {
1735
- response = zodAstToTs(val);
1736
- }
1737
- }
1738
- return { query, body, response, bodyZodText, queryZodText };
1739
- }
1740
- function deriveClassSegment(className) {
1741
- const noSuffix = className.replace(/Controller$/, "");
1742
- if (!noSuffix) {
1743
- throw new Error(
1744
- `Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
1745
- );
1746
- }
1747
- return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
1748
- }
1749
- function resolveRouteName(className, methodName, classAs, methodAs) {
1750
- const classPortion = classAs ?? deriveClassSegment(className);
1751
- const methodPortion = methodAs ?? methodName;
1752
- return `${classPortion}.${methodPortion}`;
1753
- }
1754
- function joinPaths(prefix, suffix) {
1755
- if (!prefix && !suffix) return "/";
1756
- if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
1757
- if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
1758
- const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1759
- const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
1760
- const combined = p + s;
1761
- return combined === "" ? "/" : combined;
1762
- }
1763
- function extractParams(path) {
1764
- const matches = path.matchAll(/:(\w+)/g);
1765
- return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
1766
- }
1152
+ // src/discovery/dto-type-resolver.ts
1153
+ var WRAPPER_TYPES = {
1154
+ // MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
1155
+ // wrappers around related entities; the wire shape is just the referenced
1156
+ // entity. Unwrap to the type argument.
1157
+ Ref: "unwrap",
1158
+ Reference: "unwrap",
1159
+ LoadedReference: "unwrap",
1160
+ IdentifiedReference: "unwrap",
1161
+ // MikroORM Opt<T> is a marker, Loaded<T, ...> is a wrapper; both reduce to T.
1162
+ Opt: "unwrap",
1163
+ Loaded: "unwrap",
1164
+ // Promise<T> — unwrap
1165
+ Promise: "unwrap",
1166
+ // MikroORM Collection<T> serializes as an array of T on the wire.
1167
+ Collection: "arrayOf",
1168
+ // Array<T> generic form
1169
+ Array: "arrayOf"
1170
+ };
1171
+ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
1172
+ "Record",
1173
+ "Omit",
1174
+ "Pick",
1175
+ "Partial",
1176
+ "Required",
1177
+ "Readonly",
1178
+ "Map",
1179
+ "Set"
1180
+ ]);
1767
1181
  function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1768
1182
  if (depth <= 0) return "unknown";
1769
- if (Node6.isArrayTypeNode(typeNode)) {
1183
+ if (Node5.isArrayTypeNode(typeNode)) {
1770
1184
  const elementType = typeNode.getElementTypeNode();
1771
1185
  return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
1772
1186
  }
1773
- if (Node6.isUnionTypeNode(typeNode)) {
1187
+ if (Node5.isUnionTypeNode(typeNode)) {
1774
1188
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
1775
1189
  }
1776
- if (Node6.isIntersectionTypeNode(typeNode)) {
1190
+ if (Node5.isIntersectionTypeNode(typeNode)) {
1777
1191
  return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
1778
1192
  }
1779
- if (Node6.isParenthesizedTypeNode(typeNode)) {
1193
+ if (Node5.isParenthesizedTypeNode(typeNode)) {
1780
1194
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
1781
1195
  }
1782
- if (Node6.isTypeReference(typeNode)) {
1196
+ if (Node5.isTypeReference(typeNode)) {
1783
1197
  const typeName = typeNode.getTypeName();
1784
- const name = Node6.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
1198
+ const name = Node5.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
1785
1199
  if (name === "string" || name === "number" || name === "boolean") return name;
1786
1200
  if (name === "Date") return "string";
1787
1201
  if (name === "unknown" || name === "any" || name === "void") return "unknown";
1788
1202
  if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
1789
1203
  return "unknown";
1790
- if (name === "Ref" || name === "Reference" || name === "LoadedReference" || name === "IdentifiedReference") {
1791
- const typeArgs = typeNode.getTypeArguments();
1792
- const firstTypeArg = typeArgs[0];
1793
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
1794
- return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
1795
- }
1796
- return "unknown";
1797
- }
1798
- if (name === "Collection") {
1799
- const typeArgs = typeNode.getTypeArguments();
1800
- const firstTypeArg = typeArgs[0];
1801
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
1802
- return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
1803
- }
1804
- return "Array<unknown>";
1805
- }
1806
- if (name === "Opt" || name === "Loaded") {
1807
- const typeArgs = typeNode.getTypeArguments();
1808
- const firstTypeArg = typeArgs[0];
1809
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
1810
- return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
1811
- }
1812
- return "unknown";
1204
+ const wrapperMode = WRAPPER_TYPES[name];
1205
+ if (wrapperMode) {
1206
+ return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
1813
1207
  }
1814
- if (name === "Array") {
1815
- const typeArgs = typeNode.getTypeArguments();
1816
- const firstTypeArg = typeArgs[0];
1817
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
1818
- return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
1819
- }
1820
- return "Array<unknown>";
1821
- }
1822
- if (["Record", "Omit", "Pick", "Partial", "Required", "Readonly", "Map", "Set"].includes(name)) {
1208
+ if (PASSTHROUGH_UTILITY.has(name)) {
1823
1209
  return typeNode.getText();
1824
1210
  }
1825
- if (name === "Promise") {
1826
- const typeArgs = typeNode.getTypeArguments();
1827
- const firstTypeArg = typeArgs[0];
1828
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
1829
- return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
1830
- }
1831
- return "unknown";
1832
- }
1833
1211
  const resolved = findType(name, sourceFile, project);
1834
1212
  if (resolved) {
1835
1213
  return expandTypeDecl(resolved, project, depth - 1);
@@ -1845,6 +1223,15 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1845
1223
  if (kind === SyntaxKind2.AnyKeyword) return "unknown";
1846
1224
  return typeNode.getText();
1847
1225
  }
1226
+ function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
1227
+ const typeArgs = typeNode.getTypeArguments();
1228
+ const firstTypeArg = typeArgs[0];
1229
+ if (typeArgs.length > 0 && firstTypeArg !== void 0) {
1230
+ const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
1231
+ return mode === "arrayOf" ? `Array<${inner}>` : inner;
1232
+ }
1233
+ return mode === "arrayOf" ? "Array<unknown>" : "unknown";
1234
+ }
1848
1235
  function expandTypeDecl(result, project, depth) {
1849
1236
  if (depth < 0) return "unknown";
1850
1237
  switch (result.kind) {
@@ -1910,7 +1297,7 @@ function extractParamsType(method, sourceFile, project) {
1910
1297
  const paramArgs = paramDecorator.getArguments();
1911
1298
  if (paramArgs.length === 0) continue;
1912
1299
  const nameArg = paramArgs[0];
1913
- if (!Node6.isStringLiteral(nameArg)) continue;
1300
+ if (!Node5.isStringLiteral(nameArg)) continue;
1914
1301
  const paramName = nameArg.getLiteralValue();
1915
1302
  const typeNode = param.getTypeNode();
1916
1303
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -1923,13 +1310,13 @@ function extractResponseType(method, sourceFile, project) {
1923
1310
  if (apiResponseDecorator) {
1924
1311
  const args = apiResponseDecorator.getArguments();
1925
1312
  const optsArg = args[0];
1926
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
1313
+ if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
1927
1314
  for (const prop of optsArg.getProperties()) {
1928
- if (!Node6.isPropertyAssignment(prop)) continue;
1315
+ if (!Node5.isPropertyAssignment(prop)) continue;
1929
1316
  if (prop.getName() !== "type") continue;
1930
1317
  const val = prop.getInitializer();
1931
1318
  if (!val) continue;
1932
- if (Node6.isArrayLiteralExpression(val)) {
1319
+ if (Node5.isArrayLiteralExpression(val)) {
1933
1320
  const elements = val.getElements();
1934
1321
  const firstEl = elements[0];
1935
1322
  if (elements.length > 0 && firstEl !== void 0) {
@@ -1949,7 +1336,7 @@ function extractResponseType(method, sourceFile, project) {
1949
1336
  return "unknown";
1950
1337
  }
1951
1338
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
1952
- if (!Node6.isIdentifier(node)) return "unknown";
1339
+ if (!Node5.isIdentifier(node)) return "unknown";
1953
1340
  const name = node.getText();
1954
1341
  const resolved = findType(name, sourceFile, project);
1955
1342
  if (resolved) {
@@ -1996,11 +1383,11 @@ function extractDtoContract(method, sourceFile, project) {
1996
1383
  if (apiResp) {
1997
1384
  const args = apiResp.getArguments();
1998
1385
  const optsArg = args[0];
1999
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
1386
+ if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
2000
1387
  for (const prop of optsArg.getProperties()) {
2001
- if (Node6.isPropertyAssignment(prop) && prop.getName() === "type") {
1388
+ if (Node5.isPropertyAssignment(prop) && prop.getName() === "type") {
2002
1389
  const val = prop.getInitializer();
2003
- if (val && Node6.isIdentifier(val)) {
1390
+ if (val && Node5.isIdentifier(val)) {
2004
1391
  const name = val.getText();
2005
1392
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
2006
1393
  if (localDecl?.isExported()) {
@@ -2017,27 +1404,18 @@ function extractDtoContract(method, sourceFile, project) {
2017
1404
  }
2018
1405
  }
2019
1406
  }
2020
- let bodyZodText = null;
2021
- let queryZodText = null;
2022
1407
  let bodySchema = null;
2023
1408
  let querySchema = null;
2024
- const formNested = {};
2025
1409
  const formWarnings = [];
2026
1410
  const bodyClass = resolveParamClass(method, "Body", sourceFile, project);
2027
1411
  if (bodyClass) {
2028
- const result = extractZodFromDto(bodyClass.decl, bodyClass.file, project);
2029
- bodyZodText = result.schemaText;
2030
- for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
2031
- formWarnings.push(...result.warnings);
2032
1412
  bodySchema = extractSchemaFromDto(bodyClass.decl, bodyClass.file, project);
1413
+ formWarnings.push(...bodySchema.warnings);
2033
1414
  }
2034
1415
  const queryClass = resolveParamClass(method, "Query", sourceFile, project);
2035
1416
  if (queryClass) {
2036
- const result = extractZodFromDto(queryClass.decl, queryClass.file, project);
2037
- queryZodText = result.schemaText;
2038
- for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
2039
- formWarnings.push(...result.warnings);
2040
1417
  querySchema = extractSchemaFromDto(queryClass.decl, queryClass.file, project);
1418
+ formWarnings.push(...querySchema.warnings);
2041
1419
  }
2042
1420
  return {
2043
1421
  query,
@@ -2050,9 +1428,6 @@ function extractDtoContract(method, sourceFile, project) {
2050
1428
  filterFields: filterInfo?.fieldNames ?? null,
2051
1429
  filterFieldTypes: filterInfo?.fieldTypes ?? null,
2052
1430
  filterSource: filterInfo?.source ?? null,
2053
- bodyZodText,
2054
- queryZodText,
2055
- formNestedSchemas: Object.keys(formNested).length > 0 ? formNested : null,
2056
1431
  formWarnings,
2057
1432
  bodySchema,
2058
1433
  querySchema
@@ -2072,6 +1447,201 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
2072
1447
  }
2073
1448
  return null;
2074
1449
  }
1450
+
1451
+ // src/discovery/zod-ast-to-ts.ts
1452
+ import { Node as Node6, SyntaxKind as SyntaxKind3 } from "ts-morph";
1453
+ function zodAstToTs(node) {
1454
+ if (!Node6.isCallExpression(node)) return "unknown";
1455
+ const expr = node.getExpression();
1456
+ if (Node6.isPropertyAccessExpression(expr)) {
1457
+ const methodName = expr.getName();
1458
+ const receiver = expr.getExpression();
1459
+ if (methodName === "optional") {
1460
+ return `${zodAstToTs(receiver)} | undefined`;
1461
+ }
1462
+ if (methodName === "nullable") {
1463
+ return `${zodAstToTs(receiver)} | null`;
1464
+ }
1465
+ const args = node.getArguments();
1466
+ switch (methodName) {
1467
+ case "string":
1468
+ return "string";
1469
+ case "number":
1470
+ return "number";
1471
+ case "boolean":
1472
+ return "boolean";
1473
+ case "unknown":
1474
+ return "unknown";
1475
+ case "any":
1476
+ return "unknown";
1477
+ case "literal": {
1478
+ const lit = args[0];
1479
+ if (!lit) return "unknown";
1480
+ if (Node6.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
1481
+ if (Node6.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
1482
+ if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
1483
+ if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
1484
+ return "unknown";
1485
+ }
1486
+ case "enum": {
1487
+ const arrArg = args[0];
1488
+ if (!arrArg || !Node6.isArrayLiteralExpression(arrArg)) return "unknown";
1489
+ const members = arrArg.getElements().map(
1490
+ (el) => Node6.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
1491
+ );
1492
+ return members.join(" | ");
1493
+ }
1494
+ case "array": {
1495
+ const inner = args[0];
1496
+ if (!inner) return "unknown";
1497
+ return `Array<${zodAstToTs(inner)}>`;
1498
+ }
1499
+ case "object": {
1500
+ const objArg = args[0];
1501
+ if (!objArg || !Node6.isObjectLiteralExpression(objArg)) return "unknown";
1502
+ const lines = [];
1503
+ for (const prop of objArg.getProperties()) {
1504
+ if (!Node6.isPropertyAssignment(prop)) continue;
1505
+ const key = prop.getName();
1506
+ const valNode = prop.getInitializer();
1507
+ if (!valNode) continue;
1508
+ const tsType = zodAstToTs(valNode);
1509
+ const isOpt = isOptionalChain(valNode);
1510
+ lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
1511
+ }
1512
+ return `{ ${lines.join("; ")} }`;
1513
+ }
1514
+ case "union": {
1515
+ const arrArg = args[0];
1516
+ if (!arrArg || !Node6.isArrayLiteralExpression(arrArg)) return "unknown";
1517
+ return arrArg.getElements().map(zodAstToTs).join(" | ");
1518
+ }
1519
+ case "record": {
1520
+ const valArg = args.length === 1 ? args[0] : args[1];
1521
+ if (!valArg) return "unknown";
1522
+ return `Record<string, ${zodAstToTs(valArg)}>`;
1523
+ }
1524
+ case "tuple": {
1525
+ const arrArg = args[0];
1526
+ if (!arrArg || !Node6.isArrayLiteralExpression(arrArg)) return "unknown";
1527
+ return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
1528
+ }
1529
+ default:
1530
+ return "unknown";
1531
+ }
1532
+ }
1533
+ return "unknown";
1534
+ }
1535
+ function isOptionalChain(node) {
1536
+ if (!Node6.isCallExpression(node)) return false;
1537
+ const expr = node.getExpression();
1538
+ return Node6.isPropertyAccessExpression(expr) && expr.getName() === "optional";
1539
+ }
1540
+ function parseDefineContractCall(callExpr) {
1541
+ if (!Node6.isCallExpression(callExpr)) return null;
1542
+ const callee = callExpr.getExpression();
1543
+ const calleeName = Node6.isIdentifier(callee) ? callee.getText() : Node6.isPropertyAccessExpression(callee) ? callee.getName() : "";
1544
+ if (calleeName !== "defineContract") return null;
1545
+ const args = callExpr.getArguments();
1546
+ const optsArg = args[0];
1547
+ if (!optsArg || !Node6.isObjectLiteralExpression(optsArg)) return null;
1548
+ let query = null;
1549
+ let body = null;
1550
+ let response = "unknown";
1551
+ let bodyZodText = null;
1552
+ let queryZodText = null;
1553
+ for (const prop of optsArg.getProperties()) {
1554
+ if (!Node6.isPropertyAssignment(prop)) continue;
1555
+ const propName = prop.getName();
1556
+ const val = prop.getInitializer();
1557
+ if (!val) continue;
1558
+ if (propName === "query") {
1559
+ query = zodAstToTs(val);
1560
+ queryZodText = val.getText();
1561
+ } else if (propName === "body") {
1562
+ body = zodAstToTs(val);
1563
+ bodyZodText = val.getText();
1564
+ } else if (propName === "response") {
1565
+ response = zodAstToTs(val);
1566
+ }
1567
+ }
1568
+ return { query, body, response, bodyZodText, queryZodText };
1569
+ }
1570
+
1571
+ // src/discovery/contracts-fast.ts
1572
+ async function discoverContractsFast(opts) {
1573
+ const { cwd, glob, tsconfig } = opts;
1574
+ const tsconfigPath = tsconfig ? resolve3(tsconfig) : join2(cwd, "tsconfig.json");
1575
+ let project;
1576
+ try {
1577
+ project = new Project({
1578
+ tsConfigFilePath: tsconfigPath,
1579
+ skipAddingFilesFromTsConfig: true,
1580
+ skipLoadingLibFiles: true,
1581
+ skipFileDependencyResolution: true
1582
+ });
1583
+ } catch {
1584
+ project = new Project({
1585
+ skipAddingFilesFromTsConfig: true,
1586
+ skipLoadingLibFiles: true,
1587
+ skipFileDependencyResolution: true,
1588
+ compilerOptions: {
1589
+ allowJs: true,
1590
+ resolveJsonModule: false,
1591
+ strict: false
1592
+ }
1593
+ });
1594
+ }
1595
+ const files = await fg(glob, { cwd, absolute: true, onlyFiles: true });
1596
+ for (const f of files) {
1597
+ project.addSourceFileAtPath(f);
1598
+ }
1599
+ const routes = [];
1600
+ setDiscoveryContext(project, {
1601
+ projectRoot: cwd,
1602
+ tsconfigPaths: loadTsconfigPaths(tsconfigPath)
1603
+ });
1604
+ for (const sourceFile of project.getSourceFiles()) {
1605
+ routes.push(...extractFromSourceFile(sourceFile, project));
1606
+ }
1607
+ return routes;
1608
+ }
1609
+ function decoratorStringArg(decoratorExpr) {
1610
+ if (!decoratorExpr) return void 0;
1611
+ if (Node7.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
1612
+ if (Node7.isArrayLiteralExpression(decoratorExpr)) {
1613
+ const first = decoratorExpr.getElements()[0];
1614
+ if (first && Node7.isStringLiteral(first)) return first.getLiteralValue();
1615
+ }
1616
+ return void 0;
1617
+ }
1618
+ function deriveClassSegment(className) {
1619
+ const noSuffix = className.replace(/Controller$/, "");
1620
+ if (!noSuffix) {
1621
+ throw new Error(
1622
+ `Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
1623
+ );
1624
+ }
1625
+ return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
1626
+ }
1627
+ function resolveRouteName(className, methodName, classAs, methodAs) {
1628
+ const classPortion = classAs ?? deriveClassSegment(className);
1629
+ const methodPortion = methodAs ?? methodName;
1630
+ return `${classPortion}.${methodPortion}`;
1631
+ }
1632
+ function joinPaths(prefix, suffix) {
1633
+ if (!prefix && !suffix) return "/";
1634
+ if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
1635
+ if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
1636
+ const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1637
+ const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
1638
+ const combined = p + s;
1639
+ return combined === "" ? "/" : combined;
1640
+ }
1641
+ function extractParams(path) {
1642
+ const matches = path.matchAll(/:(\w+)/g);
1643
+ return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
1644
+ }
2075
1645
  var HTTP_METHOD_DECORATORS = {
2076
1646
  Get: "GET",
2077
1647
  Post: "POST",
@@ -2082,176 +1652,186 @@ var HTTP_METHOD_DECORATORS = {
2082
1652
  Head: "HEAD",
2083
1653
  All: "ALL"
2084
1654
  };
1655
+ function resolveVerb(method) {
1656
+ for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
1657
+ const httpDecorator = method.getDecorator(decoratorName);
1658
+ if (httpDecorator) {
1659
+ const httpArgs = httpDecorator.getArguments();
1660
+ const pathArg = httpArgs[0];
1661
+ return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
1662
+ }
1663
+ }
1664
+ return null;
1665
+ }
1666
+ function readAsDecorator(node, label) {
1667
+ const asDecorator = node.getDecorator("As");
1668
+ if (!asDecorator) return void 0;
1669
+ const asName = decoratorStringArg(asDecorator.getArguments()[0]);
1670
+ if (!asName) {
1671
+ throw new Error(`@As decorator on ${label} must have a non-empty string argument.`);
1672
+ }
1673
+ return asName;
1674
+ }
1675
+ function buildRoute(args) {
1676
+ const {
1677
+ className,
1678
+ methodName,
1679
+ resolvedMethod,
1680
+ combinedPath,
1681
+ classAs,
1682
+ methodAs,
1683
+ sourceFile,
1684
+ seenNames,
1685
+ contractSource
1686
+ } = args;
1687
+ const routeName = resolveRouteName(className, methodName, classAs, methodAs);
1688
+ const qualifiedRef = `${className}.${methodName}`;
1689
+ const existing = seenNames.get(routeName);
1690
+ if (existing !== void 0) {
1691
+ throw new Error(
1692
+ `Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
1693
+ );
1694
+ }
1695
+ seenNames.set(routeName, qualifiedRef);
1696
+ return {
1697
+ method: resolvedMethod,
1698
+ path: combinedPath,
1699
+ name: routeName,
1700
+ params: extractParams(combinedPath),
1701
+ controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
1702
+ contract: { contractSource }
1703
+ };
1704
+ }
1705
+ function extractContractRoute(args) {
1706
+ const { cls, method, applyContractDecorator, verb, prefix, className, sourceFile, seenNames } = args;
1707
+ const firstDecoratorArg = applyContractDecorator.getArguments()[0];
1708
+ if (!firstDecoratorArg) return null;
1709
+ let contractDef = null;
1710
+ let bodyZodRef = null;
1711
+ let queryZodRef = null;
1712
+ if (Node7.isCallExpression(firstDecoratorArg)) {
1713
+ contractDef = parseDefineContractCall(firstDecoratorArg);
1714
+ } else if (Node7.isIdentifier(firstDecoratorArg)) {
1715
+ const identName = firstDecoratorArg.getText();
1716
+ const varDecl = sourceFile.getVariableDeclaration(identName);
1717
+ if (!varDecl) {
1718
+ console.warn(
1719
+ `[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
1720
+ );
1721
+ return null;
1722
+ }
1723
+ const initializer = varDecl.getInitializer();
1724
+ if (!initializer) return null;
1725
+ contractDef = parseDefineContractCall(initializer);
1726
+ if (contractDef && varDecl.isExported()) {
1727
+ const filePath = sourceFile.getFilePath();
1728
+ if (contractDef.body !== null) {
1729
+ bodyZodRef = { name: `${identName}.body`, filePath };
1730
+ }
1731
+ if (contractDef.query !== null) {
1732
+ queryZodRef = { name: `${identName}.query`, filePath };
1733
+ }
1734
+ }
1735
+ } else {
1736
+ console.warn(
1737
+ `[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
1738
+ );
1739
+ return null;
1740
+ }
1741
+ if (!contractDef) return null;
1742
+ if (!verb) return null;
1743
+ const resolvedPath = joinPaths(prefix, verb.handlerPath);
1744
+ const methodName = method.getName();
1745
+ const classAs = readAsDecorator(cls, `class ${className}`);
1746
+ const methodAs = readAsDecorator(method, `${className}.${methodName}`);
1747
+ return buildRoute({
1748
+ className,
1749
+ methodName,
1750
+ resolvedMethod: verb.httpMethod,
1751
+ combinedPath: resolvedPath,
1752
+ classAs,
1753
+ methodAs,
1754
+ sourceFile,
1755
+ seenNames,
1756
+ contractSource: {
1757
+ query: contractDef.query,
1758
+ body: contractDef.body,
1759
+ response: contractDef.response,
1760
+ // Path A: capture both the importable ref and the raw text. The emitter
1761
+ // prefers inlining the text (client-safe — re-exporting from a controller
1762
+ // would drag server-only deps into the client bundle).
1763
+ bodyZodRef,
1764
+ bodyZodText: contractDef.bodyZodText,
1765
+ queryZodRef,
1766
+ queryZodText: contractDef.queryZodText
1767
+ }
1768
+ });
1769
+ }
1770
+ function extractDtoRoute(args) {
1771
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
1772
+ if (!verb) return null;
1773
+ const combined = joinPaths(prefix, verb.handlerPath);
1774
+ const methodName = method.getName();
1775
+ const classAs = readAsDecorator(cls, `class ${className}`);
1776
+ const methodAs = readAsDecorator(method, `${className}.${methodName}`);
1777
+ const dtoContract = extractDtoContract(method, sourceFile, project);
1778
+ return buildRoute({
1779
+ className,
1780
+ methodName,
1781
+ resolvedMethod: verb.httpMethod,
1782
+ combinedPath: combined,
1783
+ classAs,
1784
+ methodAs,
1785
+ sourceFile,
1786
+ seenNames,
1787
+ contractSource: {
1788
+ query: dtoContract?.query ?? null,
1789
+ body: dtoContract?.body ?? null,
1790
+ response: dtoContract?.response ?? "unknown",
1791
+ queryRef: dtoContract?.queryRef ?? null,
1792
+ bodyRef: dtoContract?.bodyRef ?? null,
1793
+ responseRef: dtoContract?.responseRef ?? null,
1794
+ filterFields: dtoContract?.filterFields ?? null,
1795
+ filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
1796
+ filterSource: dtoContract?.filterSource ?? null,
1797
+ formWarnings: dtoContract?.formWarnings ?? [],
1798
+ bodySchema: dtoContract?.bodySchema ?? null,
1799
+ querySchema: dtoContract?.querySchema ?? null
1800
+ }
1801
+ });
1802
+ }
2085
1803
  function extractFromSourceFile(sourceFile, project) {
2086
1804
  const routes = [];
2087
1805
  const seenNames = /* @__PURE__ */ new Map();
2088
- const classes = sourceFile.getClasses();
2089
- for (const cls of classes) {
1806
+ for (const cls of sourceFile.getClasses()) {
2090
1807
  const controllerDecorator = cls.getDecorator("Controller");
2091
1808
  if (!controllerDecorator) continue;
2092
- const controllerArgs = controllerDecorator.getArguments();
2093
- const firstArg3 = controllerArgs[0];
2094
- const prefix = decoratorStringArg(firstArg3) ?? "";
1809
+ const firstArg2 = controllerDecorator.getArguments()[0];
1810
+ const prefix = decoratorStringArg(firstArg2) ?? "";
2095
1811
  const className = cls.getName() ?? "Unknown";
2096
1812
  for (const method of cls.getMethods()) {
2097
- let httpMethod;
2098
- let handlerPath = "";
2099
- for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
2100
- const httpDecorator = method.getDecorator(decoratorName);
2101
- if (httpDecorator) {
2102
- httpMethod = verb;
2103
- const httpArgs = httpDecorator.getArguments();
2104
- const pathArg = httpArgs[0];
2105
- handlerPath = decoratorStringArg(pathArg) ?? "";
2106
- break;
2107
- }
2108
- }
1813
+ const verb = resolveVerb(method);
2109
1814
  const applyContractDecorator = method.getDecorator("ApplyContract");
2110
- if (applyContractDecorator) {
2111
- const decoratorArgs = applyContractDecorator.getArguments();
2112
- const firstDecoratorArg = decoratorArgs[0];
2113
- if (!firstDecoratorArg) continue;
2114
- let contractDef = null;
2115
- let bodyZodRef = null;
2116
- let queryZodRef = null;
2117
- if (Node6.isCallExpression(firstDecoratorArg)) {
2118
- contractDef = parseDefineContractCall(firstDecoratorArg);
2119
- } else if (Node6.isIdentifier(firstDecoratorArg)) {
2120
- const identName = firstDecoratorArg.getText();
2121
- const varDecl = sourceFile.getVariableDeclaration(identName);
2122
- if (!varDecl) {
2123
- console.warn(
2124
- `[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
2125
- );
2126
- continue;
2127
- }
2128
- const initializer = varDecl.getInitializer();
2129
- if (!initializer) continue;
2130
- contractDef = parseDefineContractCall(initializer);
2131
- if (contractDef && varDecl.isExported()) {
2132
- const filePath = sourceFile.getFilePath();
2133
- if (contractDef.body !== null) {
2134
- bodyZodRef = { name: `${identName}.body`, filePath };
2135
- }
2136
- if (contractDef.query !== null) {
2137
- queryZodRef = { name: `${identName}.query`, filePath };
2138
- }
2139
- }
2140
- } else {
2141
- console.warn(
2142
- `[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
2143
- );
2144
- continue;
2145
- }
2146
- if (!contractDef) continue;
2147
- if (!httpMethod) continue;
2148
- const resolvedMethod = httpMethod;
2149
- const resolvedPath = joinPaths(prefix, handlerPath);
2150
- const combined = resolvedPath;
2151
- const params = extractParams(combined);
2152
- const methodName = method.getName();
2153
- const classAsDecorator = cls.getDecorator("As");
2154
- let classAs;
2155
- if (classAsDecorator) {
2156
- const classAsArgs = classAsDecorator.getArguments();
2157
- const classAsName = decoratorStringArg(classAsArgs[0]);
2158
- if (!classAsName) {
2159
- throw new Error(
2160
- `@As decorator on class ${className} must have a non-empty string argument.`
2161
- );
2162
- }
2163
- classAs = classAsName;
2164
- }
2165
- const methodAsDecorator = method.getDecorator("As");
2166
- let methodAs;
2167
- if (methodAsDecorator) {
2168
- const methodAsArgs = methodAsDecorator.getArguments();
2169
- const methodAsName = decoratorStringArg(methodAsArgs[0]);
2170
- if (!methodAsName) {
2171
- throw new Error(
2172
- `@As decorator on ${className}.${methodName} must have a non-empty string argument.`
2173
- );
2174
- }
2175
- methodAs = methodAsName;
2176
- }
2177
- const routeName = resolveRouteName(className, methodName, classAs, methodAs);
2178
- const qualifiedRef = `${className}.${methodName}`;
2179
- const existing = seenNames.get(routeName);
2180
- if (existing !== void 0) {
2181
- throw new Error(
2182
- `Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
2183
- );
2184
- }
2185
- seenNames.set(routeName, qualifiedRef);
2186
- routes.push({
2187
- method: resolvedMethod,
2188
- path: combined,
2189
- name: routeName,
2190
- params,
2191
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
2192
- contract: {
2193
- contractSource: {
2194
- query: contractDef.query,
2195
- body: contractDef.body,
2196
- response: contractDef.response,
2197
- // Path A: capture both the importable ref and the raw text. The
2198
- // emitter prefers inlining the text (client-safe — re-exporting from
2199
- // a controller would drag server-only deps into the client bundle).
2200
- bodyZodRef,
2201
- bodyZodText: contractDef.bodyZodText,
2202
- queryZodRef,
2203
- queryZodText: contractDef.queryZodText
2204
- }
2205
- }
2206
- });
2207
- } else {
2208
- if (!httpMethod) continue;
2209
- const combined = joinPaths(prefix, handlerPath);
2210
- const params = extractParams(combined);
2211
- const methodName = method.getName();
2212
- const classAsDecorator = cls.getDecorator("As");
2213
- let classAs;
2214
- if (classAsDecorator) {
2215
- const classAsArgs = classAsDecorator.getArguments();
2216
- const classAsName = decoratorStringArg(classAsArgs[0]);
2217
- if (classAsName) classAs = classAsName;
2218
- }
2219
- const methodAsDecorator = method.getDecorator("As");
2220
- let methodAs;
2221
- if (methodAsDecorator) {
2222
- const methodAsArgs = methodAsDecorator.getArguments();
2223
- const methodAsName = decoratorStringArg(methodAsArgs[0]);
2224
- if (methodAsName) methodAs = methodAsName;
2225
- }
2226
- const routeName = resolveRouteName(className, methodName, classAs, methodAs);
2227
- const dtoContract = extractDtoContract(method, sourceFile, project);
2228
- routes.push({
2229
- method: httpMethod,
2230
- path: combined,
2231
- name: routeName,
2232
- params,
2233
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
2234
- contract: {
2235
- contractSource: {
2236
- query: dtoContract?.query ?? null,
2237
- body: dtoContract?.body ?? null,
2238
- response: dtoContract?.response ?? "unknown",
2239
- queryRef: dtoContract?.queryRef ?? null,
2240
- bodyRef: dtoContract?.bodyRef ?? null,
2241
- responseRef: dtoContract?.responseRef ?? null,
2242
- filterFields: dtoContract?.filterFields ?? null,
2243
- filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
2244
- filterSource: dtoContract?.filterSource ?? null,
2245
- bodyZodText: dtoContract?.bodyZodText ?? null,
2246
- queryZodText: dtoContract?.queryZodText ?? null,
2247
- formNestedSchemas: dtoContract?.formNestedSchemas ?? null,
2248
- formWarnings: dtoContract?.formWarnings ?? [],
2249
- bodySchema: dtoContract?.bodySchema ?? null,
2250
- querySchema: dtoContract?.querySchema ?? null
2251
- }
2252
- }
2253
- });
2254
- }
1815
+ const route = applyContractDecorator ? extractContractRoute({
1816
+ cls,
1817
+ method,
1818
+ applyContractDecorator,
1819
+ verb,
1820
+ prefix,
1821
+ className,
1822
+ sourceFile,
1823
+ seenNames
1824
+ }) : extractDtoRoute({
1825
+ cls,
1826
+ method,
1827
+ verb,
1828
+ prefix,
1829
+ className,
1830
+ sourceFile,
1831
+ project,
1832
+ seenNames
1833
+ });
1834
+ if (route) routes.push(route);
2255
1835
  }
2256
1836
  }
2257
1837
  return routes;
@@ -2259,8 +1839,7 @@ function extractFromSourceFile(sourceFile, project) {
2259
1839
 
2260
1840
  // src/generate.ts
2261
1841
  import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
2262
- import { dirname as dirname3, join as join10 } from "path";
2263
- import { Project as Project3 } from "ts-morph";
1842
+ import { dirname as dirname3, join as join11 } from "path";
2264
1843
 
2265
1844
  // src/discovery/pages.ts
2266
1845
  import { readFile } from "fs/promises";
@@ -2315,7 +1894,8 @@ function extractPropsSource(source, exportName) {
2315
1894
  }
2316
1895
 
2317
1896
  // src/discovery/shared-props.ts
2318
- import { Node as Node7, SyntaxKind as SyntaxKind3 } from "ts-morph";
1897
+ import { join as join4 } from "path";
1898
+ import { Node as Node8, Project as Project2, SyntaxKind as SyntaxKind4 } from "ts-morph";
2319
1899
  function discoverSharedProps(project, moduleEntry) {
2320
1900
  try {
2321
1901
  let sourceFile = project.getSourceFile(moduleEntry);
@@ -2335,14 +1915,39 @@ function discoverSharedProps(project, moduleEntry) {
2335
1915
  return null;
2336
1916
  }
2337
1917
  }
1918
+ function discoverSharedPropsFromConfig(config) {
1919
+ if (!config.app?.moduleEntry) return null;
1920
+ try {
1921
+ const tsconfigPath = config.app.tsconfig ?? join4(config.codegen.cwd, "tsconfig.json");
1922
+ let project;
1923
+ try {
1924
+ project = new Project2({
1925
+ tsConfigFilePath: tsconfigPath,
1926
+ skipAddingFilesFromTsConfig: true,
1927
+ skipLoadingLibFiles: true,
1928
+ skipFileDependencyResolution: true
1929
+ });
1930
+ } catch {
1931
+ project = new Project2({
1932
+ skipAddingFilesFromTsConfig: true,
1933
+ skipLoadingLibFiles: true,
1934
+ skipFileDependencyResolution: true,
1935
+ compilerOptions: { allowJs: true, strict: false }
1936
+ });
1937
+ }
1938
+ return discoverSharedProps(project, config.app.moduleEntry);
1939
+ } catch {
1940
+ return null;
1941
+ }
1942
+ }
2338
1943
  function findForRootCall(sourceFile) {
2339
- const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind3.CallExpression);
1944
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
2340
1945
  for (const call of callExpressions) {
2341
1946
  const expr = call.getExpression();
2342
- if (!Node7.isPropertyAccessExpression(expr)) continue;
1947
+ if (!Node8.isPropertyAccessExpression(expr)) continue;
2343
1948
  const methodName = expr.getName();
2344
1949
  const objectExpr = expr.getExpression();
2345
- if (methodName === "forRoot" && Node7.isIdentifier(objectExpr)) {
1950
+ if (methodName === "forRoot" && Node8.isIdentifier(objectExpr)) {
2346
1951
  const name = objectExpr.getText();
2347
1952
  if (name === "InertiaModule") {
2348
1953
  return call;
@@ -2352,19 +1957,19 @@ function findForRootCall(sourceFile) {
2352
1957
  return null;
2353
1958
  }
2354
1959
  function findShareInitializer(forRootCall) {
2355
- if (!Node7.isCallExpression(forRootCall)) return null;
1960
+ if (!Node8.isCallExpression(forRootCall)) return null;
2356
1961
  const args = forRootCall.getArguments();
2357
- const firstArg3 = args[0];
2358
- if (!firstArg3 || !Node7.isObjectLiteralExpression(firstArg3)) return null;
2359
- for (const prop of firstArg3.getProperties()) {
2360
- if (Node7.isPropertyAssignment(prop) && prop.getName() === "share") {
1962
+ const firstArg2 = args[0];
1963
+ if (!firstArg2 || !Node8.isObjectLiteralExpression(firstArg2)) return null;
1964
+ for (const prop of firstArg2.getProperties()) {
1965
+ if (Node8.isPropertyAssignment(prop) && prop.getName() === "share") {
2361
1966
  return prop.getInitializer() ?? null;
2362
1967
  }
2363
1968
  }
2364
1969
  return null;
2365
1970
  }
2366
1971
  function extractShareType(node, sourceFile, project) {
2367
- if (Node7.isIdentifier(node)) {
1972
+ if (Node8.isIdentifier(node)) {
2368
1973
  const ref = resolveIdentifierToImportRef(node, sourceFile, project);
2369
1974
  if (ref) {
2370
1975
  return {
@@ -2374,22 +1979,22 @@ function extractShareType(node, sourceFile, project) {
2374
1979
  };
2375
1980
  }
2376
1981
  }
2377
- if (Node7.isArrowFunction(node)) {
1982
+ if (Node8.isArrowFunction(node)) {
2378
1983
  const result = extractFromFunctionLike(node, sourceFile);
2379
1984
  return result ? { ...result, isImportRef: false } : null;
2380
1985
  }
2381
- if (Node7.isFunctionExpression(node)) {
1986
+ if (Node8.isFunctionExpression(node)) {
2382
1987
  const result = extractFromFunctionLike(node, sourceFile);
2383
1988
  return result ? { ...result, isImportRef: false } : null;
2384
1989
  }
2385
- if (Node7.isObjectLiteralExpression(node)) {
1990
+ if (Node8.isObjectLiteralExpression(node)) {
2386
1991
  const result = extractFromObjectLiteral(node);
2387
1992
  return result ? { ...result, isImportRef: false } : null;
2388
1993
  }
2389
1994
  return null;
2390
1995
  }
2391
1996
  function resolveIdentifierToImportRef(id, sourceFile, project) {
2392
- if (!Node7.isIdentifier(id)) return null;
1997
+ if (!Node8.isIdentifier(id)) return null;
2393
1998
  const name = id.getText();
2394
1999
  const localFunc = sourceFile.getFunction(name);
2395
2000
  if (localFunc?.isExported()) {
@@ -2423,49 +2028,49 @@ function resolveIdentifierToImportRef(id, sourceFile, project) {
2423
2028
  return null;
2424
2029
  }
2425
2030
  function extractFromFunctionLike(node, _sourceFile) {
2426
- const returnTypeNode = Node7.isArrowFunction(node) || Node7.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
2031
+ const returnTypeNode = Node8.isArrowFunction(node) || Node8.isFunctionExpression(node) ? node.getReturnTypeNode() : null;
2427
2032
  if (returnTypeNode) {
2428
2033
  return extractFromReturnTypeAnnotation(returnTypeNode);
2429
2034
  }
2430
- if (Node7.isArrowFunction(node)) {
2035
+ if (Node8.isArrowFunction(node)) {
2431
2036
  const body = node.getBody();
2432
- if (Node7.isParenthesizedExpression(body)) {
2037
+ if (Node8.isParenthesizedExpression(body)) {
2433
2038
  const inner = body.getExpression();
2434
- if (Node7.isObjectLiteralExpression(inner)) {
2039
+ if (Node8.isObjectLiteralExpression(inner)) {
2435
2040
  return extractFromObjectLiteral(inner);
2436
2041
  }
2437
2042
  }
2438
- if (Node7.isObjectLiteralExpression(body)) {
2043
+ if (Node8.isObjectLiteralExpression(body)) {
2439
2044
  return extractFromObjectLiteral(body);
2440
2045
  }
2441
- if (Node7.isBlock(body)) {
2046
+ if (Node8.isBlock(body)) {
2442
2047
  return extractFromBlockReturn(body);
2443
2048
  }
2444
2049
  }
2445
- if (Node7.isFunctionExpression(node)) {
2050
+ if (Node8.isFunctionExpression(node)) {
2446
2051
  const body = node.getBody();
2447
- if (Node7.isBlock(body)) {
2052
+ if (Node8.isBlock(body)) {
2448
2053
  return extractFromBlockReturn(body);
2449
2054
  }
2450
2055
  }
2451
2056
  return null;
2452
2057
  }
2453
2058
  function extractFromReturnTypeAnnotation(typeNode) {
2454
- if (Node7.isTypeReference(typeNode)) {
2059
+ if (Node8.isTypeReference(typeNode)) {
2455
2060
  const typeName = typeNode.getTypeName();
2456
- if (Node7.isIdentifier(typeName) && typeName.getText() === "Promise") {
2061
+ if (Node8.isIdentifier(typeName) && typeName.getText() === "Promise") {
2457
2062
  const typeArgs = typeNode.getTypeArguments();
2458
- const firstArg3 = typeArgs[0];
2459
- if (firstArg3) {
2460
- return extractFromReturnTypeAnnotation(firstArg3);
2063
+ const firstArg2 = typeArgs[0];
2064
+ if (firstArg2) {
2065
+ return extractFromReturnTypeAnnotation(firstArg2);
2461
2066
  }
2462
2067
  return null;
2463
2068
  }
2464
2069
  }
2465
- if (Node7.isTypeLiteral(typeNode)) {
2070
+ if (Node8.isTypeLiteral(typeNode)) {
2466
2071
  const properties = [];
2467
2072
  for (const member of typeNode.getMembers()) {
2468
- if (Node7.isPropertySignature(member)) {
2073
+ if (Node8.isPropertySignature(member)) {
2469
2074
  const name = member.getName();
2470
2075
  const memberTypeNode = member.getTypeNode();
2471
2076
  const type = memberTypeNode ? memberTypeNode.getText() : "unknown";
@@ -2479,19 +2084,19 @@ function extractFromReturnTypeAnnotation(typeNode) {
2479
2084
  return null;
2480
2085
  }
2481
2086
  function extractFromBlockReturn(block) {
2482
- if (!Node7.isBlock(block)) return null;
2087
+ if (!Node8.isBlock(block)) return null;
2483
2088
  const statements = block.getStatements();
2484
2089
  for (let i = statements.length - 1; i >= 0; i--) {
2485
2090
  const stmt = statements[i];
2486
- if (!Node7.isReturnStatement(stmt)) continue;
2091
+ if (!Node8.isReturnStatement(stmt)) continue;
2487
2092
  const expr = stmt.getExpression();
2488
2093
  if (!expr) continue;
2489
- if (Node7.isObjectLiteralExpression(expr)) {
2094
+ if (Node8.isObjectLiteralExpression(expr)) {
2490
2095
  return extractFromObjectLiteral(expr);
2491
2096
  }
2492
- if (Node7.isParenthesizedExpression(expr)) {
2097
+ if (Node8.isParenthesizedExpression(expr)) {
2493
2098
  const inner = expr.getExpression();
2494
- if (Node7.isObjectLiteralExpression(inner)) {
2099
+ if (Node8.isObjectLiteralExpression(inner)) {
2495
2100
  return extractFromObjectLiteral(inner);
2496
2101
  }
2497
2102
  }
@@ -2500,10 +2105,10 @@ function extractFromBlockReturn(block) {
2500
2105
  return null;
2501
2106
  }
2502
2107
  function extractFromObjectLiteral(objLiteral) {
2503
- if (!Node7.isObjectLiteralExpression(objLiteral)) return null;
2108
+ if (!Node8.isObjectLiteralExpression(objLiteral)) return null;
2504
2109
  const properties = [];
2505
2110
  for (const prop of objLiteral.getProperties()) {
2506
- if (!Node7.isPropertyAssignment(prop)) continue;
2111
+ if (!Node8.isPropertyAssignment(prop)) continue;
2507
2112
  const name = prop.getName();
2508
2113
  const initializer = prop.getInitializer();
2509
2114
  if (!initializer) continue;
@@ -2515,21 +2120,21 @@ function extractFromObjectLiteral(objLiteral) {
2515
2120
  return { typeString, properties, isImportRef: false };
2516
2121
  }
2517
2122
  function inferExpressionType(node) {
2518
- if (Node7.isStringLiteral(node)) return "string";
2519
- if (Node7.isTemplateExpression(node) || Node7.isNoSubstitutionTemplateLiteral(node))
2123
+ if (Node8.isStringLiteral(node)) return "string";
2124
+ if (Node8.isTemplateExpression(node) || Node8.isNoSubstitutionTemplateLiteral(node))
2520
2125
  return "string";
2521
- if (Node7.isNumericLiteral(node)) return "number";
2522
- if (node.getKind() === SyntaxKind3.TrueKeyword || node.getKind() === SyntaxKind3.FalseKeyword) {
2126
+ if (Node8.isNumericLiteral(node)) return "number";
2127
+ if (node.getKind() === SyntaxKind4.TrueKeyword || node.getKind() === SyntaxKind4.FalseKeyword) {
2523
2128
  return "boolean";
2524
2129
  }
2525
- if (node.getKind() === SyntaxKind3.NullKeyword) return "null";
2526
- if (Node7.isIdentifier(node) && node.getText() === "undefined") return "undefined";
2527
- if (Node7.isObjectLiteralExpression(node)) {
2130
+ if (node.getKind() === SyntaxKind4.NullKeyword) return "null";
2131
+ if (Node8.isIdentifier(node) && node.getText() === "undefined") return "undefined";
2132
+ if (Node8.isObjectLiteralExpression(node)) {
2528
2133
  const props = node.getProperties();
2529
2134
  if (props.length === 0) return "Record<string, unknown>";
2530
2135
  const entries = [];
2531
2136
  for (const prop of props) {
2532
- if (!Node7.isPropertyAssignment(prop)) continue;
2137
+ if (!Node8.isPropertyAssignment(prop)) continue;
2533
2138
  const key = prop.getName();
2534
2139
  const init = prop.getInitializer();
2535
2140
  if (!init) continue;
@@ -2538,23 +2143,23 @@ function inferExpressionType(node) {
2538
2143
  if (entries.length === 0) return "Record<string, unknown>";
2539
2144
  return `{ ${entries.join("; ")} }`;
2540
2145
  }
2541
- if (Node7.isArrayLiteralExpression(node)) {
2146
+ if (Node8.isArrayLiteralExpression(node)) {
2542
2147
  const elements = node.getElements();
2543
2148
  if (elements.length === 0) return "Array<unknown>";
2544
2149
  const first = elements[0];
2545
2150
  if (first) return `Array<${inferExpressionType(first)}>`;
2546
2151
  return "Array<unknown>";
2547
2152
  }
2548
- if (Node7.isConditionalExpression(node)) {
2153
+ if (Node8.isConditionalExpression(node)) {
2549
2154
  const whenTrue = inferExpressionType(node.getWhenTrue());
2550
2155
  const whenFalse = inferExpressionType(node.getWhenFalse());
2551
2156
  if (whenTrue === whenFalse) return whenTrue;
2552
2157
  return `${whenTrue} | ${whenFalse}`;
2553
2158
  }
2554
- if (Node7.isParenthesizedExpression(node)) {
2159
+ if (Node8.isParenthesizedExpression(node)) {
2555
2160
  return inferExpressionType(node.getExpression());
2556
2161
  }
2557
- if (Node7.isAsExpression(node)) {
2162
+ if (Node8.isAsExpression(node)) {
2558
2163
  const typeNode = node.getTypeNode();
2559
2164
  if (typeNode) return typeNode.getText();
2560
2165
  }
@@ -2563,25 +2168,14 @@ function inferExpressionType(node) {
2563
2168
 
2564
2169
  // src/emit/emit-api.ts
2565
2170
  import { mkdir, writeFile } from "fs/promises";
2566
- import { isAbsolute as isAbsolute2, join as join4, relative as relative3 } from "path";
2171
+ import { isAbsolute as isAbsolute2, join as join5, relative as relative3 } from "path";
2567
2172
 
2568
2173
  // src/extension/registry.ts
2569
- import { Project as Project2 } from "ts-morph";
2174
+ import { Project as Project3 } from "ts-morph";
2570
2175
  function resolveApiSlots(extensions) {
2571
- let transport;
2572
- let transportOwner;
2573
2176
  let layer;
2574
2177
  let layerOwner;
2575
2178
  for (const ext of extensions) {
2576
- if (ext.apiTransport) {
2577
- if (transport) {
2578
- throw new CodegenError(
2579
- `api transport claimed by both "${transportOwner}" and "${ext.name}" \u2014 only one extension may set apiTransport.`
2580
- );
2581
- }
2582
- transport = ext.apiTransport;
2583
- transportOwner = ext.name;
2584
- }
2585
2179
  if (ext.apiClientLayer) {
2586
2180
  if (layer) {
2587
2181
  throw new CodegenError(
@@ -2593,11 +2187,22 @@ function resolveApiSlots(extensions) {
2593
2187
  }
2594
2188
  }
2595
2189
  return {
2596
- ...transport ? { transport } : {},
2597
2190
  ...layer ? { layer } : {}
2598
2191
  };
2599
2192
  }
2600
2193
  var CORE_FILES = /* @__PURE__ */ new Set(["routes.ts", "api.ts", "forms.ts", "index.d.ts", "pages.d.ts"]);
2194
+ function mergeExclusive(target, incoming, {
2195
+ owner,
2196
+ describe
2197
+ }) {
2198
+ for (const [key, value] of incoming) {
2199
+ const prev = target.get(key);
2200
+ if (prev !== void 0) {
2201
+ throw new CodegenError(describe(key, prev.owner, owner));
2202
+ }
2203
+ target.set(key, { value, owner });
2204
+ }
2205
+ }
2601
2206
  function createExtensionContext(config, getRoutes) {
2602
2207
  let project;
2603
2208
  return {
@@ -2609,7 +2214,7 @@ function createExtensionContext(config, getRoutes) {
2609
2214
  },
2610
2215
  project() {
2611
2216
  if (!project) {
2612
- project = new Project2({
2217
+ project = new Project3({
2613
2218
  skipAddingFilesFromTsConfig: true,
2614
2219
  skipLoadingLibFiles: true,
2615
2220
  skipFileDependencyResolution: true,
@@ -2642,29 +2247,36 @@ async function collectEmittedFiles(extensions, ctx) {
2642
2247
  `Extension "${ext.name}" tried to emit the core-owned file "${file.path}". Core files (${[...CORE_FILES].join(", ")}) cannot be produced by extensions.`
2643
2248
  );
2644
2249
  }
2645
- const prev = owners.get(key);
2646
- if (prev !== void 0) {
2647
- throw new CodegenError(
2648
- `Output file "${file.path}" is emitted by both "${prev}" and "${ext.name}". Two extensions cannot write the same file.`
2649
- );
2650
- }
2651
- owners.set(key, ext.name);
2250
+ mergeExclusive(owners, [[key, file]], {
2251
+ owner: ext.name,
2252
+ describe: (_key, prevOwner, owner) => `Output file "${file.path}" is emitted by both "${prevOwner}" and "${owner}". Two extensions cannot write the same file.`
2253
+ });
2652
2254
  files.push(file);
2653
2255
  }
2654
2256
  }
2655
2257
  return files;
2656
2258
  }
2657
2259
 
2260
+ // src/extension/types.ts
2261
+ function requestShape(route) {
2262
+ const cs = route.contract?.contractSource;
2263
+ const isGet = route.method.toUpperCase() === "GET";
2264
+ const isQuery = isGet || !!cs?.filterFields?.length;
2265
+ const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
2266
+ const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
2267
+ return { isGet, isQuery, hasBody, hasQuery };
2268
+ }
2269
+
2658
2270
  // src/emit/emit-api.ts
2659
2271
  async function emitApi(routes, outDir, opts = {}) {
2660
2272
  await mkdir(outDir, { recursive: true });
2661
2273
  const content = buildApiFile(routes, outDir, opts);
2662
- await writeFile(join4(outDir, "api.ts"), content, "utf8");
2274
+ await writeFile(join5(outDir, "api.ts"), content, "utf8");
2663
2275
  }
2664
2276
  function splitName(name) {
2665
2277
  return name.split(".");
2666
2278
  }
2667
- function toObjectKey3(segment) {
2279
+ function toObjectKey(segment) {
2668
2280
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
2669
2281
  return segment;
2670
2282
  }
@@ -2771,7 +2383,7 @@ function emitRouterTypeBlock(tree, indent, outDir) {
2771
2383
  const pad = " ".repeat(indent);
2772
2384
  const lines = [];
2773
2385
  for (const [key, node] of tree) {
2774
- const objKey = toObjectKey3(key);
2386
+ const objKey = toObjectKey(key);
2775
2387
  if (node.kind === "leaf") {
2776
2388
  const c = node;
2777
2389
  const method = c.method.toUpperCase();
@@ -2797,15 +2409,12 @@ function emitRouterTypeBlock(tree, indent, outDir) {
2797
2409
  return lines;
2798
2410
  }
2799
2411
  function buildRequestModel(c) {
2800
- const isGet = c.method.toUpperCase() === "GET";
2801
2412
  const m = c.method.toLowerCase();
2802
2413
  const flat = JSON.stringify(c.name);
2803
2414
  const path = JSON.stringify(c.path);
2804
2415
  const TA = buildRouterTypeAccess(c.name);
2805
2416
  const withParams = hasPathParams(c.params);
2806
- const hasBody = !!c.contractSource.bodyRef || c.contractSource.body != null && c.contractSource.body !== "never";
2807
- const isQuery = isGet || !!c.contractSource.filterFields?.length;
2808
- const hasQuery = isGet || !!c.contractSource.queryRef || c.contractSource.query != null && c.contractSource.query !== "never";
2417
+ const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
2809
2418
  const fields = [];
2810
2419
  if (withParams) fields.push(`params: ${TA}['params']`);
2811
2420
  if (hasQuery) fields.push(`query?: ${TA}['query']`);
@@ -2827,7 +2436,6 @@ function buildRequestModel(c) {
2827
2436
  urlExpr,
2828
2437
  optsExpr,
2829
2438
  responseType: `${TA}['response']`,
2830
- bodyType: `${TA}['body']`,
2831
2439
  queryKeyExpr: `[${flat}, input] as const`
2832
2440
  };
2833
2441
  }
@@ -2875,7 +2483,7 @@ function emitApiObjectBlock(tree, indent, p) {
2875
2483
  const pad = " ".repeat(indent);
2876
2484
  const lines = [];
2877
2485
  for (const [key, node] of tree) {
2878
- const objKey = toObjectKey3(key);
2486
+ const objKey = toObjectKey(key);
2879
2487
  if (node.kind === "branch") {
2880
2488
  lines.push(`${pad}${objKey}: {`);
2881
2489
  lines.push(...emitApiObjectBlock(node.children, indent + 2, p));
@@ -2883,30 +2491,28 @@ function emitApiObjectBlock(tree, indent, p) {
2883
2491
  continue;
2884
2492
  }
2885
2493
  const req = buildRequestModel(node);
2886
- const route = {
2887
- method: node.method,
2888
- path: node.path,
2889
- name: node.name,
2890
- params: node.params,
2891
- contract: { contractSource: node.contractSource },
2892
- ...node.controllerRef ? { controllerRef: node.controllerRef } : {}
2494
+ const leaf = {
2495
+ route: node.route,
2496
+ request: req,
2497
+ requestExpr: renderFetcherRequest(req)
2893
2498
  };
2894
- const leaf = { route, request: req, requestExpr: "" };
2895
- leaf.requestExpr = p.transport ? p.transport.renderRequest(leaf, p.ctx) : renderFetcherRequest(req);
2896
- const members = {};
2897
- if (p.layer) Object.assign(members, p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx));
2499
+ const owned = /* @__PURE__ */ new Map();
2500
+ if (p.layer) {
2501
+ mergeExclusive(owned, Object.entries(p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx)), {
2502
+ owner: p.layer.name,
2503
+ describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
2504
+ });
2505
+ }
2898
2506
  for (const ext of p.memberExts) {
2899
2507
  const extra = ext.apiMembers?.(leaf, p.ctx);
2900
2508
  if (!extra) continue;
2901
- for (const [name, value] of Object.entries(extra)) {
2902
- if (name in members) {
2903
- throw new Error(
2904
- `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict at "${ext.name}").`
2905
- );
2906
- }
2907
- members[name] = value;
2908
- }
2509
+ mergeExclusive(owned, Object.entries(extra), {
2510
+ owner: ext.name,
2511
+ describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
2512
+ });
2909
2513
  }
2514
+ const members = {};
2515
+ for (const [name, { value }] of owned) members[name] = value;
2910
2516
  lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members));
2911
2517
  }
2912
2518
  return lines;
@@ -2915,10 +2521,82 @@ function buildRouterTypeAccess(name) {
2915
2521
  const segments = splitName(name);
2916
2522
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
2917
2523
  }
2524
+ var RESOLVER_HELPERS = [
2525
+ // --- Recursive helper type _RouterAt: walks nested ApiRouter by dot-path ---
2526
+ "type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`",
2527
+ " ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never",
2528
+ " : P extends keyof R ? R[P] : never;",
2529
+ "",
2530
+ // --- ResolveByName: resolve a field from a dot-path name ---
2531
+ "type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;",
2532
+ "",
2533
+ // --- ResolveByPath: scan all leaves for matching method + url ---
2534
+ // Flattens ApiRouter recursively and finds the entry whose method === M and url === U.
2535
+ "type _LeafValues<T> = T extends { method: string; url: string }",
2536
+ " ? T",
2537
+ " : T extends object ? _LeafValues<T[keyof T]> : never;",
2538
+ "",
2539
+ "type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L",
2540
+ " ? L extends { method: M; url: U }",
2541
+ " ? Field extends keyof L ? L[Field] : never",
2542
+ " : never",
2543
+ " : never;",
2544
+ ""
2545
+ ];
2546
+ var ROUTE_NAMESPACE = [
2547
+ "export namespace Route {",
2548
+ ' export type Response<K extends string> = ResolveByName<K, "response">;',
2549
+ ' export type Body<K extends string> = ResolveByName<K, "body">;',
2550
+ ' export type Query<K extends string> = ResolveByName<K, "query">;',
2551
+ ' export type Params<K extends string> = ResolveByName<K, "params">;',
2552
+ ' export type Error<K extends string> = ResolveByName<K, "error">;',
2553
+ ' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
2554
+ " export type Request<K extends string> = {",
2555
+ " body: Body<K>;",
2556
+ " query: Query<K>;",
2557
+ " params: Params<K>;",
2558
+ " };",
2559
+ "}",
2560
+ ""
2561
+ ];
2562
+ var PATH_NAMESPACE = [
2563
+ "export namespace Path {",
2564
+ ' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
2565
+ ' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;',
2566
+ ' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;',
2567
+ ' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
2568
+ ' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
2569
+ ' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
2570
+ "}",
2571
+ ""
2572
+ ];
2573
+ var EMPTY_ROUTE_NAMESPACE = [
2574
+ "export namespace Route {",
2575
+ " export type Response<K extends string> = never;",
2576
+ " export type Body<K extends string> = never;",
2577
+ " export type Query<K extends string> = never;",
2578
+ " export type Params<K extends string> = never;",
2579
+ " export type Error<K extends string> = never;",
2580
+ " export type FilterFields<K extends string> = never;",
2581
+ " export type Request<K extends string> = { body: never; query: never; params: never };",
2582
+ "}",
2583
+ ""
2584
+ ];
2585
+ var EMPTY_PATH_NAMESPACE = [
2586
+ "export namespace Path {",
2587
+ " export type Response<M extends string, U extends string> = never;",
2588
+ " export type Body<M extends string, U extends string> = never;",
2589
+ " export type Query<M extends string, U extends string> = never;",
2590
+ " export type Params<M extends string, U extends string> = never;",
2591
+ " export type Error<M extends string, U extends string> = never;",
2592
+ " export type FilterFields<M extends string, U extends string> = never;",
2593
+ "}",
2594
+ ""
2595
+ ];
2918
2596
  function buildApiFile(routes, outDir, opts = {}) {
2919
2597
  const fetcherImportPath = opts.fetcherImportPath;
2920
2598
  const extensions = opts.extensions ?? [];
2921
- const { transport, layer } = resolveApiSlots(extensions);
2599
+ const { layer } = resolveApiSlots(extensions);
2922
2600
  const memberExts = extensions.filter((e) => e.apiMembers);
2923
2601
  const headerExts = extensions.filter((e) => e.apiHeader);
2924
2602
  const contracted = routes.filter((r) => r.contract);
@@ -2963,7 +2641,6 @@ function buildApiFile(routes, outDir, opts = {}) {
2963
2641
  seenImports.add(imp);
2964
2642
  extImports.push(imp);
2965
2643
  };
2966
- for (const imp of transport?.imports?.(ctx) ?? []) pushImport(imp);
2967
2644
  for (const imp of layer?.imports?.(ctx) ?? []) pushImport(imp);
2968
2645
  for (const ext of headerExts) {
2969
2646
  for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
@@ -3008,27 +2685,8 @@ function buildApiFile(routes, outDir, opts = {}) {
3008
2685
  lines.push("}");
3009
2686
  lines.push("export type Api = ReturnType<typeof createApi>;");
3010
2687
  lines.push("");
3011
- lines.push("export namespace Route {");
3012
- lines.push(" export type Response<K extends string> = never;");
3013
- lines.push(" export type Body<K extends string> = never;");
3014
- lines.push(" export type Query<K extends string> = never;");
3015
- lines.push(" export type Params<K extends string> = never;");
3016
- lines.push(" export type Error<K extends string> = never;");
3017
- lines.push(" export type FilterFields<K extends string> = never;");
3018
- lines.push(
3019
- " export type Request<K extends string> = { body: never; query: never; params: never };"
3020
- );
3021
- lines.push("}");
3022
- lines.push("");
3023
- lines.push("export namespace Path {");
3024
- lines.push(" export type Response<M extends string, U extends string> = never;");
3025
- lines.push(" export type Body<M extends string, U extends string> = never;");
3026
- lines.push(" export type Query<M extends string, U extends string> = never;");
3027
- lines.push(" export type Params<M extends string, U extends string> = never;");
3028
- lines.push(" export type Error<M extends string, U extends string> = never;");
3029
- lines.push(" export type FilterFields<M extends string, U extends string> = never;");
3030
- lines.push("}");
3031
- lines.push("");
2688
+ lines.push(...EMPTY_ROUTE_NAMESPACE);
2689
+ lines.push(...EMPTY_PATH_NAMESPACE);
3032
2690
  return lines.join("\n");
3033
2691
  }
3034
2692
  const tree = /* @__PURE__ */ new Map();
@@ -3046,7 +2704,8 @@ function buildApiFile(routes, outDir, opts = {}) {
3046
2704
  path: r.path,
3047
2705
  params: r.params,
3048
2706
  controllerRef: r.controllerRef,
3049
- contractSource: c.contractSource
2707
+ contractSource: c.contractSource,
2708
+ route: r
3050
2709
  };
3051
2710
  insertIntoTree(tree, segments, leaf, name);
3052
2711
  }
@@ -3059,7 +2718,6 @@ function buildApiFile(routes, outDir, opts = {}) {
3059
2718
  lines.push(" return {");
3060
2719
  lines.push(
3061
2720
  ...emitApiObjectBlock(tree, 4, {
3062
- ...transport ? { transport } : {},
3063
2721
  ...layer ? { layer } : {},
3064
2722
  memberExts,
3065
2723
  ctx
@@ -3070,61 +2728,9 @@ function buildApiFile(routes, outDir, opts = {}) {
3070
2728
  lines.push("");
3071
2729
  lines.push("export type Api = ReturnType<typeof createApi>;");
3072
2730
  lines.push("");
3073
- lines.push("type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`");
3074
- lines.push(" ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never");
3075
- lines.push(" : P extends keyof R ? R[P] : never;");
3076
- lines.push("");
3077
- lines.push(
3078
- "type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;"
3079
- );
3080
- lines.push("");
3081
- lines.push("type _LeafValues<T> = T extends { method: string; url: string }");
3082
- lines.push(" ? T");
3083
- lines.push(" : T extends object ? _LeafValues<T[keyof T]> : never;");
3084
- lines.push("");
3085
- lines.push(
3086
- "type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L"
3087
- );
3088
- lines.push(" ? L extends { method: M; url: U }");
3089
- lines.push(" ? Field extends keyof L ? L[Field] : never");
3090
- lines.push(" : never");
3091
- lines.push(" : never;");
3092
- lines.push("");
3093
- lines.push("export namespace Route {");
3094
- lines.push(' export type Response<K extends string> = ResolveByName<K, "response">;');
3095
- lines.push(' export type Body<K extends string> = ResolveByName<K, "body">;');
3096
- lines.push(' export type Query<K extends string> = ResolveByName<K, "query">;');
3097
- lines.push(' export type Params<K extends string> = ResolveByName<K, "params">;');
3098
- lines.push(' export type Error<K extends string> = ResolveByName<K, "error">;');
3099
- lines.push(' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;');
3100
- lines.push(" export type Request<K extends string> = {");
3101
- lines.push(" body: Body<K>;");
3102
- lines.push(" query: Query<K>;");
3103
- lines.push(" params: Params<K>;");
3104
- lines.push(" };");
3105
- lines.push("}");
3106
- lines.push("");
3107
- lines.push("export namespace Path {");
3108
- lines.push(
3109
- ' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;'
3110
- );
3111
- lines.push(
3112
- ' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;'
3113
- );
3114
- lines.push(
3115
- ' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;'
3116
- );
3117
- lines.push(
3118
- ' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;'
3119
- );
3120
- lines.push(
3121
- ' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;'
3122
- );
3123
- lines.push(
3124
- ' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;'
3125
- );
3126
- lines.push("}");
3127
- lines.push("");
2731
+ lines.push(...RESOLVER_HELPERS);
2732
+ lines.push(...ROUTE_NAMESPACE);
2733
+ lines.push(...PATH_NAMESPACE);
3128
2734
  for (const ext of headerExts) {
3129
2735
  const statements = ext.apiHeader?.(ctx)?.statements;
3130
2736
  if (statements?.length) {
@@ -3136,7 +2742,7 @@ function buildApiFile(routes, outDir, opts = {}) {
3136
2742
 
3137
2743
  // src/emit/emit-cache.ts
3138
2744
  import { mkdir as mkdir2, stat, writeFile as writeFile2 } from "fs/promises";
3139
- import { join as join5 } from "path";
2745
+ import { join as join6 } from "path";
3140
2746
  async function emitCache(pages, outDir) {
3141
2747
  await mkdir2(outDir, { recursive: true });
3142
2748
  const entries = await Promise.all(
@@ -3150,95 +2756,21 @@ async function emitCache(pages, outDir) {
3150
2756
  })
3151
2757
  );
3152
2758
  const cache = { pages: entries };
3153
- await writeFile2(join5(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
2759
+ await writeFile2(join6(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
3154
2760
  `, "utf8");
3155
2761
  }
3156
2762
 
3157
2763
  // src/emit/emit-forms.ts
3158
2764
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
3159
- import { join as join6, relative as relative4 } from "path";
2765
+ import { join as join7, relative as relative4 } from "path";
3160
2766
  async function emitForms(routes, outDir, config, adapter) {
3161
2767
  if (config && config.enabled === false) return false;
3162
- if (adapter && adapter.name !== "zod") {
3163
- const content2 = buildFormsFileWithAdapter(routes, adapter);
3164
- if (content2 === null) return false;
3165
- await mkdir3(outDir, { recursive: true });
3166
- await writeFile3(join6(outDir, "forms.ts"), content2, "utf8");
3167
- return true;
3168
- }
3169
- const entries = collectFormEntries(routes);
3170
- if (entries.length === 0) return false;
2768
+ const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
2769
+ if (content === null) return false;
3171
2770
  await mkdir3(outDir, { recursive: true });
3172
- const content = buildFormsFile(entries, outDir, config);
3173
- await writeFile3(join6(outDir, "forms.ts"), content, "utf8");
2771
+ await writeFile3(join7(outDir, "forms.ts"), content, "utf8");
3174
2772
  return true;
3175
2773
  }
3176
- function buildFormsFileWithAdapter(routes, adapter) {
3177
- const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
3178
- const methodNameCounts = /* @__PURE__ */ new Map();
3179
- for (const route of sorted) {
3180
- const cs = route.contract.contractSource;
3181
- if (!cs.bodySchema && !cs.querySchema) continue;
3182
- methodNameCounts.set(
3183
- deriveBaseName(route.name).method,
3184
- (methodNameCounts.get(deriveBaseName(route.name).method) ?? 0) + 1
3185
- );
3186
- }
3187
- const named = /* @__PURE__ */ new Map();
3188
- const decls = [];
3189
- const mapEntries = [];
3190
- let used = false;
3191
- for (const route of sorted) {
3192
- const cs = route.contract.contractSource;
3193
- const { method, full } = deriveBaseName(route.name);
3194
- const base = (methodNameCounts.get(method) ?? 0) > 1 ? full : method;
3195
- const block = [];
3196
- if (cs.bodyZodText && !cs.bodySchema) {
3197
- block.push(
3198
- `// warning: ${route.name} body is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
3199
- );
3200
- }
3201
- let bodyConst;
3202
- if (cs.bodySchema) {
3203
- used = true;
3204
- const r = adapter.renderModule(cs.bodySchema);
3205
- for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
3206
- bodyConst = `${base}BodySchema`;
3207
- block.push(`export const ${bodyConst} = ${r.schemaText};`);
3208
- block.push(`export type ${base}Body = ${adapter.inferType(bodyConst)};`);
3209
- }
3210
- if (cs.querySchema) {
3211
- used = true;
3212
- const r = adapter.renderModule(cs.querySchema);
3213
- for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
3214
- const queryConst = `${base}QuerySchema`;
3215
- block.push(`export const ${queryConst} = ${r.schemaText};`);
3216
- block.push(`export type ${base}Query = ${adapter.inferType(queryConst)};`);
3217
- }
3218
- if (block.length === 0) continue;
3219
- decls.push(`// ${route.name}`, ...block, "");
3220
- if (bodyConst) mapEntries.push(` ${JSON.stringify(route.name)}: ${bodyConst},`);
3221
- }
3222
- if (!used) return null;
3223
- const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
3224
- for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
3225
- lines.push("");
3226
- if (named.size > 0) {
3227
- lines.push("// Hoisted nested schemas (shared across endpoints).");
3228
- for (const [n, t] of named) lines.push(`const ${n} = ${t};`);
3229
- lines.push("");
3230
- }
3231
- lines.push(...decls);
3232
- lines.push("/** Route name \u2192 body schema map. */");
3233
- lines.push("export const formSchemas = {");
3234
- lines.push(...mapEntries);
3235
- lines.push("} as const;");
3236
- lines.push("");
3237
- return lines.join("\n");
3238
- }
3239
- function hasSchema(src) {
3240
- return !!src && (src.ref !== null || src.text !== null);
3241
- }
3242
2774
  function pascal(segment) {
3243
2775
  return segment.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
3244
2776
  }
@@ -3248,37 +2780,6 @@ function deriveBaseName(routeName) {
3248
2780
  const full = segments.map(pascal).join("");
3249
2781
  return { method, full };
3250
2782
  }
3251
- function collectFormEntries(routes) {
3252
- const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
3253
- const methodNameCounts = /* @__PURE__ */ new Map();
3254
- const candidates = [];
3255
- for (const route of sorted) {
3256
- const cs = route.contract.contractSource;
3257
- const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
3258
- const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
3259
- if (!hasSchema(body) && !hasSchema(query)) continue;
3260
- const { method, full } = deriveBaseName(route.name);
3261
- methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
3262
- candidates.push({ route, method, full });
3263
- }
3264
- const entries = [];
3265
- for (const { route, method, full } of candidates) {
3266
- const cs = route.contract.contractSource;
3267
- const collision = (methodNameCounts.get(method) ?? 0) > 1;
3268
- const baseName = collision ? full : method;
3269
- const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
3270
- const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
3271
- entries.push({
3272
- routeName: route.name,
3273
- baseName,
3274
- body: hasSchema(body) ? body : void 0,
3275
- query: hasSchema(query) ? query : void 0,
3276
- nestedSchemas: cs.formNestedSchemas ?? null,
3277
- warnings: cs.formWarnings ?? []
3278
- });
3279
- }
3280
- return entries;
3281
- }
3282
2783
  function relImport(outDir, filePath) {
3283
2784
  let relPath = relative4(outDir, filePath).replace(/\.ts$/, "");
3284
2785
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
@@ -3287,85 +2788,8 @@ function relImport(outDir, filePath) {
3287
2788
  function refRootIdentifier(refName) {
3288
2789
  return refName.split(".")[0] ?? refName;
3289
2790
  }
3290
- function buildFormsFile(entries, outDir, config) {
3291
- const zodImport = config?.zodImport ?? "zod";
3292
- const lines = [
3293
- "// Generated by @dudousxd/nestjs-codegen. Do not edit.",
3294
- `import { z } from '${zodImport}';`
3295
- ];
3296
- const importsByFile = /* @__PURE__ */ new Map();
3297
- const refAlias = /* @__PURE__ */ new Map();
3298
- for (const entry of entries) {
3299
- for (const src of [entry.body, entry.query]) {
3300
- if (src?.ref && !src.text) {
3301
- const root = refRootIdentifier(src.ref.name);
3302
- const set = importsByFile.get(src.ref.filePath) ?? /* @__PURE__ */ new Set();
3303
- set.add(root);
3304
- importsByFile.set(src.ref.filePath, set);
3305
- }
3306
- }
3307
- }
3308
- if (importsByFile.size > 0) {
3309
- const emitted = /* @__PURE__ */ new Set();
3310
- for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
3311
- const relPath = relImport(outDir, filePath);
3312
- const specifiers = [];
3313
- for (const root of [...roots].sort()) {
3314
- if (emitted.has(root)) {
3315
- const alias = `${root}_${emitted.size}`;
3316
- specifiers.push(`${root} as ${alias}`);
3317
- emitted.add(alias);
3318
- refAlias.set(`${filePath}\0${root}`, alias);
3319
- } else {
3320
- specifiers.push(root);
3321
- emitted.add(root);
3322
- refAlias.set(`${filePath}\0${root}`, root);
3323
- }
3324
- }
3325
- lines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
3326
- }
3327
- }
3328
- lines.push("");
3329
- const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
3330
- if (globalSchemas.size > 0) {
3331
- lines.push("// Hoisted nested schemas (shared across endpoints).");
3332
- for (const [name, text] of globalSchemas) {
3333
- lines.push(`const ${name} = ${text};`);
3334
- }
3335
- lines.push("");
3336
- }
3337
- const mapEntries = [];
3338
- for (const entry of entries) {
3339
- lines.push(`// ${entry.routeName}`);
3340
- if (entry.warnings && entry.warnings.length > 0) {
3341
- for (const w of entry.warnings) {
3342
- lines.push(`// warning: ${w}`);
3343
- }
3344
- }
3345
- const rename = renamesByEntry.get(entry) ?? null;
3346
- if (entry.body) {
3347
- const schemaName = `${entry.baseName}BodySchema`;
3348
- const typeName = `${entry.baseName}Body`;
3349
- const text = applyRenames(renderSchema(entry.body, outDir, refAlias), rename);
3350
- lines.push(`export const ${schemaName} = ${text};`);
3351
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
3352
- mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${schemaName},`);
3353
- }
3354
- if (entry.query) {
3355
- const schemaName = `${entry.baseName}QuerySchema`;
3356
- const typeName = `${entry.baseName}Query`;
3357
- const text = applyRenames(renderSchema(entry.query, outDir, refAlias), rename);
3358
- lines.push(`export const ${schemaName} = ${text};`);
3359
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
3360
- }
3361
- lines.push("");
3362
- }
3363
- lines.push("/** Route name \u2192 body schema map. */");
3364
- lines.push("export const formSchemas = {");
3365
- lines.push(...mapEntries);
3366
- lines.push("} as const;");
3367
- lines.push("");
3368
- return lines.join("\n");
2791
+ function hasSource(src) {
2792
+ return !!(src.schema || src.zodText || src.zodRef);
3369
2793
  }
3370
2794
  function applyRenames(text, renames) {
3371
2795
  if (!renames || renames.size === 0) return text;
@@ -3431,20 +2855,172 @@ function planNestedSchemas(entries) {
3431
2855
  }
3432
2856
  return { globalSchemas, renamesByEntry };
3433
2857
  }
3434
- function renderSchema(src, outDir, refAlias) {
3435
- if (src.text) return src.text;
3436
- if (src.ref) {
3437
- const root = refRootIdentifier(src.ref.name);
3438
- const alias = refAlias.get(`${src.ref.filePath}\0${root}`) ?? root;
3439
- const member = src.ref.name.slice(root.length);
3440
- return `${alias}${member}`;
2858
+ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
2859
+ const acceptsRawZod = adapter.acceptsRawZodSource === true;
2860
+ const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
2861
+ const methodNameCounts = /* @__PURE__ */ new Map();
2862
+ const candidates = [];
2863
+ for (const route of sorted) {
2864
+ const cs = route.contract.contractSource;
2865
+ const body = {
2866
+ schema: cs.bodySchema ?? null,
2867
+ zodText: cs.bodyZodText ?? null,
2868
+ zodRef: cs.bodyZodRef ?? null
2869
+ };
2870
+ const query = {
2871
+ schema: cs.querySchema ?? null,
2872
+ zodText: cs.queryZodText ?? null,
2873
+ zodRef: cs.queryZodRef ?? null
2874
+ };
2875
+ if (!hasSource(body) && !hasSource(query)) continue;
2876
+ const { method, full } = deriveBaseName(route.name);
2877
+ methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
2878
+ candidates.push({
2879
+ routeName: route.name,
2880
+ baseName: full,
2881
+ // resolved below
2882
+ body: hasSource(body) ? body : void 0,
2883
+ query: hasSource(query) ? query : void 0,
2884
+ nestedSchemas: cs.formNestedSchemas ?? null,
2885
+ warnings: cs.formWarnings ?? []
2886
+ });
2887
+ }
2888
+ const entries = candidates.map((c) => {
2889
+ const { method, full } = deriveBaseName(c.routeName);
2890
+ const collision = (methodNameCounts.get(method) ?? 0) > 1;
2891
+ return { ...c, baseName: collision ? full : method };
2892
+ });
2893
+ if (entries.length === 0) return null;
2894
+ const importsByFile = /* @__PURE__ */ new Map();
2895
+ const refAlias = /* @__PURE__ */ new Map();
2896
+ for (const entry of entries) {
2897
+ for (const src of [entry.body, entry.query]) {
2898
+ if (src?.zodRef && !src.zodText && !src.schema) {
2899
+ const root = refRootIdentifier(src.zodRef.name);
2900
+ const set = importsByFile.get(src.zodRef.filePath) ?? /* @__PURE__ */ new Set();
2901
+ set.add(root);
2902
+ importsByFile.set(src.zodRef.filePath, set);
2903
+ }
2904
+ }
2905
+ }
2906
+ const importLines = [];
2907
+ if (importsByFile.size > 0) {
2908
+ const emitted = /* @__PURE__ */ new Set();
2909
+ for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
2910
+ const relPath = relImport(outDir, filePath);
2911
+ const specifiers = [];
2912
+ for (const root of [...roots].sort()) {
2913
+ if (emitted.has(root)) {
2914
+ const alias = `${root}_${emitted.size}`;
2915
+ specifiers.push(`${root} as ${alias}`);
2916
+ emitted.add(alias);
2917
+ refAlias.set(`${filePath}\0${root}`, alias);
2918
+ } else {
2919
+ specifiers.push(root);
2920
+ emitted.add(root);
2921
+ refAlias.set(`${filePath}\0${root}`, root);
2922
+ }
2923
+ }
2924
+ importLines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
2925
+ }
2926
+ }
2927
+ const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
2928
+ const irNamed = /* @__PURE__ */ new Map();
2929
+ const decls = [];
2930
+ const mapEntries = [];
2931
+ let used = false;
2932
+ const renderSource = (src, rename) => {
2933
+ if (src.schema) {
2934
+ const r = adapter.renderModule(src.schema);
2935
+ for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
2936
+ return { text: r.schemaText };
2937
+ }
2938
+ if (src.zodText) {
2939
+ if (!acceptsRawZod) {
2940
+ return {
2941
+ text: "",
2942
+ warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
2943
+ };
2944
+ }
2945
+ return { text: applyRenames(src.zodText, rename) };
2946
+ }
2947
+ if (src.zodRef) {
2948
+ if (!acceptsRawZod) {
2949
+ return {
2950
+ text: "",
2951
+ warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
2952
+ };
2953
+ }
2954
+ const root = refRootIdentifier(src.zodRef.name);
2955
+ const alias = refAlias.get(`${src.zodRef.filePath}\0${root}`) ?? root;
2956
+ const member = src.zodRef.name.slice(root.length);
2957
+ return { text: `${alias}${member}` };
2958
+ }
2959
+ return null;
2960
+ };
2961
+ for (const entry of entries) {
2962
+ const block = [];
2963
+ const rename = renamesByEntry.get(entry) ?? null;
2964
+ let bodyConst;
2965
+ if (entry.warnings && entry.warnings.length > 0) {
2966
+ for (const w of entry.warnings) block.push(`// warning: ${w}`);
2967
+ }
2968
+ if (entry.body) {
2969
+ const rendered = renderSource(entry.body, rename);
2970
+ if (rendered?.warn) {
2971
+ block.push(`// warning: ${entry.routeName} body ${rendered.warn}`);
2972
+ } else if (rendered) {
2973
+ used = true;
2974
+ bodyConst = `${entry.baseName}BodySchema`;
2975
+ block.push(`export const ${bodyConst} = ${rendered.text};`);
2976
+ block.push(`export type ${entry.baseName}Body = ${adapter.inferType(bodyConst)};`);
2977
+ }
2978
+ }
2979
+ if (entry.query) {
2980
+ const rendered = renderSource(entry.query, rename);
2981
+ if (rendered?.warn) {
2982
+ block.push(`// warning: ${entry.routeName} query ${rendered.warn}`);
2983
+ } else if (rendered) {
2984
+ used = true;
2985
+ const queryConst = `${entry.baseName}QuerySchema`;
2986
+ block.push(`export const ${queryConst} = ${rendered.text};`);
2987
+ block.push(`export type ${entry.baseName}Query = ${adapter.inferType(queryConst)};`);
2988
+ }
2989
+ }
2990
+ if (block.length === 0) continue;
2991
+ decls.push(`// ${entry.routeName}`, ...block, "");
2992
+ if (bodyConst) mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${bodyConst},`);
2993
+ }
2994
+ if (!used) return null;
2995
+ const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
2996
+ if (acceptsRawZod) {
2997
+ const zodImport = config?.zodImport ?? "zod";
2998
+ lines.push(`import { z } from '${zodImport}';`);
2999
+ } else {
3000
+ for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
3001
+ }
3002
+ lines.push(...importLines);
3003
+ lines.push("");
3004
+ const allNested = /* @__PURE__ */ new Map();
3005
+ for (const [n, t] of globalSchemas) allNested.set(n, t);
3006
+ for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
3007
+ if (allNested.size > 0) {
3008
+ lines.push("// Hoisted nested schemas (shared across endpoints).");
3009
+ for (const [n, t] of allNested) lines.push(`const ${n} = ${t};`);
3010
+ lines.push("");
3441
3011
  }
3442
- return "z.unknown()";
3012
+ lines.push(...decls);
3013
+ lines.push("/** Route name \u2192 body schema map. */");
3014
+ lines.push("export const formSchemas = {");
3015
+ lines.push(...mapEntries);
3016
+ lines.push("} as const;");
3017
+ lines.push("");
3018
+ return lines.join("\n");
3443
3019
  }
3444
3020
 
3445
3021
  // src/emit/emit-index.ts
3446
3022
  import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
3447
- import { join as join7 } from "path";
3023
+ import { join as join8 } from "path";
3448
3024
  async function emitIndex(outDir, hasContracts = false, hasForms = false) {
3449
3025
  await mkdir4(outDir, { recursive: true });
3450
3026
  const exports = ["export * from './pages.js';", "export * from './routes.js';"];
@@ -3457,12 +3033,12 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
3457
3033
  const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports, ""].join(
3458
3034
  "\n"
3459
3035
  );
3460
- await writeFile4(join7(outDir, "index.d.ts"), content, "utf8");
3036
+ await writeFile4(join8(outDir, "index.d.ts"), content, "utf8");
3461
3037
  }
3462
3038
 
3463
3039
  // src/emit/emit-pages.ts
3464
3040
  import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
3465
- import { join as join8, relative as relative5 } from "path";
3041
+ import { join as join9, relative as relative5 } from "path";
3466
3042
  async function emitPages(pages, outDir, _options = {}) {
3467
3043
  await mkdir5(outDir, { recursive: true });
3468
3044
  const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
@@ -3483,7 +3059,7 @@ ${augBody}
3483
3059
  }
3484
3060
  ${sharedPropsBlock}}
3485
3061
  `;
3486
- await writeFile5(join8(outDir, "pages.d.ts"), content, "utf8");
3062
+ await writeFile5(join9(outDir, "pages.d.ts"), content, "utf8");
3487
3063
  }
3488
3064
  function buildSharedPropsBlock(sharedProps) {
3489
3065
  if (!sharedProps) return "";
@@ -3514,11 +3090,11 @@ function needsQuotes(name) {
3514
3090
 
3515
3091
  // src/emit/emit-routes.ts
3516
3092
  import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
3517
- import { join as join9 } from "path";
3093
+ import { join as join10 } from "path";
3518
3094
  async function emitRoutes(routes, outDir) {
3519
3095
  await mkdir6(outDir, { recursive: true });
3520
3096
  const content = buildRoutesFile(routes);
3521
- await writeFile6(join9(outDir, "routes.ts"), content, "utf8");
3097
+ await writeFile6(join10(outDir, "routes.ts"), content, "utf8");
3522
3098
  }
3523
3099
  function buildRoutesFile(routes) {
3524
3100
  if (routes.length === 0) {
@@ -3646,30 +3222,7 @@ async function generate(config, inputRoutes = []) {
3646
3222
  propsExport: pagesConfig.propsExport,
3647
3223
  componentNameStrategy: pagesConfig.componentNameStrategy
3648
3224
  });
3649
- let sharedProps = null;
3650
- if (config.app?.moduleEntry) {
3651
- try {
3652
- const tsconfigPath = config.app.tsconfig ?? join10(config.codegen.cwd, "tsconfig.json");
3653
- let project;
3654
- try {
3655
- project = new Project3({
3656
- tsConfigFilePath: tsconfigPath,
3657
- skipAddingFilesFromTsConfig: true,
3658
- skipLoadingLibFiles: true,
3659
- skipFileDependencyResolution: true
3660
- });
3661
- } catch {
3662
- project = new Project3({
3663
- skipAddingFilesFromTsConfig: true,
3664
- skipLoadingLibFiles: true,
3665
- skipFileDependencyResolution: true,
3666
- compilerOptions: { allowJs: true, strict: false }
3667
- });
3668
- }
3669
- sharedProps = discoverSharedProps(project, config.app.moduleEntry);
3670
- } catch {
3671
- }
3672
- }
3225
+ const sharedProps = discoverSharedPropsFromConfig(config);
3673
3226
  await emitPages(pages, config.codegen.outDir, {
3674
3227
  propsExport: pagesConfig.propsExport,
3675
3228
  sharedProps
@@ -3693,7 +3246,7 @@ async function generate(config, inputRoutes = []) {
3693
3246
  if (extensions.length > 0) {
3694
3247
  const extraFiles = await collectEmittedFiles(extensions, ctx);
3695
3248
  for (const file of extraFiles) {
3696
- const dest = join10(config.codegen.outDir, file.path);
3249
+ const dest = join11(config.codegen.outDir, file.path);
3697
3250
  await mkdir7(dirname3(dest), { recursive: true });
3698
3251
  await writeFile7(dest, file.contents, "utf8");
3699
3252
  }
@@ -3703,7 +3256,7 @@ async function generate(config, inputRoutes = []) {
3703
3256
  // src/watch/lock-file.ts
3704
3257
  import { open } from "fs/promises";
3705
3258
  import { mkdir as mkdir8, readFile as readFile2, unlink } from "fs/promises";
3706
- import { join as join11 } from "path";
3259
+ import { join as join12 } from "path";
3707
3260
  var LOCK_FILE = ".watcher.lock";
3708
3261
  function isProcessAlive(pid) {
3709
3262
  try {
@@ -3715,7 +3268,7 @@ function isProcessAlive(pid) {
3715
3268
  }
3716
3269
  async function acquireLock(outDir) {
3717
3270
  await mkdir8(outDir, { recursive: true });
3718
- const lockPath = join11(outDir, LOCK_FILE);
3271
+ const lockPath = join12(outDir, LOCK_FILE);
3719
3272
  const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
3720
3273
  try {
3721
3274
  const fd = await open(lockPath, "wx");
@@ -3755,7 +3308,7 @@ async function watch(config, onChange) {
3755
3308
  if (lock === null) {
3756
3309
  let holderPid = "unknown";
3757
3310
  try {
3758
- const raw = await readFile3(join12(config.codegen.outDir, ".watcher.lock"), "utf8");
3311
+ const raw = await readFile3(join13(config.codegen.outDir, ".watcher.lock"), "utf8");
3759
3312
  const data = JSON.parse(raw);
3760
3313
  if (data.pid !== void 0) holderPid = String(data.pid);
3761
3314
  } catch {
@@ -3783,7 +3336,7 @@ async function watch(config, onChange) {
3783
3336
  }
3784
3337
  let pagesDebounceTimer;
3785
3338
  const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
3786
- const pagesWatcher = chokidar.watch(join12(config.codegen.cwd, pagesGlob), {
3339
+ const pagesWatcher = chokidar.watch(join13(config.codegen.cwd, pagesGlob), {
3787
3340
  ignoreInitial: true,
3788
3341
  persistent: true,
3789
3342
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -3809,7 +3362,7 @@ async function watch(config, onChange) {
3809
3362
  pagesWatcher.on("change", schedulePagesRegenerate);
3810
3363
  pagesWatcher.on("unlink", schedulePagesRegenerate);
3811
3364
  let contractsDebounceTimer;
3812
- const contractsWatcher = chokidar.watch(join12(config.codegen.cwd, config.contracts.glob), {
3365
+ const contractsWatcher = chokidar.watch(join13(config.codegen.cwd, config.contracts.glob), {
3813
3366
  ignoreInitial: true,
3814
3367
  persistent: true,
3815
3368
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -3839,7 +3392,7 @@ async function watch(config, onChange) {
3839
3392
  contractsWatcher.on("add", scheduleContractsRegenerate);
3840
3393
  contractsWatcher.on("change", scheduleContractsRegenerate);
3841
3394
  contractsWatcher.on("unlink", scheduleContractsRegenerate);
3842
- const formsWatcher = chokidar.watch(join12(config.codegen.cwd, config.forms.watch), {
3395
+ const formsWatcher = chokidar.watch(join13(config.codegen.cwd, config.forms.watch), {
3843
3396
  ignoreInitial: true,
3844
3397
  persistent: true,
3845
3398
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }