@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.
package/dist/index.js CHANGED
@@ -22,112 +22,9 @@ var CodegenError = class extends Error {
22
22
  }
23
23
  };
24
24
 
25
- // src/adapters/zod.ts
26
- function toObjectKey(name) {
27
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
28
- }
29
- function messageSuffix(messageRaw2) {
30
- return messageRaw2 ? `{ message: ${messageRaw2} }` : "";
31
- }
32
- function renderStringChecks(checks) {
33
- let out = "";
34
- for (const c of checks) {
35
- switch (c.check) {
36
- case "email":
37
- out += `.email(${messageSuffix(c.messageRaw)})`;
38
- break;
39
- case "url":
40
- out += `.url(${messageSuffix(c.messageRaw)})`;
41
- break;
42
- case "uuid":
43
- out += `.uuid(${messageSuffix(c.messageRaw)})`;
44
- break;
45
- case "regex":
46
- out += `.regex(${c.pattern})`;
47
- break;
48
- case "min":
49
- out += `.min(${c.value})`;
50
- break;
51
- case "max":
52
- out += `.max(${c.value})`;
53
- break;
54
- }
55
- }
56
- return out;
57
- }
58
- function render(node, ctx) {
59
- switch (node.kind) {
60
- case "string":
61
- return `z.string()${renderStringChecks(node.checks)}`;
62
- case "number": {
63
- let out = "z.number()";
64
- for (const c of node.checks) {
65
- if (c.check === "int") out += ".int()";
66
- else if (c.check === "positive") out += ".positive()";
67
- else if (c.check === "negative") out += ".negative()";
68
- else if (c.check === "min") out += `.min(${c.value})`;
69
- else if (c.check === "max") out += `.max(${c.value})`;
70
- }
71
- return out;
72
- }
73
- case "boolean":
74
- return "z.boolean()";
75
- case "date":
76
- return "z.coerce.date()";
77
- case "unknown":
78
- return node.note ? `z.unknown() /* ${node.note} */` : "z.unknown()";
79
- case "instanceof":
80
- return `z.instanceof(${node.ctor})`;
81
- case "enum":
82
- return `z.enum([${node.literals.join(", ")}])`;
83
- case "literal":
84
- return `z.literal(${node.raw})`;
85
- case "union":
86
- return `z.union([${node.options.map((o) => render(o, ctx)).join(", ")}])`;
87
- case "object": {
88
- if (node.fields.length === 0) {
89
- return node.passthrough ? "z.object({}).passthrough()" : "z.object({})";
90
- }
91
- const inner = node.fields.map((f) => `${toObjectKey(f.key)}: ${render(f.value, ctx)}`).join(", ");
92
- return `z.object({ ${inner} })${node.passthrough ? ".passthrough()" : ""}`;
93
- }
94
- case "array":
95
- return `z.array(${render(node.element, ctx)})`;
96
- case "optional":
97
- return `${render(node.inner, ctx)}.optional()`;
98
- case "ref":
99
- return node.name;
100
- case "lazyRef":
101
- return `z.lazy(() => ${node.name})`;
102
- case "annotated": {
103
- const comments = node.unmappable.map((n) => `/* @${n}: not translatable to zod (server-only) */`).join(" ");
104
- return `${render(node.inner, ctx)} ${comments}`;
105
- }
106
- }
107
- }
108
- var zodAdapter = {
109
- name: "zod",
110
- importStatements(usage) {
111
- return usage.used ? ["import { z } from 'zod';"] : [];
112
- },
113
- render,
114
- inferType(schemaConst) {
115
- return `z.infer<typeof ${schemaConst}>`;
116
- },
117
- renderModule(mod) {
118
- const ctx = { named: mod.named };
119
- const namedNestedSchemas = /* @__PURE__ */ new Map();
120
- for (const [name, node] of mod.named) {
121
- namedNestedSchemas.set(name, render(node, ctx));
122
- }
123
- return { schemaText: render(mod.root, ctx), namedNestedSchemas, warnings: mod.warnings };
124
- }
125
- };
126
-
127
25
  // src/adapters/registry.ts
128
26
  function resolveAdapter(option) {
129
27
  if (typeof option !== "string") return option;
130
- if (option === "zod") return zodAdapter;
131
28
  const pkg = `@dudousxd/nestjs-codegen-${option}`;
132
29
  const named = `${option}Adapter`;
133
30
  throw new ConfigError(
@@ -180,8 +77,21 @@ If this is intentional, move the file inside your project directory.`
180
77
  function resolveConfig(userConfig, cwd) {
181
78
  return applyDefaults(userConfig, cwd ?? process.cwd());
182
79
  }
80
+ function validateUserConfig(userConfig) {
81
+ if (userConfig.validation == null) {
82
+ throw new ConfigError(
83
+ "validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
84
+ );
85
+ }
86
+ if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
87
+ throw new ConfigError(
88
+ "Config validation failed: `pages.glob` must be a string when `pages` is set"
89
+ );
90
+ }
91
+ }
183
92
  function applyDefaults(userConfig, cwd) {
184
- const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : join(cwd, ".nestjs-inertia");
93
+ validateUserConfig(userConfig);
94
+ const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : join(cwd, ".nestjs-codegen");
185
95
  const resolvedCwd = userConfig.codegen?.cwd ? resolveAbsolute(cwd, userConfig.codegen.cwd) : cwd;
186
96
  let app = null;
187
97
  if (userConfig.app) {
@@ -199,7 +109,8 @@ function applyDefaults(userConfig, cwd) {
199
109
  }
200
110
  return {
201
111
  extensions: userConfig.extensions ?? [],
202
- validation: resolveAdapter(userConfig.validation ?? "zod"),
112
+ // Non-null: validateUserConfig() above throws when `validation` is absent.
113
+ validation: resolveAdapter(userConfig.validation),
203
114
  pages: userConfig.pages ? {
204
115
  glob: userConfig.pages.glob,
205
116
  propsExport: userConfig.pages.propsExport ?? "ComponentProps",
@@ -253,18 +164,12 @@ Run \`nestjs-codegen init\` to create a starter config.`
253
164
  `Config file must have a default export. Did you forget \`export default defineConfig({...})\`? (${configPath})`
254
165
  );
255
166
  }
256
- if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
257
- throw new ConfigError(
258
- `Config validation failed: \`pages.glob\` must be a string when \`pages\` is set (${configPath})`
259
- );
260
- }
261
167
  return applyDefaults(userConfig, resolvedCwd);
262
168
  }
263
169
 
264
170
  // src/generate.ts
265
171
  import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
266
- import { dirname as dirname2, join as join9 } from "path";
267
- import { Project as Project2 } from "ts-morph";
172
+ import { dirname as dirname2, join as join10 } from "path";
268
173
 
269
174
  // src/discovery/pages.ts
270
175
  import { readFile } from "fs/promises";
@@ -319,7 +224,8 @@ function extractPropsSource(source, exportName) {
319
224
  }
320
225
 
321
226
  // src/discovery/shared-props.ts
322
- import { Node, SyntaxKind } from "ts-morph";
227
+ import { join as join3 } from "path";
228
+ import { Node, Project, SyntaxKind } from "ts-morph";
323
229
  function discoverSharedProps(project, moduleEntry) {
324
230
  try {
325
231
  let sourceFile = project.getSourceFile(moduleEntry);
@@ -339,6 +245,31 @@ function discoverSharedProps(project, moduleEntry) {
339
245
  return null;
340
246
  }
341
247
  }
248
+ function discoverSharedPropsFromConfig(config) {
249
+ if (!config.app?.moduleEntry) return null;
250
+ try {
251
+ const tsconfigPath = config.app.tsconfig ?? join3(config.codegen.cwd, "tsconfig.json");
252
+ let project;
253
+ try {
254
+ project = new Project({
255
+ tsConfigFilePath: tsconfigPath,
256
+ skipAddingFilesFromTsConfig: true,
257
+ skipLoadingLibFiles: true,
258
+ skipFileDependencyResolution: true
259
+ });
260
+ } catch {
261
+ project = new Project({
262
+ skipAddingFilesFromTsConfig: true,
263
+ skipLoadingLibFiles: true,
264
+ skipFileDependencyResolution: true,
265
+ compilerOptions: { allowJs: true, strict: false }
266
+ });
267
+ }
268
+ return discoverSharedProps(project, config.app.moduleEntry);
269
+ } catch {
270
+ return null;
271
+ }
272
+ }
342
273
  function findForRootCall(sourceFile) {
343
274
  const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
344
275
  for (const call of callExpressions) {
@@ -358,9 +289,9 @@ function findForRootCall(sourceFile) {
358
289
  function findShareInitializer(forRootCall) {
359
290
  if (!Node.isCallExpression(forRootCall)) return null;
360
291
  const args = forRootCall.getArguments();
361
- const firstArg3 = args[0];
362
- if (!firstArg3 || !Node.isObjectLiteralExpression(firstArg3)) return null;
363
- for (const prop of firstArg3.getProperties()) {
292
+ const firstArg2 = args[0];
293
+ if (!firstArg2 || !Node.isObjectLiteralExpression(firstArg2)) return null;
294
+ for (const prop of firstArg2.getProperties()) {
364
295
  if (Node.isPropertyAssignment(prop) && prop.getName() === "share") {
365
296
  return prop.getInitializer() ?? null;
366
297
  }
@@ -459,9 +390,9 @@ function extractFromReturnTypeAnnotation(typeNode) {
459
390
  const typeName = typeNode.getTypeName();
460
391
  if (Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
461
392
  const typeArgs = typeNode.getTypeArguments();
462
- const firstArg3 = typeArgs[0];
463
- if (firstArg3) {
464
- return extractFromReturnTypeAnnotation(firstArg3);
393
+ const firstArg2 = typeArgs[0];
394
+ if (firstArg2) {
395
+ return extractFromReturnTypeAnnotation(firstArg2);
465
396
  }
466
397
  return null;
467
398
  }
@@ -567,25 +498,14 @@ function inferExpressionType(node) {
567
498
 
568
499
  // src/emit/emit-api.ts
569
500
  import { mkdir, writeFile } from "fs/promises";
570
- import { isAbsolute as isAbsolute2, join as join3, relative as relative3 } from "path";
501
+ import { isAbsolute as isAbsolute2, join as join4, relative as relative3 } from "path";
571
502
 
572
503
  // src/extension/registry.ts
573
- import { Project } from "ts-morph";
504
+ import { Project as Project2 } from "ts-morph";
574
505
  function resolveApiSlots(extensions) {
575
- let transport;
576
- let transportOwner;
577
506
  let layer;
578
507
  let layerOwner;
579
508
  for (const ext of extensions) {
580
- if (ext.apiTransport) {
581
- if (transport) {
582
- throw new CodegenError(
583
- `api transport claimed by both "${transportOwner}" and "${ext.name}" \u2014 only one extension may set apiTransport.`
584
- );
585
- }
586
- transport = ext.apiTransport;
587
- transportOwner = ext.name;
588
- }
589
509
  if (ext.apiClientLayer) {
590
510
  if (layer) {
591
511
  throw new CodegenError(
@@ -597,11 +517,22 @@ function resolveApiSlots(extensions) {
597
517
  }
598
518
  }
599
519
  return {
600
- ...transport ? { transport } : {},
601
520
  ...layer ? { layer } : {}
602
521
  };
603
522
  }
604
523
  var CORE_FILES = /* @__PURE__ */ new Set(["routes.ts", "api.ts", "forms.ts", "index.d.ts", "pages.d.ts"]);
524
+ function mergeExclusive(target, incoming, {
525
+ owner,
526
+ describe
527
+ }) {
528
+ for (const [key, value] of incoming) {
529
+ const prev = target.get(key);
530
+ if (prev !== void 0) {
531
+ throw new CodegenError(describe(key, prev.owner, owner));
532
+ }
533
+ target.set(key, { value, owner });
534
+ }
535
+ }
605
536
  function createExtensionContext(config, getRoutes) {
606
537
  let project;
607
538
  return {
@@ -613,7 +544,7 @@ function createExtensionContext(config, getRoutes) {
613
544
  },
614
545
  project() {
615
546
  if (!project) {
616
- project = new Project({
547
+ project = new Project2({
617
548
  skipAddingFilesFromTsConfig: true,
618
549
  skipLoadingLibFiles: true,
619
550
  skipFileDependencyResolution: true,
@@ -646,29 +577,36 @@ async function collectEmittedFiles(extensions, ctx) {
646
577
  `Extension "${ext.name}" tried to emit the core-owned file "${file.path}". Core files (${[...CORE_FILES].join(", ")}) cannot be produced by extensions.`
647
578
  );
648
579
  }
649
- const prev = owners.get(key);
650
- if (prev !== void 0) {
651
- throw new CodegenError(
652
- `Output file "${file.path}" is emitted by both "${prev}" and "${ext.name}". Two extensions cannot write the same file.`
653
- );
654
- }
655
- owners.set(key, ext.name);
580
+ mergeExclusive(owners, [[key, file]], {
581
+ owner: ext.name,
582
+ describe: (_key, prevOwner, owner) => `Output file "${file.path}" is emitted by both "${prevOwner}" and "${owner}". Two extensions cannot write the same file.`
583
+ });
656
584
  files.push(file);
657
585
  }
658
586
  }
659
587
  return files;
660
588
  }
661
589
 
590
+ // src/extension/types.ts
591
+ function requestShape(route) {
592
+ const cs = route.contract?.contractSource;
593
+ const isGet = route.method.toUpperCase() === "GET";
594
+ const isQuery = isGet || !!cs?.filterFields?.length;
595
+ const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
596
+ const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
597
+ return { isGet, isQuery, hasBody, hasQuery };
598
+ }
599
+
662
600
  // src/emit/emit-api.ts
663
601
  async function emitApi(routes, outDir, opts = {}) {
664
602
  await mkdir(outDir, { recursive: true });
665
603
  const content = buildApiFile(routes, outDir, opts);
666
- await writeFile(join3(outDir, "api.ts"), content, "utf8");
604
+ await writeFile(join4(outDir, "api.ts"), content, "utf8");
667
605
  }
668
606
  function splitName(name) {
669
607
  return name.split(".");
670
608
  }
671
- function toObjectKey2(segment) {
609
+ function toObjectKey(segment) {
672
610
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
673
611
  return segment;
674
612
  }
@@ -775,7 +713,7 @@ function emitRouterTypeBlock(tree, indent, outDir) {
775
713
  const pad = " ".repeat(indent);
776
714
  const lines = [];
777
715
  for (const [key, node] of tree) {
778
- const objKey = toObjectKey2(key);
716
+ const objKey = toObjectKey(key);
779
717
  if (node.kind === "leaf") {
780
718
  const c = node;
781
719
  const method = c.method.toUpperCase();
@@ -801,15 +739,12 @@ function emitRouterTypeBlock(tree, indent, outDir) {
801
739
  return lines;
802
740
  }
803
741
  function buildRequestModel(c) {
804
- const isGet = c.method.toUpperCase() === "GET";
805
742
  const m = c.method.toLowerCase();
806
743
  const flat = JSON.stringify(c.name);
807
744
  const path = JSON.stringify(c.path);
808
745
  const TA = buildRouterTypeAccess(c.name);
809
746
  const withParams = hasPathParams(c.params);
810
- const hasBody = !!c.contractSource.bodyRef || c.contractSource.body != null && c.contractSource.body !== "never";
811
- const isQuery = isGet || !!c.contractSource.filterFields?.length;
812
- const hasQuery = isGet || !!c.contractSource.queryRef || c.contractSource.query != null && c.contractSource.query !== "never";
747
+ const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
813
748
  const fields = [];
814
749
  if (withParams) fields.push(`params: ${TA}['params']`);
815
750
  if (hasQuery) fields.push(`query?: ${TA}['query']`);
@@ -831,7 +766,6 @@ function buildRequestModel(c) {
831
766
  urlExpr,
832
767
  optsExpr,
833
768
  responseType: `${TA}['response']`,
834
- bodyType: `${TA}['body']`,
835
769
  queryKeyExpr: `[${flat}, input] as const`
836
770
  };
837
771
  }
@@ -879,7 +813,7 @@ function emitApiObjectBlock(tree, indent, p) {
879
813
  const pad = " ".repeat(indent);
880
814
  const lines = [];
881
815
  for (const [key, node] of tree) {
882
- const objKey = toObjectKey2(key);
816
+ const objKey = toObjectKey(key);
883
817
  if (node.kind === "branch") {
884
818
  lines.push(`${pad}${objKey}: {`);
885
819
  lines.push(...emitApiObjectBlock(node.children, indent + 2, p));
@@ -887,30 +821,28 @@ function emitApiObjectBlock(tree, indent, p) {
887
821
  continue;
888
822
  }
889
823
  const req = buildRequestModel(node);
890
- const route = {
891
- method: node.method,
892
- path: node.path,
893
- name: node.name,
894
- params: node.params,
895
- contract: { contractSource: node.contractSource },
896
- ...node.controllerRef ? { controllerRef: node.controllerRef } : {}
824
+ const leaf = {
825
+ route: node.route,
826
+ request: req,
827
+ requestExpr: renderFetcherRequest(req)
897
828
  };
898
- const leaf = { route, request: req, requestExpr: "" };
899
- leaf.requestExpr = p.transport ? p.transport.renderRequest(leaf, p.ctx) : renderFetcherRequest(req);
900
- const members = {};
901
- if (p.layer) Object.assign(members, p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx));
829
+ const owned = /* @__PURE__ */ new Map();
830
+ if (p.layer) {
831
+ mergeExclusive(owned, Object.entries(p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx)), {
832
+ owner: p.layer.name,
833
+ describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
834
+ });
835
+ }
902
836
  for (const ext of p.memberExts) {
903
837
  const extra = ext.apiMembers?.(leaf, p.ctx);
904
838
  if (!extra) continue;
905
- for (const [name, value] of Object.entries(extra)) {
906
- if (name in members) {
907
- throw new Error(
908
- `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict at "${ext.name}").`
909
- );
910
- }
911
- members[name] = value;
912
- }
839
+ mergeExclusive(owned, Object.entries(extra), {
840
+ owner: ext.name,
841
+ describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
842
+ });
913
843
  }
844
+ const members = {};
845
+ for (const [name, { value }] of owned) members[name] = value;
914
846
  lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members));
915
847
  }
916
848
  return lines;
@@ -919,10 +851,82 @@ function buildRouterTypeAccess(name) {
919
851
  const segments = splitName(name);
920
852
  return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
921
853
  }
854
+ var RESOLVER_HELPERS = [
855
+ // --- Recursive helper type _RouterAt: walks nested ApiRouter by dot-path ---
856
+ "type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`",
857
+ " ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never",
858
+ " : P extends keyof R ? R[P] : never;",
859
+ "",
860
+ // --- ResolveByName: resolve a field from a dot-path name ---
861
+ "type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;",
862
+ "",
863
+ // --- ResolveByPath: scan all leaves for matching method + url ---
864
+ // Flattens ApiRouter recursively and finds the entry whose method === M and url === U.
865
+ "type _LeafValues<T> = T extends { method: string; url: string }",
866
+ " ? T",
867
+ " : T extends object ? _LeafValues<T[keyof T]> : never;",
868
+ "",
869
+ "type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L",
870
+ " ? L extends { method: M; url: U }",
871
+ " ? Field extends keyof L ? L[Field] : never",
872
+ " : never",
873
+ " : never;",
874
+ ""
875
+ ];
876
+ var ROUTE_NAMESPACE = [
877
+ "export namespace Route {",
878
+ ' export type Response<K extends string> = ResolveByName<K, "response">;',
879
+ ' export type Body<K extends string> = ResolveByName<K, "body">;',
880
+ ' export type Query<K extends string> = ResolveByName<K, "query">;',
881
+ ' export type Params<K extends string> = ResolveByName<K, "params">;',
882
+ ' export type Error<K extends string> = ResolveByName<K, "error">;',
883
+ ' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
884
+ " export type Request<K extends string> = {",
885
+ " body: Body<K>;",
886
+ " query: Query<K>;",
887
+ " params: Params<K>;",
888
+ " };",
889
+ "}",
890
+ ""
891
+ ];
892
+ var PATH_NAMESPACE = [
893
+ "export namespace Path {",
894
+ ' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
895
+ ' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;',
896
+ ' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;',
897
+ ' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
898
+ ' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
899
+ ' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
900
+ "}",
901
+ ""
902
+ ];
903
+ var EMPTY_ROUTE_NAMESPACE = [
904
+ "export namespace Route {",
905
+ " export type Response<K extends string> = never;",
906
+ " export type Body<K extends string> = never;",
907
+ " export type Query<K extends string> = never;",
908
+ " export type Params<K extends string> = never;",
909
+ " export type Error<K extends string> = never;",
910
+ " export type FilterFields<K extends string> = never;",
911
+ " export type Request<K extends string> = { body: never; query: never; params: never };",
912
+ "}",
913
+ ""
914
+ ];
915
+ var EMPTY_PATH_NAMESPACE = [
916
+ "export namespace Path {",
917
+ " export type Response<M extends string, U extends string> = never;",
918
+ " export type Body<M extends string, U extends string> = never;",
919
+ " export type Query<M extends string, U extends string> = never;",
920
+ " export type Params<M extends string, U extends string> = never;",
921
+ " export type Error<M extends string, U extends string> = never;",
922
+ " export type FilterFields<M extends string, U extends string> = never;",
923
+ "}",
924
+ ""
925
+ ];
922
926
  function buildApiFile(routes, outDir, opts = {}) {
923
927
  const fetcherImportPath = opts.fetcherImportPath;
924
928
  const extensions = opts.extensions ?? [];
925
- const { transport, layer } = resolveApiSlots(extensions);
929
+ const { layer } = resolveApiSlots(extensions);
926
930
  const memberExts = extensions.filter((e) => e.apiMembers);
927
931
  const headerExts = extensions.filter((e) => e.apiHeader);
928
932
  const contracted = routes.filter((r) => r.contract);
@@ -967,7 +971,6 @@ function buildApiFile(routes, outDir, opts = {}) {
967
971
  seenImports.add(imp);
968
972
  extImports.push(imp);
969
973
  };
970
- for (const imp of transport?.imports?.(ctx) ?? []) pushImport(imp);
971
974
  for (const imp of layer?.imports?.(ctx) ?? []) pushImport(imp);
972
975
  for (const ext of headerExts) {
973
976
  for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
@@ -1012,27 +1015,8 @@ function buildApiFile(routes, outDir, opts = {}) {
1012
1015
  lines.push("}");
1013
1016
  lines.push("export type Api = ReturnType<typeof createApi>;");
1014
1017
  lines.push("");
1015
- lines.push("export namespace Route {");
1016
- lines.push(" export type Response<K extends string> = never;");
1017
- lines.push(" export type Body<K extends string> = never;");
1018
- lines.push(" export type Query<K extends string> = never;");
1019
- lines.push(" export type Params<K extends string> = never;");
1020
- lines.push(" export type Error<K extends string> = never;");
1021
- lines.push(" export type FilterFields<K extends string> = never;");
1022
- lines.push(
1023
- " export type Request<K extends string> = { body: never; query: never; params: never };"
1024
- );
1025
- lines.push("}");
1026
- lines.push("");
1027
- lines.push("export namespace Path {");
1028
- lines.push(" export type Response<M extends string, U extends string> = never;");
1029
- lines.push(" export type Body<M extends string, U extends string> = never;");
1030
- lines.push(" export type Query<M extends string, U extends string> = never;");
1031
- lines.push(" export type Params<M extends string, U extends string> = never;");
1032
- lines.push(" export type Error<M extends string, U extends string> = never;");
1033
- lines.push(" export type FilterFields<M extends string, U extends string> = never;");
1034
- lines.push("}");
1035
- lines.push("");
1018
+ lines.push(...EMPTY_ROUTE_NAMESPACE);
1019
+ lines.push(...EMPTY_PATH_NAMESPACE);
1036
1020
  return lines.join("\n");
1037
1021
  }
1038
1022
  const tree = /* @__PURE__ */ new Map();
@@ -1050,7 +1034,8 @@ function buildApiFile(routes, outDir, opts = {}) {
1050
1034
  path: r.path,
1051
1035
  params: r.params,
1052
1036
  controllerRef: r.controllerRef,
1053
- contractSource: c.contractSource
1037
+ contractSource: c.contractSource,
1038
+ route: r
1054
1039
  };
1055
1040
  insertIntoTree(tree, segments, leaf, name);
1056
1041
  }
@@ -1063,7 +1048,6 @@ function buildApiFile(routes, outDir, opts = {}) {
1063
1048
  lines.push(" return {");
1064
1049
  lines.push(
1065
1050
  ...emitApiObjectBlock(tree, 4, {
1066
- ...transport ? { transport } : {},
1067
1051
  ...layer ? { layer } : {},
1068
1052
  memberExts,
1069
1053
  ctx
@@ -1074,61 +1058,9 @@ function buildApiFile(routes, outDir, opts = {}) {
1074
1058
  lines.push("");
1075
1059
  lines.push("export type Api = ReturnType<typeof createApi>;");
1076
1060
  lines.push("");
1077
- lines.push("type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`");
1078
- lines.push(" ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never");
1079
- lines.push(" : P extends keyof R ? R[P] : never;");
1080
- lines.push("");
1081
- lines.push(
1082
- "type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;"
1083
- );
1084
- lines.push("");
1085
- lines.push("type _LeafValues<T> = T extends { method: string; url: string }");
1086
- lines.push(" ? T");
1087
- lines.push(" : T extends object ? _LeafValues<T[keyof T]> : never;");
1088
- lines.push("");
1089
- lines.push(
1090
- "type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L"
1091
- );
1092
- lines.push(" ? L extends { method: M; url: U }");
1093
- lines.push(" ? Field extends keyof L ? L[Field] : never");
1094
- lines.push(" : never");
1095
- lines.push(" : never;");
1096
- lines.push("");
1097
- lines.push("export namespace Route {");
1098
- lines.push(' export type Response<K extends string> = ResolveByName<K, "response">;');
1099
- lines.push(' export type Body<K extends string> = ResolveByName<K, "body">;');
1100
- lines.push(' export type Query<K extends string> = ResolveByName<K, "query">;');
1101
- lines.push(' export type Params<K extends string> = ResolveByName<K, "params">;');
1102
- lines.push(' export type Error<K extends string> = ResolveByName<K, "error">;');
1103
- lines.push(' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;');
1104
- lines.push(" export type Request<K extends string> = {");
1105
- lines.push(" body: Body<K>;");
1106
- lines.push(" query: Query<K>;");
1107
- lines.push(" params: Params<K>;");
1108
- lines.push(" };");
1109
- lines.push("}");
1110
- lines.push("");
1111
- lines.push("export namespace Path {");
1112
- lines.push(
1113
- ' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;'
1114
- );
1115
- lines.push(
1116
- ' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;'
1117
- );
1118
- lines.push(
1119
- ' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;'
1120
- );
1121
- lines.push(
1122
- ' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;'
1123
- );
1124
- lines.push(
1125
- ' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;'
1126
- );
1127
- lines.push(
1128
- ' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;'
1129
- );
1130
- lines.push("}");
1131
- lines.push("");
1061
+ lines.push(...RESOLVER_HELPERS);
1062
+ lines.push(...ROUTE_NAMESPACE);
1063
+ lines.push(...PATH_NAMESPACE);
1132
1064
  for (const ext of headerExts) {
1133
1065
  const statements = ext.apiHeader?.(ctx)?.statements;
1134
1066
  if (statements?.length) {
@@ -1140,7 +1072,7 @@ function buildApiFile(routes, outDir, opts = {}) {
1140
1072
 
1141
1073
  // src/emit/emit-cache.ts
1142
1074
  import { mkdir as mkdir2, stat, writeFile as writeFile2 } from "fs/promises";
1143
- import { join as join4 } from "path";
1075
+ import { join as join5 } from "path";
1144
1076
  async function emitCache(pages, outDir) {
1145
1077
  await mkdir2(outDir, { recursive: true });
1146
1078
  const entries = await Promise.all(
@@ -1154,95 +1086,21 @@ async function emitCache(pages, outDir) {
1154
1086
  })
1155
1087
  );
1156
1088
  const cache = { pages: entries };
1157
- await writeFile2(join4(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
1089
+ await writeFile2(join5(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
1158
1090
  `, "utf8");
1159
1091
  }
1160
1092
 
1161
1093
  // src/emit/emit-forms.ts
1162
1094
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
1163
- import { join as join5, relative as relative4 } from "path";
1095
+ import { join as join6, relative as relative4 } from "path";
1164
1096
  async function emitForms(routes, outDir, config, adapter) {
1165
1097
  if (config && config.enabled === false) return false;
1166
- if (adapter && adapter.name !== "zod") {
1167
- const content2 = buildFormsFileWithAdapter(routes, adapter);
1168
- if (content2 === null) return false;
1169
- await mkdir3(outDir, { recursive: true });
1170
- await writeFile3(join5(outDir, "forms.ts"), content2, "utf8");
1171
- return true;
1172
- }
1173
- const entries = collectFormEntries(routes);
1174
- if (entries.length === 0) return false;
1098
+ const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
1099
+ if (content === null) return false;
1175
1100
  await mkdir3(outDir, { recursive: true });
1176
- const content = buildFormsFile(entries, outDir, config);
1177
- await writeFile3(join5(outDir, "forms.ts"), content, "utf8");
1101
+ await writeFile3(join6(outDir, "forms.ts"), content, "utf8");
1178
1102
  return true;
1179
1103
  }
1180
- function buildFormsFileWithAdapter(routes, adapter) {
1181
- const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
1182
- const methodNameCounts = /* @__PURE__ */ new Map();
1183
- for (const route of sorted) {
1184
- const cs = route.contract.contractSource;
1185
- if (!cs.bodySchema && !cs.querySchema) continue;
1186
- methodNameCounts.set(
1187
- deriveBaseName(route.name).method,
1188
- (methodNameCounts.get(deriveBaseName(route.name).method) ?? 0) + 1
1189
- );
1190
- }
1191
- const named = /* @__PURE__ */ new Map();
1192
- const decls = [];
1193
- const mapEntries = [];
1194
- let used = false;
1195
- for (const route of sorted) {
1196
- const cs = route.contract.contractSource;
1197
- const { method, full } = deriveBaseName(route.name);
1198
- const base = (methodNameCounts.get(method) ?? 0) > 1 ? full : method;
1199
- const block = [];
1200
- if (cs.bodyZodText && !cs.bodySchema) {
1201
- block.push(
1202
- `// warning: ${route.name} body is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
1203
- );
1204
- }
1205
- let bodyConst;
1206
- if (cs.bodySchema) {
1207
- used = true;
1208
- const r = adapter.renderModule(cs.bodySchema);
1209
- for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
1210
- bodyConst = `${base}BodySchema`;
1211
- block.push(`export const ${bodyConst} = ${r.schemaText};`);
1212
- block.push(`export type ${base}Body = ${adapter.inferType(bodyConst)};`);
1213
- }
1214
- if (cs.querySchema) {
1215
- used = true;
1216
- const r = adapter.renderModule(cs.querySchema);
1217
- for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
1218
- const queryConst = `${base}QuerySchema`;
1219
- block.push(`export const ${queryConst} = ${r.schemaText};`);
1220
- block.push(`export type ${base}Query = ${adapter.inferType(queryConst)};`);
1221
- }
1222
- if (block.length === 0) continue;
1223
- decls.push(`// ${route.name}`, ...block, "");
1224
- if (bodyConst) mapEntries.push(` ${JSON.stringify(route.name)}: ${bodyConst},`);
1225
- }
1226
- if (!used) return null;
1227
- const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
1228
- for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
1229
- lines.push("");
1230
- if (named.size > 0) {
1231
- lines.push("// Hoisted nested schemas (shared across endpoints).");
1232
- for (const [n, t] of named) lines.push(`const ${n} = ${t};`);
1233
- lines.push("");
1234
- }
1235
- lines.push(...decls);
1236
- lines.push("/** Route name \u2192 body schema map. */");
1237
- lines.push("export const formSchemas = {");
1238
- lines.push(...mapEntries);
1239
- lines.push("} as const;");
1240
- lines.push("");
1241
- return lines.join("\n");
1242
- }
1243
- function hasSchema(src) {
1244
- return !!src && (src.ref !== null || src.text !== null);
1245
- }
1246
1104
  function pascal(segment) {
1247
1105
  return segment.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1248
1106
  }
@@ -1252,37 +1110,6 @@ function deriveBaseName(routeName) {
1252
1110
  const full = segments.map(pascal).join("");
1253
1111
  return { method, full };
1254
1112
  }
1255
- function collectFormEntries(routes) {
1256
- const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
1257
- const methodNameCounts = /* @__PURE__ */ new Map();
1258
- const candidates = [];
1259
- for (const route of sorted) {
1260
- const cs = route.contract.contractSource;
1261
- const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
1262
- const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
1263
- if (!hasSchema(body) && !hasSchema(query)) continue;
1264
- const { method, full } = deriveBaseName(route.name);
1265
- methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
1266
- candidates.push({ route, method, full });
1267
- }
1268
- const entries = [];
1269
- for (const { route, method, full } of candidates) {
1270
- const cs = route.contract.contractSource;
1271
- const collision = (methodNameCounts.get(method) ?? 0) > 1;
1272
- const baseName = collision ? full : method;
1273
- const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
1274
- const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
1275
- entries.push({
1276
- routeName: route.name,
1277
- baseName,
1278
- body: hasSchema(body) ? body : void 0,
1279
- query: hasSchema(query) ? query : void 0,
1280
- nestedSchemas: cs.formNestedSchemas ?? null,
1281
- warnings: cs.formWarnings ?? []
1282
- });
1283
- }
1284
- return entries;
1285
- }
1286
1113
  function relImport(outDir, filePath) {
1287
1114
  let relPath = relative4(outDir, filePath).replace(/\.ts$/, "");
1288
1115
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
@@ -1291,85 +1118,8 @@ function relImport(outDir, filePath) {
1291
1118
  function refRootIdentifier(refName) {
1292
1119
  return refName.split(".")[0] ?? refName;
1293
1120
  }
1294
- function buildFormsFile(entries, outDir, config) {
1295
- const zodImport = config?.zodImport ?? "zod";
1296
- const lines = [
1297
- "// Generated by @dudousxd/nestjs-codegen. Do not edit.",
1298
- `import { z } from '${zodImport}';`
1299
- ];
1300
- const importsByFile = /* @__PURE__ */ new Map();
1301
- const refAlias = /* @__PURE__ */ new Map();
1302
- for (const entry of entries) {
1303
- for (const src of [entry.body, entry.query]) {
1304
- if (src?.ref && !src.text) {
1305
- const root = refRootIdentifier(src.ref.name);
1306
- const set = importsByFile.get(src.ref.filePath) ?? /* @__PURE__ */ new Set();
1307
- set.add(root);
1308
- importsByFile.set(src.ref.filePath, set);
1309
- }
1310
- }
1311
- }
1312
- if (importsByFile.size > 0) {
1313
- const emitted = /* @__PURE__ */ new Set();
1314
- for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
1315
- const relPath = relImport(outDir, filePath);
1316
- const specifiers = [];
1317
- for (const root of [...roots].sort()) {
1318
- if (emitted.has(root)) {
1319
- const alias = `${root}_${emitted.size}`;
1320
- specifiers.push(`${root} as ${alias}`);
1321
- emitted.add(alias);
1322
- refAlias.set(`${filePath}\0${root}`, alias);
1323
- } else {
1324
- specifiers.push(root);
1325
- emitted.add(root);
1326
- refAlias.set(`${filePath}\0${root}`, root);
1327
- }
1328
- }
1329
- lines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
1330
- }
1331
- }
1332
- lines.push("");
1333
- const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
1334
- if (globalSchemas.size > 0) {
1335
- lines.push("// Hoisted nested schemas (shared across endpoints).");
1336
- for (const [name, text] of globalSchemas) {
1337
- lines.push(`const ${name} = ${text};`);
1338
- }
1339
- lines.push("");
1340
- }
1341
- const mapEntries = [];
1342
- for (const entry of entries) {
1343
- lines.push(`// ${entry.routeName}`);
1344
- if (entry.warnings && entry.warnings.length > 0) {
1345
- for (const w of entry.warnings) {
1346
- lines.push(`// warning: ${w}`);
1347
- }
1348
- }
1349
- const rename = renamesByEntry.get(entry) ?? null;
1350
- if (entry.body) {
1351
- const schemaName = `${entry.baseName}BodySchema`;
1352
- const typeName = `${entry.baseName}Body`;
1353
- const text = applyRenames(renderSchema(entry.body, outDir, refAlias), rename);
1354
- lines.push(`export const ${schemaName} = ${text};`);
1355
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
1356
- mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${schemaName},`);
1357
- }
1358
- if (entry.query) {
1359
- const schemaName = `${entry.baseName}QuerySchema`;
1360
- const typeName = `${entry.baseName}Query`;
1361
- const text = applyRenames(renderSchema(entry.query, outDir, refAlias), rename);
1362
- lines.push(`export const ${schemaName} = ${text};`);
1363
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
1364
- }
1365
- lines.push("");
1366
- }
1367
- lines.push("/** Route name \u2192 body schema map. */");
1368
- lines.push("export const formSchemas = {");
1369
- lines.push(...mapEntries);
1370
- lines.push("} as const;");
1371
- lines.push("");
1372
- return lines.join("\n");
1121
+ function hasSource(src) {
1122
+ return !!(src.schema || src.zodText || src.zodRef);
1373
1123
  }
1374
1124
  function applyRenames(text, renames) {
1375
1125
  if (!renames || renames.size === 0) return text;
@@ -1435,42 +1185,194 @@ function planNestedSchemas(entries) {
1435
1185
  }
1436
1186
  return { globalSchemas, renamesByEntry };
1437
1187
  }
1438
- function renderSchema(src, outDir, refAlias) {
1439
- if (src.text) return src.text;
1440
- if (src.ref) {
1441
- const root = refRootIdentifier(src.ref.name);
1442
- const alias = refAlias.get(`${src.ref.filePath}\0${root}`) ?? root;
1443
- const member = src.ref.name.slice(root.length);
1444
- return `${alias}${member}`;
1188
+ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
1189
+ const acceptsRawZod = adapter.acceptsRawZodSource === true;
1190
+ const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
1191
+ const methodNameCounts = /* @__PURE__ */ new Map();
1192
+ const candidates = [];
1193
+ for (const route of sorted) {
1194
+ const cs = route.contract.contractSource;
1195
+ const body = {
1196
+ schema: cs.bodySchema ?? null,
1197
+ zodText: cs.bodyZodText ?? null,
1198
+ zodRef: cs.bodyZodRef ?? null
1199
+ };
1200
+ const query = {
1201
+ schema: cs.querySchema ?? null,
1202
+ zodText: cs.queryZodText ?? null,
1203
+ zodRef: cs.queryZodRef ?? null
1204
+ };
1205
+ if (!hasSource(body) && !hasSource(query)) continue;
1206
+ const { method, full } = deriveBaseName(route.name);
1207
+ methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
1208
+ candidates.push({
1209
+ routeName: route.name,
1210
+ baseName: full,
1211
+ // resolved below
1212
+ body: hasSource(body) ? body : void 0,
1213
+ query: hasSource(query) ? query : void 0,
1214
+ nestedSchemas: cs.formNestedSchemas ?? null,
1215
+ warnings: cs.formWarnings ?? []
1216
+ });
1445
1217
  }
1446
- return "z.unknown()";
1447
- }
1448
-
1449
- // src/emit/emit-index.ts
1450
- import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
1451
- import { join as join6 } from "path";
1452
- async function emitIndex(outDir, hasContracts = false, hasForms = false) {
1453
- await mkdir4(outDir, { recursive: true });
1454
- const exports = ["export * from './pages.js';", "export * from './routes.js';"];
1455
- if (hasContracts) {
1456
- exports.push("export * from './api.js';");
1218
+ const entries = candidates.map((c) => {
1219
+ const { method, full } = deriveBaseName(c.routeName);
1220
+ const collision = (methodNameCounts.get(method) ?? 0) > 1;
1221
+ return { ...c, baseName: collision ? full : method };
1222
+ });
1223
+ if (entries.length === 0) return null;
1224
+ const importsByFile = /* @__PURE__ */ new Map();
1225
+ const refAlias = /* @__PURE__ */ new Map();
1226
+ for (const entry of entries) {
1227
+ for (const src of [entry.body, entry.query]) {
1228
+ if (src?.zodRef && !src.zodText && !src.schema) {
1229
+ const root = refRootIdentifier(src.zodRef.name);
1230
+ const set = importsByFile.get(src.zodRef.filePath) ?? /* @__PURE__ */ new Set();
1231
+ set.add(root);
1232
+ importsByFile.set(src.zodRef.filePath, set);
1233
+ }
1234
+ }
1457
1235
  }
1458
- if (hasForms) {
1459
- exports.push("export * from './forms.js';");
1236
+ const importLines = [];
1237
+ if (importsByFile.size > 0) {
1238
+ const emitted = /* @__PURE__ */ new Set();
1239
+ for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
1240
+ const relPath = relImport(outDir, filePath);
1241
+ const specifiers = [];
1242
+ for (const root of [...roots].sort()) {
1243
+ if (emitted.has(root)) {
1244
+ const alias = `${root}_${emitted.size}`;
1245
+ specifiers.push(`${root} as ${alias}`);
1246
+ emitted.add(alias);
1247
+ refAlias.set(`${filePath}\0${root}`, alias);
1248
+ } else {
1249
+ specifiers.push(root);
1250
+ emitted.add(root);
1251
+ refAlias.set(`${filePath}\0${root}`, root);
1252
+ }
1253
+ }
1254
+ importLines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
1255
+ }
1460
1256
  }
1461
- const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports, ""].join(
1462
- "\n"
1463
- );
1464
- await writeFile4(join6(outDir, "index.d.ts"), content, "utf8");
1465
- }
1466
-
1467
- // src/emit/emit-pages.ts
1468
- import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
1469
- import { join as join7, relative as relative5 } from "path";
1470
- async function emitPages(pages, outDir, _options = {}) {
1471
- await mkdir5(outDir, { recursive: true });
1472
- const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
1473
- const augBody = pages.map((p) => {
1257
+ const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
1258
+ const irNamed = /* @__PURE__ */ new Map();
1259
+ const decls = [];
1260
+ const mapEntries = [];
1261
+ let used = false;
1262
+ const renderSource = (src, rename) => {
1263
+ if (src.schema) {
1264
+ const r = adapter.renderModule(src.schema);
1265
+ for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
1266
+ return { text: r.schemaText };
1267
+ }
1268
+ if (src.zodText) {
1269
+ if (!acceptsRawZod) {
1270
+ return {
1271
+ text: "",
1272
+ warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
1273
+ };
1274
+ }
1275
+ return { text: applyRenames(src.zodText, rename) };
1276
+ }
1277
+ if (src.zodRef) {
1278
+ if (!acceptsRawZod) {
1279
+ return {
1280
+ text: "",
1281
+ warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
1282
+ };
1283
+ }
1284
+ const root = refRootIdentifier(src.zodRef.name);
1285
+ const alias = refAlias.get(`${src.zodRef.filePath}\0${root}`) ?? root;
1286
+ const member = src.zodRef.name.slice(root.length);
1287
+ return { text: `${alias}${member}` };
1288
+ }
1289
+ return null;
1290
+ };
1291
+ for (const entry of entries) {
1292
+ const block = [];
1293
+ const rename = renamesByEntry.get(entry) ?? null;
1294
+ let bodyConst;
1295
+ if (entry.warnings && entry.warnings.length > 0) {
1296
+ for (const w of entry.warnings) block.push(`// warning: ${w}`);
1297
+ }
1298
+ if (entry.body) {
1299
+ const rendered = renderSource(entry.body, rename);
1300
+ if (rendered?.warn) {
1301
+ block.push(`// warning: ${entry.routeName} body ${rendered.warn}`);
1302
+ } else if (rendered) {
1303
+ used = true;
1304
+ bodyConst = `${entry.baseName}BodySchema`;
1305
+ block.push(`export const ${bodyConst} = ${rendered.text};`);
1306
+ block.push(`export type ${entry.baseName}Body = ${adapter.inferType(bodyConst)};`);
1307
+ }
1308
+ }
1309
+ if (entry.query) {
1310
+ const rendered = renderSource(entry.query, rename);
1311
+ if (rendered?.warn) {
1312
+ block.push(`// warning: ${entry.routeName} query ${rendered.warn}`);
1313
+ } else if (rendered) {
1314
+ used = true;
1315
+ const queryConst = `${entry.baseName}QuerySchema`;
1316
+ block.push(`export const ${queryConst} = ${rendered.text};`);
1317
+ block.push(`export type ${entry.baseName}Query = ${adapter.inferType(queryConst)};`);
1318
+ }
1319
+ }
1320
+ if (block.length === 0) continue;
1321
+ decls.push(`// ${entry.routeName}`, ...block, "");
1322
+ if (bodyConst) mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${bodyConst},`);
1323
+ }
1324
+ if (!used) return null;
1325
+ const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
1326
+ if (acceptsRawZod) {
1327
+ const zodImport = config?.zodImport ?? "zod";
1328
+ lines.push(`import { z } from '${zodImport}';`);
1329
+ } else {
1330
+ for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
1331
+ }
1332
+ lines.push(...importLines);
1333
+ lines.push("");
1334
+ const allNested = /* @__PURE__ */ new Map();
1335
+ for (const [n, t] of globalSchemas) allNested.set(n, t);
1336
+ for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
1337
+ if (allNested.size > 0) {
1338
+ lines.push("// Hoisted nested schemas (shared across endpoints).");
1339
+ for (const [n, t] of allNested) lines.push(`const ${n} = ${t};`);
1340
+ lines.push("");
1341
+ }
1342
+ lines.push(...decls);
1343
+ lines.push("/** Route name \u2192 body schema map. */");
1344
+ lines.push("export const formSchemas = {");
1345
+ lines.push(...mapEntries);
1346
+ lines.push("} as const;");
1347
+ lines.push("");
1348
+ return lines.join("\n");
1349
+ }
1350
+
1351
+ // src/emit/emit-index.ts
1352
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
1353
+ import { join as join7 } from "path";
1354
+ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
1355
+ await mkdir4(outDir, { recursive: true });
1356
+ const exports = ["export * from './pages.js';", "export * from './routes.js';"];
1357
+ if (hasContracts) {
1358
+ exports.push("export * from './api.js';");
1359
+ }
1360
+ if (hasForms) {
1361
+ exports.push("export * from './forms.js';");
1362
+ }
1363
+ const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports, ""].join(
1364
+ "\n"
1365
+ );
1366
+ await writeFile4(join7(outDir, "index.d.ts"), content, "utf8");
1367
+ }
1368
+
1369
+ // src/emit/emit-pages.ts
1370
+ import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
1371
+ import { join as join8, relative as relative5 } from "path";
1372
+ async function emitPages(pages, outDir, _options = {}) {
1373
+ await mkdir5(outDir, { recursive: true });
1374
+ const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
1375
+ const augBody = pages.map((p) => {
1474
1376
  const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
1475
1377
  const valueType = buildAugmentationType(p, outDir);
1476
1378
  return ` ${key}: ${valueType};`;
@@ -1487,7 +1389,7 @@ ${augBody}
1487
1389
  }
1488
1390
  ${sharedPropsBlock}}
1489
1391
  `;
1490
- await writeFile5(join7(outDir, "pages.d.ts"), content, "utf8");
1392
+ await writeFile5(join8(outDir, "pages.d.ts"), content, "utf8");
1491
1393
  }
1492
1394
  function buildSharedPropsBlock(sharedProps) {
1493
1395
  if (!sharedProps) return "";
@@ -1518,11 +1420,11 @@ function needsQuotes(name) {
1518
1420
 
1519
1421
  // src/emit/emit-routes.ts
1520
1422
  import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
1521
- import { join as join8 } from "path";
1423
+ import { join as join9 } from "path";
1522
1424
  async function emitRoutes(routes, outDir) {
1523
1425
  await mkdir6(outDir, { recursive: true });
1524
1426
  const content = buildRoutesFile(routes);
1525
- await writeFile6(join8(outDir, "routes.ts"), content, "utf8");
1427
+ await writeFile6(join9(outDir, "routes.ts"), content, "utf8");
1526
1428
  }
1527
1429
  function buildRoutesFile(routes) {
1528
1430
  if (routes.length === 0) {
@@ -1650,30 +1552,7 @@ async function generate(config, inputRoutes = []) {
1650
1552
  propsExport: pagesConfig.propsExport,
1651
1553
  componentNameStrategy: pagesConfig.componentNameStrategy
1652
1554
  });
1653
- let sharedProps = null;
1654
- if (config.app?.moduleEntry) {
1655
- try {
1656
- const tsconfigPath = config.app.tsconfig ?? join9(config.codegen.cwd, "tsconfig.json");
1657
- let project;
1658
- try {
1659
- project = new Project2({
1660
- tsConfigFilePath: tsconfigPath,
1661
- skipAddingFilesFromTsConfig: true,
1662
- skipLoadingLibFiles: true,
1663
- skipFileDependencyResolution: true
1664
- });
1665
- } catch {
1666
- project = new Project2({
1667
- skipAddingFilesFromTsConfig: true,
1668
- skipLoadingLibFiles: true,
1669
- skipFileDependencyResolution: true,
1670
- compilerOptions: { allowJs: true, strict: false }
1671
- });
1672
- }
1673
- sharedProps = discoverSharedProps(project, config.app.moduleEntry);
1674
- } catch {
1675
- }
1676
- }
1555
+ const sharedProps = discoverSharedPropsFromConfig(config);
1677
1556
  await emitPages(pages, config.codegen.outDir, {
1678
1557
  propsExport: pagesConfig.propsExport,
1679
1558
  sharedProps
@@ -1697,7 +1576,7 @@ async function generate(config, inputRoutes = []) {
1697
1576
  if (extensions.length > 0) {
1698
1577
  const extraFiles = await collectEmittedFiles(extensions, ctx);
1699
1578
  for (const file of extraFiles) {
1700
- const dest = join9(config.codegen.outDir, file.path);
1579
+ const dest = join10(config.codegen.outDir, file.path);
1701
1580
  await mkdir7(dirname2(dest), { recursive: true });
1702
1581
  await writeFile7(dest, file.contents, "utf8");
1703
1582
  }
@@ -1706,15 +1585,20 @@ async function generate(config, inputRoutes = []) {
1706
1585
 
1707
1586
  // src/watch/watcher.ts
1708
1587
  import { readFile as readFile3 } from "fs/promises";
1709
- import { join as join12 } from "path";
1588
+ import { join as join13 } from "path";
1710
1589
  import chokidar from "chokidar";
1711
1590
 
1712
1591
  // src/discovery/contracts-fast.ts
1713
- import { join as join10, resolve as resolve3 } from "path";
1592
+ import { join as join11, resolve as resolve3 } from "path";
1714
1593
  import fg2 from "fast-glob";
1715
1594
  import {
1716
- Node as Node7,
1717
- Project as Project3,
1595
+ Node as Node8,
1596
+ Project as Project3
1597
+ } from "ts-morph";
1598
+
1599
+ // src/discovery/dto-type-resolver.ts
1600
+ import {
1601
+ Node as Node6,
1718
1602
  SyntaxKind as SyntaxKind3
1719
1603
  } from "ts-morph";
1720
1604
 
@@ -1729,20 +1613,13 @@ import { dirname as dirname3, resolve as resolve2 } from "path";
1729
1613
  import {
1730
1614
  Node as Node2
1731
1615
  } from "ts-morph";
1732
- var _ctx = { projectRoot: "", tsconfigPaths: null };
1733
- function setDiscoveryContext(ctx) {
1734
- const prev = _ctx;
1735
- _ctx = ctx;
1736
- return prev;
1737
- }
1738
- function restoreDiscoveryContext(ctx) {
1739
- _ctx = ctx;
1616
+ var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
1617
+ var _ctxByProject = /* @__PURE__ */ new WeakMap();
1618
+ function setDiscoveryContext(project, ctx) {
1619
+ _ctxByProject.set(project, ctx);
1740
1620
  }
1741
- function _projectRoot() {
1742
- return _ctx.projectRoot;
1743
- }
1744
- function _tsconfigPaths() {
1745
- return _ctx.tsconfigPaths;
1621
+ function _ctxFor(project) {
1622
+ return _ctxByProject.get(project) ?? _EMPTY_CTX;
1746
1623
  }
1747
1624
  var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
1748
1625
  function dbg(...args) {
@@ -1784,7 +1661,7 @@ function findTypeInFile(name, file) {
1784
1661
  }
1785
1662
  return null;
1786
1663
  }
1787
- function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
1664
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
1788
1665
  if (moduleSpecifier.startsWith(".")) {
1789
1666
  const dir = dirname3(sourceFile.getFilePath());
1790
1667
  const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
@@ -1794,8 +1671,9 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
1794
1671
  resolve2(dir, moduleSpecifier, "index.ts")
1795
1672
  ];
1796
1673
  }
1797
- const baseUrl = _projectRoot();
1798
- const tsconfigPaths = _tsconfigPaths();
1674
+ const ctx = _ctxFor(project);
1675
+ const baseUrl = ctx.projectRoot;
1676
+ const tsconfigPaths = ctx.tsconfigPaths;
1799
1677
  dbg(
1800
1678
  "resolveModuleSpecifier",
1801
1679
  moduleSpecifier,
@@ -1941,7 +1819,7 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
1941
1819
  if (!namedImport) continue;
1942
1820
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
1943
1821
  if (opts.allowBareSpecifier && !moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
1944
- const tsconfigPaths = _tsconfigPaths();
1822
+ const tsconfigPaths = _ctxFor(project).tsconfigPaths;
1945
1823
  const isAlias = tsconfigPaths != null && Object.keys(tsconfigPaths).some((p) => {
1946
1824
  const prefix = p.replace("*", "");
1947
1825
  return moduleSpecifier.startsWith(prefix);
@@ -2294,465 +2172,135 @@ function inSchemaFromDecorator(decorator) {
2294
2172
  return null;
2295
2173
  }
2296
2174
 
2297
- // src/discovery/dto-to-zod.ts
2175
+ // src/discovery/filter-for.ts
2298
2176
  import {
2299
- Node as Node4
2177
+ Node as Node5
2300
2178
  } from "ts-morph";
2301
- var KNOWN_DECORATORS2 = /* @__PURE__ */ new Set([
2302
- "IsString",
2303
- "IsNumber",
2304
- "IsInt",
2305
- "IsBoolean",
2306
- "IsDate",
2307
- "IsEmail",
2308
- "IsUrl",
2309
- "IsUUID",
2310
- "MinLength",
2311
- "MaxLength",
2312
- "Length",
2313
- "Min",
2314
- "Max",
2315
- "IsPositive",
2316
- "IsNegative",
2317
- "Matches",
2318
- "IsEnum",
2319
- "IsIn",
2320
- "IsOptional",
2321
- "IsNotEmpty",
2322
- "IsArray",
2323
- "ValidateNested",
2324
- "Type",
2325
- "IsObject",
2326
- "Allow",
2327
- "IsDefined"
2328
- ]);
2329
- function extractZodFromDto(classDecl, sourceFile, project) {
2330
- const ctx = {
2331
- sourceFile,
2332
- project,
2333
- namedNestedSchemas: /* @__PURE__ */ new Map(),
2334
- warnings: [],
2335
- warnedDecorators: /* @__PURE__ */ new Set(),
2336
- emittedClasses: /* @__PURE__ */ new Map(),
2337
- visiting: /* @__PURE__ */ new Set(),
2338
- recursiveSchemas: /* @__PURE__ */ new Set(),
2339
- depth: 0
2340
- };
2341
- const schemaText = buildObjectSchema(classDecl, sourceFile, ctx);
2342
- for (const schemaName of ctx.recursiveSchemas) {
2343
- ctx.namedNestedSchemas.set(schemaName, "z.unknown() /* recursive type \u2014 not expanded */");
2344
- }
2345
- return {
2346
- schemaText,
2347
- namedNestedSchemas: ctx.namedNestedSchemas,
2348
- warnings: ctx.warnings
2349
- };
2179
+
2180
+ // src/discovery/filter-field-types.ts
2181
+ import {
2182
+ Node as Node4,
2183
+ SyntaxKind as SyntaxKind2
2184
+ } from "ts-morph";
2185
+
2186
+ // src/discovery/enum-resolution.ts
2187
+ function resolveEnumValues(name, sourceFile, project) {
2188
+ const resolved = findType(name, sourceFile, project);
2189
+ if (!resolved || resolved.kind !== "enum") return null;
2190
+ let numeric = true;
2191
+ const values = resolved.members.map((m) => {
2192
+ const parsed = JSON.parse(m);
2193
+ if (typeof parsed === "string") numeric = false;
2194
+ return String(parsed);
2195
+ });
2196
+ if (values.length === 0) return null;
2197
+ return { values, numeric };
2350
2198
  }
2351
- function buildObjectSchema(classDecl, classFile, ctx) {
2352
- const props = classDecl.getProperties();
2353
- if (props.length === 0) {
2354
- return "z.object({}).passthrough()";
2355
- }
2356
- const fields = [];
2357
- for (const prop of props) {
2358
- const name = prop.getName();
2359
- const expr = buildPropertySchema(prop, classFile, ctx);
2360
- fields.push(`${toObjectKey3(name)}: ${expr}`);
2361
- }
2362
- return `z.object({ ${fields.join(", ")} })`;
2199
+
2200
+ // src/discovery/filter-field-types.ts
2201
+ var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
2202
+ var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
2203
+ var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
2204
+ var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
2205
+ var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
2206
+ function classifyTypeKeyword(raw) {
2207
+ const t = raw.toLowerCase();
2208
+ if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
2209
+ if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
2210
+ if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
2211
+ if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
2212
+ if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
2213
+ return null;
2363
2214
  }
2364
- function toObjectKey3(name) {
2365
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2215
+ function markNullable(r, nullable) {
2216
+ return nullable ? { ...r, nullable: true } : r;
2366
2217
  }
2367
- function buildPropertySchema(prop, classFile, ctx) {
2368
- const decorators = /* @__PURE__ */ new Map();
2369
- for (const d of prop.getDecorators()) decorators.set(d.getName(), d);
2370
- const has = (n) => decorators.has(n);
2371
- const dec = (n) => decorators.get(n);
2372
- const typeNode = prop.getTypeNode();
2373
- const typeText = typeNode?.getText() ?? "unknown";
2374
- const isArrayType = !!typeNode && typeNode.getText().endsWith("[]");
2375
- const comments = [];
2376
- const typeRefName = resolveTypeFactoryName2(dec("Type"));
2377
- if (has("ValidateNested") || typeRefName) {
2378
- const childName = typeRefName ?? singularClassName2(typeText);
2379
- if (childName) {
2380
- const childExpr = buildNestedReference2(childName, classFile, ctx);
2381
- const wrapArray = has("IsArray") || isArrayType;
2382
- let expr2 = wrapArray ? `z.array(${childExpr})` : childExpr;
2383
- expr2 = applyPresence2(expr2, decorators);
2384
- return expr2;
2385
- }
2386
- }
2387
- let base = baseFromType2(typeText, isArrayType, ctx, classFile);
2388
- const refinements = [];
2389
- if (has("IsString")) base = "z.string()";
2390
- if (has("IsBoolean")) base = "z.boolean()";
2391
- if (has("IsDate")) base = "z.coerce.date()";
2392
- if (has("IsNumber")) base = "z.number()";
2393
- if (has("IsInt")) base = "z.number().int()";
2394
- if (has("IsObject") && !has("ValidateNested")) base = "z.object({}).passthrough()";
2395
- if (has("Allow")) base = "z.unknown()";
2396
- if (has("IsEmail")) {
2397
- base = ensureStringBase(base);
2398
- refinements.push(`.email(${messageArg(dec("IsEmail"))})`);
2399
- }
2400
- if (has("IsUrl")) {
2401
- base = ensureStringBase(base);
2402
- refinements.push(`.url(${messageArg(dec("IsUrl"))})`);
2403
- }
2404
- if (has("IsUUID")) {
2405
- base = ensureStringBase(base);
2406
- refinements.push(`.uuid(${messageArg(dec("IsUUID"))})`);
2407
- }
2408
- if (has("Matches")) {
2409
- const re = firstArgText2(dec("Matches"));
2410
- if (re) {
2411
- base = ensureStringBase(base);
2412
- refinements.push(`.regex(${re})`);
2413
- }
2414
- }
2415
- if (has("MinLength")) {
2416
- const n = numericArg2(dec("MinLength"));
2417
- if (n !== null) refinements.push(`.min(${n})`);
2418
- }
2419
- if (has("MaxLength")) {
2420
- const n = numericArg2(dec("MaxLength"));
2421
- if (n !== null) refinements.push(`.max(${n})`);
2422
- }
2423
- if (has("Length")) {
2424
- const [min, max] = numericArgs2(dec("Length"));
2425
- if (min !== null) refinements.push(`.min(${min})`);
2426
- if (max !== null) refinements.push(`.max(${max})`);
2427
- }
2428
- if (has("Min")) {
2429
- const n = numericArg2(dec("Min"));
2430
- if (n !== null) refinements.push(`.min(${n})`);
2431
- }
2432
- if (has("Max")) {
2433
- const n = numericArg2(dec("Max"));
2434
- if (n !== null) refinements.push(`.max(${n})`);
2435
- }
2436
- if (has("IsPositive")) refinements.push(".positive()");
2437
- if (has("IsNegative")) refinements.push(".negative()");
2438
- if (has("IsNotEmpty") && isStringBase(base)) refinements.push(".min(1)");
2439
- if (has("IsEnum")) {
2440
- const enumExpr = enumSchemaFromDecorator2(dec("IsEnum"), classFile, ctx);
2441
- if (enumExpr) base = enumExpr;
2442
- }
2443
- if (has("IsIn")) {
2444
- const inExpr = inSchemaFromDecorator2(dec("IsIn"));
2445
- if (inExpr) base = inExpr;
2446
- }
2447
- for (const name of decorators.keys()) {
2448
- if (!KNOWN_DECORATORS2.has(name)) {
2449
- comments.push(`/* @${name}: not translatable to zod (server-only) */`);
2450
- if (!ctx.warnedDecorators.has(name)) {
2451
- ctx.warnedDecorators.add(name);
2452
- const msg = `@${name} is not translatable to zod and was skipped (server-only validation).`;
2453
- ctx.warnings.push(msg);
2454
- console.warn(`[nestjs-codegen/forms] ${msg}`);
2218
+ function classifyTypeNode(typeNode, sourceFile, project, opts) {
2219
+ if (Node4.isUnionTypeNode(typeNode)) {
2220
+ let nullable = false;
2221
+ const stringLits = [];
2222
+ const numberLits = [];
2223
+ const others = [];
2224
+ for (const member of typeNode.getTypeNodes()) {
2225
+ const kind = member.getKind();
2226
+ if (kind === SyntaxKind2.NullKeyword || kind === SyntaxKind2.UndefinedKeyword) {
2227
+ nullable = true;
2228
+ continue;
2229
+ }
2230
+ if (Node4.isLiteralTypeNode(member)) {
2231
+ const lit = member.getLiteral();
2232
+ if (Node4.isStringLiteral(lit)) {
2233
+ stringLits.push(lit.getLiteralValue());
2234
+ continue;
2235
+ }
2236
+ if (Node4.isNumericLiteral(lit)) {
2237
+ numberLits.push(lit.getText());
2238
+ continue;
2239
+ }
2240
+ if (lit.getKind() === SyntaxKind2.NullKeyword) {
2241
+ nullable = true;
2242
+ continue;
2243
+ }
2455
2244
  }
2245
+ others.push(member);
2246
+ }
2247
+ if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
2248
+ return markNullable({ kind: "string", enumValues: stringLits }, nullable);
2249
+ }
2250
+ if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
2251
+ return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
2456
2252
  }
2253
+ if (others.length === 1) {
2254
+ const inner = classifyTypeNode(others[0], sourceFile, project, opts);
2255
+ return markNullable(inner, nullable || inner.nullable === true);
2256
+ }
2257
+ return markNullable({ kind: "unknown" }, nullable);
2457
2258
  }
2458
- let expr = base + refinements.join("");
2459
- if (isArrayType && !expr.startsWith("z.array(")) {
2460
- expr = `z.array(${expr})`;
2259
+ switch (typeNode.getKind()) {
2260
+ case SyntaxKind2.StringKeyword:
2261
+ return { kind: "string" };
2262
+ case SyntaxKind2.NumberKeyword:
2263
+ return { kind: "number" };
2264
+ case SyntaxKind2.BooleanKeyword:
2265
+ return { kind: "boolean" };
2266
+ case SyntaxKind2.AnyKeyword:
2267
+ case SyntaxKind2.UnknownKeyword:
2268
+ return { kind: "unknown" };
2269
+ default:
2270
+ break;
2461
2271
  }
2462
- expr = applyPresence2(expr, decorators);
2463
- if (comments.length > 0) {
2464
- expr = `${expr} ${comments.join(" ")}`;
2272
+ if (Node4.isTypeReference(typeNode)) {
2273
+ const refName = typeNode.getTypeName().getText();
2274
+ if (refName === "Date") return { kind: "date" };
2275
+ if (refName === "Record" || refName === "Object") return { kind: "json" };
2276
+ const typeRef = opts?.resolveRef?.(refName) ?? null;
2277
+ const en = resolveEnumValues(refName, sourceFile, project);
2278
+ if (en) {
2279
+ const base = en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
2280
+ return typeRef ? { ...base, typeRef } : base;
2281
+ }
2282
+ if (typeRef) return { kind: "unknown", typeRef };
2283
+ return { kind: "unknown" };
2465
2284
  }
2466
- return expr;
2467
- }
2468
- function applyPresence2(expr, decorators) {
2469
- if (decorators.has("IsDefined")) return expr;
2470
- if (decorators.has("IsOptional")) return `${expr}.optional()`;
2471
- return expr;
2472
- }
2473
- function baseFromType2(typeText, isArrayType, ctx, classFile) {
2474
- const inner = isArrayType ? typeText.slice(0, -2).trim() : typeText;
2475
- switch (inner) {
2476
- case "string":
2477
- return "z.string()";
2478
- case "number":
2479
- return "z.number()";
2480
- case "boolean":
2481
- return "z.boolean()";
2482
- case "Date":
2483
- return "z.coerce.date()";
2484
- case "File":
2485
- case "Express.Multer.File":
2486
- return "z.instanceof(File)";
2487
- default:
2488
- return "z.unknown()";
2489
- }
2490
- }
2491
- function ensureStringBase(base) {
2492
- return isStringBase(base) ? base : "z.string()";
2493
- }
2494
- function isStringBase(base) {
2495
- return base.startsWith("z.string(");
2496
- }
2497
- function buildNestedReference2(className, fromFile, ctx) {
2498
- if (ctx.visiting.has(className) || ctx.depth >= 8) {
2499
- const reserved = ctx.emittedClasses.get(className) ?? aliasFor2(className, ctx);
2500
- ctx.emittedClasses.set(className, reserved);
2501
- ctx.recursiveSchemas.add(reserved);
2502
- if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
2503
- ctx.warnedDecorators.add(`recursive:${reserved}`);
2504
- const msg = `${className} is a recursive type and was not expanded; the generated form schema uses z.unknown() for it.`;
2505
- ctx.warnings.push(msg);
2506
- console.warn(`[nestjs-codegen/forms] ${msg}`);
2507
- }
2508
- return `z.lazy(() => ${reserved})`;
2509
- }
2510
- const existing = ctx.emittedClasses.get(className);
2511
- if (existing) return existing;
2512
- const schemaName = aliasFor2(className, ctx);
2513
- const resolved = findType(className, fromFile, ctx.project);
2514
- if (!resolved || resolved.kind !== "class") {
2515
- return "z.object({}).passthrough()";
2516
- }
2517
- ctx.emittedClasses.set(className, schemaName);
2518
- ctx.visiting.add(className);
2519
- ctx.depth += 1;
2520
- const childText = buildObjectSchema(resolved.decl, resolved.file, ctx);
2521
- ctx.depth -= 1;
2522
- ctx.visiting.delete(className);
2523
- ctx.namedNestedSchemas.set(schemaName, childText);
2524
- return schemaName;
2525
- }
2526
- function aliasFor2(className, ctx) {
2527
- const baseName = `${className}Schema`;
2528
- let candidate = baseName;
2529
- let i = 1;
2530
- const used = new Set(ctx.namedNestedSchemas.keys());
2531
- for (const v of ctx.emittedClasses.values()) used.add(v);
2532
- while (used.has(candidate)) {
2533
- candidate = `${baseName}_${i}`;
2534
- i += 1;
2535
- }
2536
- return candidate;
2537
- }
2538
- function firstArg2(decorator) {
2539
- return decorator?.getArguments()[0];
2540
- }
2541
- function firstArgText2(decorator) {
2542
- const arg = firstArg2(decorator);
2543
- return arg ? arg.getText() : null;
2544
- }
2545
- function numericArg2(decorator) {
2546
- const arg = firstArg2(decorator);
2547
- if (arg && Node4.isNumericLiteral(arg)) return arg.getText();
2548
- return null;
2549
- }
2550
- function numericArgs2(decorator) {
2551
- const args = decorator?.getArguments() ?? [];
2552
- const num = (n) => n && Node4.isNumericLiteral(n) ? n.getText() : null;
2553
- return [num(args[0]), num(args[1])];
2554
- }
2555
- function messageArg(decorator) {
2556
- const args = decorator?.getArguments() ?? [];
2557
- for (const arg of args) {
2558
- if (Node4.isObjectLiteralExpression(arg)) {
2559
- for (const prop of arg.getProperties()) {
2560
- if (Node4.isPropertyAssignment(prop) && prop.getName() === "message") {
2561
- const init = prop.getInitializer();
2562
- if (init && Node4.isStringLiteral(init)) {
2563
- return `{ message: ${init.getText()} }`;
2564
- }
2565
- }
2566
- }
2567
- }
2568
- }
2569
- return "";
2570
- }
2571
- function resolveTypeFactoryName2(decorator) {
2572
- const arg = firstArg2(decorator);
2573
- if (!arg) return null;
2574
- if (Node4.isArrowFunction(arg)) {
2575
- const body = arg.getBody();
2576
- if (Node4.isIdentifier(body)) return body.getText();
2577
- }
2578
- return null;
2579
- }
2580
- function singularClassName2(typeText) {
2581
- const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
2582
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
2583
- }
2584
- function enumSchemaFromDecorator2(decorator, classFile, ctx) {
2585
- const arg = firstArg2(decorator);
2586
- if (!arg) return null;
2587
- if (Node4.isIdentifier(arg)) {
2588
- const name = arg.getText();
2589
- const resolved = findType(name, classFile, ctx.project);
2590
- if (resolved && resolved.kind === "enum") {
2591
- return `z.enum([${resolved.members.join(", ")}])`;
2592
- }
2593
- 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().`;
2594
- if (!ctx.warnedDecorators.has(`IsEnum:${name}`)) {
2595
- ctx.warnedDecorators.add(`IsEnum:${name}`);
2596
- ctx.warnings.push(msg);
2597
- console.warn(`[nestjs-codegen/forms] ${msg}`);
2598
- }
2599
- return `z.unknown() /* @IsEnum(${name}): enum not resolvable to literals */`;
2600
- }
2601
- if (Node4.isObjectLiteralExpression(arg)) {
2602
- const values = [];
2603
- for (const p of arg.getProperties()) {
2604
- if (!Node4.isPropertyAssignment(p)) continue;
2605
- const init = p.getInitializer();
2606
- if (init && Node4.isStringLiteral(init)) values.push(init.getText());
2607
- }
2608
- if (values.length > 0) return `z.enum([${values.join(", ")}])`;
2609
- }
2610
- return null;
2611
- }
2612
- function inSchemaFromDecorator2(decorator) {
2613
- const arg = firstArg2(decorator);
2614
- if (arg && Node4.isArrayLiteralExpression(arg)) {
2615
- const elements = arg.getElements();
2616
- const allStrings = elements.every((e) => Node4.isStringLiteral(e));
2617
- if (allStrings && elements.length > 0) {
2618
- return `z.enum([${elements.map((e) => e.getText()).join(", ")}])`;
2619
- }
2620
- if (elements.length > 0) {
2621
- return `z.union([${elements.map((e) => `z.literal(${e.getText()})`).join(", ")}])`;
2622
- }
2623
- }
2624
- return null;
2625
- }
2626
-
2627
- // src/discovery/filter-for.ts
2628
- import {
2629
- Node as Node6
2630
- } from "ts-morph";
2631
-
2632
- // src/discovery/filter-field-types.ts
2633
- import {
2634
- Node as Node5,
2635
- SyntaxKind as SyntaxKind2
2636
- } from "ts-morph";
2637
-
2638
- // src/discovery/enum-resolution.ts
2639
- function resolveEnumValues(name, sourceFile, project) {
2640
- const resolved = findType(name, sourceFile, project);
2641
- if (!resolved || resolved.kind !== "enum") return null;
2642
- let numeric = true;
2643
- const values = resolved.members.map((m) => {
2644
- const parsed = JSON.parse(m);
2645
- if (typeof parsed === "string") numeric = false;
2646
- return String(parsed);
2647
- });
2648
- if (values.length === 0) return null;
2649
- return { values, numeric };
2650
- }
2651
-
2652
- // src/discovery/filter-field-types.ts
2653
- var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
2654
- var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
2655
- var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
2656
- var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
2657
- var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
2658
- function classifyTypeKeyword(raw) {
2659
- const t = raw.toLowerCase();
2660
- if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
2661
- if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
2662
- if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
2663
- if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
2664
- if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
2665
- return null;
2666
- }
2667
- function markNullable(r, nullable) {
2668
- return nullable ? { ...r, nullable: true } : r;
2669
- }
2670
- function classifyTypeNode(typeNode, sourceFile, project, opts) {
2671
- if (Node5.isUnionTypeNode(typeNode)) {
2672
- let nullable = false;
2673
- const stringLits = [];
2674
- const numberLits = [];
2675
- const others = [];
2676
- for (const member of typeNode.getTypeNodes()) {
2677
- const kind = member.getKind();
2678
- if (kind === SyntaxKind2.NullKeyword || kind === SyntaxKind2.UndefinedKeyword) {
2679
- nullable = true;
2680
- continue;
2681
- }
2682
- if (Node5.isLiteralTypeNode(member)) {
2683
- const lit = member.getLiteral();
2684
- if (Node5.isStringLiteral(lit)) {
2685
- stringLits.push(lit.getLiteralValue());
2686
- continue;
2687
- }
2688
- if (Node5.isNumericLiteral(lit)) {
2689
- numberLits.push(lit.getText());
2690
- continue;
2691
- }
2692
- if (lit.getKind() === SyntaxKind2.NullKeyword) {
2693
- nullable = true;
2694
- continue;
2695
- }
2696
- }
2697
- others.push(member);
2698
- }
2699
- if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
2700
- return markNullable({ kind: "string", enumValues: stringLits }, nullable);
2701
- }
2702
- if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
2703
- return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
2704
- }
2705
- if (others.length === 1) {
2706
- const inner = classifyTypeNode(others[0], sourceFile, project, opts);
2707
- return markNullable(inner, nullable || inner.nullable === true);
2708
- }
2709
- return markNullable({ kind: "unknown" }, nullable);
2710
- }
2711
- switch (typeNode.getKind()) {
2712
- case SyntaxKind2.StringKeyword:
2713
- return { kind: "string" };
2714
- case SyntaxKind2.NumberKeyword:
2715
- return { kind: "number" };
2716
- case SyntaxKind2.BooleanKeyword:
2717
- return { kind: "boolean" };
2718
- case SyntaxKind2.AnyKeyword:
2719
- case SyntaxKind2.UnknownKeyword:
2720
- return { kind: "unknown" };
2721
- default:
2722
- break;
2723
- }
2724
- if (Node5.isTypeReference(typeNode)) {
2725
- const refName = typeNode.getTypeName().getText();
2726
- if (refName === "Date") return { kind: "date" };
2727
- if (refName === "Record" || refName === "Object") return { kind: "json" };
2728
- const typeRef = opts?.resolveRef?.(refName) ?? null;
2729
- const en = resolveEnumValues(refName, sourceFile, project);
2730
- if (en) {
2731
- const base = en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
2732
- return typeRef ? { ...base, typeRef } : base;
2733
- }
2734
- if (typeRef) return { kind: "unknown", typeRef };
2735
- return { kind: "unknown" };
2736
- }
2737
- if (Node5.isTypeLiteral(typeNode)) return { kind: "json" };
2738
- return { kind: "unknown" };
2285
+ if (Node4.isTypeLiteral(typeNode)) return { kind: "json" };
2286
+ return { kind: "unknown" };
2739
2287
  }
2740
2288
  function enumFromDecoratorArgs(args, sourceFile, project) {
2741
2289
  for (const arg of args) {
2742
- if (Node5.isArrowFunction(arg)) {
2290
+ if (Node4.isArrowFunction(arg)) {
2743
2291
  const body = arg.getBody();
2744
- if (Node5.isIdentifier(body)) {
2292
+ if (Node4.isIdentifier(body)) {
2745
2293
  const en = resolveEnumValues(body.getText(), sourceFile, project);
2746
2294
  if (en) return en;
2747
2295
  }
2748
2296
  }
2749
- if (Node5.isObjectLiteralExpression(arg)) {
2297
+ if (Node4.isObjectLiteralExpression(arg)) {
2750
2298
  const itemsProp = arg.getProperty("items");
2751
- if (itemsProp && Node5.isPropertyAssignment(itemsProp)) {
2299
+ if (itemsProp && Node4.isPropertyAssignment(itemsProp)) {
2752
2300
  const init = itemsProp.getInitializer();
2753
- if (init && Node5.isArrowFunction(init)) {
2301
+ if (init && Node4.isArrowFunction(init)) {
2754
2302
  const body = init.getBody();
2755
- if (Node5.isIdentifier(body)) {
2303
+ if (Node4.isIdentifier(body)) {
2756
2304
  const en = resolveEnumValues(body.getText(), sourceFile, project);
2757
2305
  if (en) return en;
2758
2306
  }
@@ -2775,7 +2323,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
2775
2323
  return { kind: "string" };
2776
2324
  }
2777
2325
  for (const arg of args) {
2778
- if (Node5.isStringLiteral(arg)) {
2326
+ if (Node4.isStringLiteral(arg)) {
2779
2327
  const raw = arg.getLiteralValue();
2780
2328
  const kind = classifyTypeKeyword(raw);
2781
2329
  if (kind) {
@@ -2783,11 +2331,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
2783
2331
  return { kind };
2784
2332
  }
2785
2333
  }
2786
- if (Node5.isObjectLiteralExpression(arg)) {
2334
+ if (Node4.isObjectLiteralExpression(arg)) {
2787
2335
  const enumProp = arg.getProperty("enum");
2788
- if (enumProp && Node5.isPropertyAssignment(enumProp)) {
2336
+ if (enumProp && Node4.isPropertyAssignment(enumProp)) {
2789
2337
  const init = enumProp.getInitializer();
2790
- if (init && Node5.isIdentifier(init)) {
2338
+ if (init && Node4.isIdentifier(init)) {
2791
2339
  const en = resolveEnumValues(init.getText(), sourceFile, project);
2792
2340
  if (en) {
2793
2341
  return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
@@ -2796,9 +2344,9 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
2796
2344
  }
2797
2345
  }
2798
2346
  const typeProp = arg.getProperty("type");
2799
- if (typeProp && Node5.isPropertyAssignment(typeProp)) {
2347
+ if (typeProp && Node4.isPropertyAssignment(typeProp)) {
2800
2348
  const init = typeProp.getInitializer();
2801
- if (init && Node5.isStringLiteral(init)) {
2349
+ if (init && Node4.isStringLiteral(init)) {
2802
2350
  const kind = classifyTypeKeyword(init.getLiteralValue());
2803
2351
  if (kind) return { kind };
2804
2352
  }
@@ -2834,7 +2382,7 @@ function toFilterFieldType(name, r) {
2834
2382
 
2835
2383
  // src/discovery/filter-for.ts
2836
2384
  function classifyFilterForHint(typeInit) {
2837
- if (Node6.isStringLiteral(typeInit)) {
2385
+ if (Node5.isStringLiteral(typeInit)) {
2838
2386
  switch (typeInit.getLiteralValue()) {
2839
2387
  case "string":
2840
2388
  return { kind: "string" };
@@ -2848,10 +2396,10 @@ function classifyFilterForHint(typeInit) {
2848
2396
  return null;
2849
2397
  }
2850
2398
  }
2851
- if (Node6.isArrayLiteralExpression(typeInit)) {
2399
+ if (Node5.isArrayLiteralExpression(typeInit)) {
2852
2400
  const values = [];
2853
2401
  for (const el of typeInit.getElements()) {
2854
- if (!Node6.isStringLiteral(el)) return null;
2402
+ if (!Node5.isStringLiteral(el)) return null;
2855
2403
  values.push(el.getLiteralValue());
2856
2404
  }
2857
2405
  if (values.length === 0) return null;
@@ -2886,11 +2434,11 @@ function extractFilterForHints(classDecl, project) {
2886
2434
  if (!filterForDec) continue;
2887
2435
  const args = filterForDec.getArguments();
2888
2436
  const keyArg = args[0];
2889
- const inputKey = keyArg && Node6.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
2437
+ const inputKey = keyArg && Node5.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
2890
2438
  const optsArg = args[1];
2891
- if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
2439
+ if (optsArg && Node5.isObjectLiteralExpression(optsArg)) {
2892
2440
  const typeProp = optsArg.getProperty("type");
2893
- if (typeProp && Node6.isPropertyAssignment(typeProp)) {
2441
+ if (typeProp && Node5.isPropertyAssignment(typeProp)) {
2894
2442
  const typeInit = typeProp.getInitializer();
2895
2443
  if (typeInit) {
2896
2444
  const classified = classifyFilterForHint(typeInit);
@@ -2913,14 +2461,14 @@ function extractApplyFilterInfo(method, sourceFile, project) {
2913
2461
  const args = filterDecorator.getArguments();
2914
2462
  if (args.length === 0) continue;
2915
2463
  const filterClassArg = args[0];
2916
- if (!filterClassArg || !Node6.isIdentifier(filterClassArg)) continue;
2464
+ if (!filterClassArg || !Node5.isIdentifier(filterClassArg)) continue;
2917
2465
  let source = "query";
2918
2466
  const optionsArg = args[1];
2919
- if (optionsArg && Node6.isObjectLiteralExpression(optionsArg)) {
2467
+ if (optionsArg && Node5.isObjectLiteralExpression(optionsArg)) {
2920
2468
  const sourceProp = optionsArg.getProperty("source");
2921
- if (sourceProp && Node6.isPropertyAssignment(sourceProp)) {
2469
+ if (sourceProp && Node5.isPropertyAssignment(sourceProp)) {
2922
2470
  const init = sourceProp.getInitializer();
2923
- if (init && Node6.isStringLiteral(init) && init.getLiteralValue() === "body") {
2471
+ if (init && Node5.isStringLiteral(init) && init.getLiteralValue() === "body") {
2924
2472
  source = "body";
2925
2473
  }
2926
2474
  }
@@ -2983,22 +2531,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
2983
2531
  const args = dec.getArguments();
2984
2532
  if (args.length === 0) continue;
2985
2533
  const arg = args[0];
2986
- if (Node6.isObjectLiteralExpression(arg)) {
2534
+ if (Node5.isObjectLiteralExpression(arg)) {
2987
2535
  const entityProp = arg.getProperty("entity");
2988
- if (entityProp && Node6.isPropertyAssignment(entityProp)) {
2536
+ if (entityProp && Node5.isPropertyAssignment(entityProp)) {
2989
2537
  const init = entityProp.getInitializer();
2990
- if (init && Node6.isArrowFunction(init)) {
2538
+ if (init && Node5.isArrowFunction(init)) {
2991
2539
  const body = init.getBody();
2992
- if (Node6.isIdentifier(body)) {
2540
+ if (Node5.isIdentifier(body)) {
2993
2541
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
2994
2542
  if (resolved?.kind === "class") return resolved.decl;
2995
2543
  }
2996
2544
  }
2997
2545
  }
2998
2546
  }
2999
- if (Node6.isArrowFunction(arg)) {
2547
+ if (Node5.isArrowFunction(arg)) {
3000
2548
  const body = arg.getBody();
3001
- if (Node6.isIdentifier(body)) {
2549
+ if (Node5.isIdentifier(body)) {
3002
2550
  const resolved = findType(body.getText(), prop.getSourceFile(), project);
3003
2551
  if (resolved?.kind === "class") return resolved.decl;
3004
2552
  }
@@ -3022,11 +2570,11 @@ function extractFilterableEntityFields(filterClass, project) {
3022
2570
  const args = filterableDecorator.getArguments();
3023
2571
  if (args.length === 0) return [];
3024
2572
  const optionsArg = args[0];
3025
- if (!Node6.isObjectLiteralExpression(optionsArg)) return [];
2573
+ if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
3026
2574
  const entityProp = optionsArg.getProperty("entity");
3027
- if (!entityProp || !Node6.isPropertyAssignment(entityProp)) return [];
2575
+ if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return [];
3028
2576
  const entityInit = entityProp.getInitializer();
3029
- if (!entityInit || !Node6.isIdentifier(entityInit)) return [];
2577
+ if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
3030
2578
  const entityName = entityInit.getText();
3031
2579
  const filterSourceFile = filterClass.getSourceFile();
3032
2580
  const resolvedEntity = findType(entityName, filterSourceFile, project);
@@ -3042,17 +2590,17 @@ function extractFilterableEntityFields(filterClass, project) {
3042
2590
  const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
3043
2591
  if (relationsDecorator) {
3044
2592
  const relArgs = relationsDecorator.getArguments();
3045
- if (relArgs.length > 0 && Node6.isObjectLiteralExpression(relArgs[0])) {
2593
+ if (relArgs.length > 0 && Node5.isObjectLiteralExpression(relArgs[0])) {
3046
2594
  for (const relProp of relArgs[0].getProperties()) {
3047
- if (!Node6.isPropertyAssignment(relProp)) continue;
2595
+ if (!Node5.isPropertyAssignment(relProp)) continue;
3048
2596
  const relInit = relProp.getInitializer();
3049
- if (!relInit || !Node6.isObjectLiteralExpression(relInit)) continue;
2597
+ if (!relInit || !Node5.isObjectLiteralExpression(relInit)) continue;
3050
2598
  const keysProp = relInit.getProperty("keys");
3051
- if (!keysProp || !Node6.isPropertyAssignment(keysProp)) continue;
2599
+ if (!keysProp || !Node5.isPropertyAssignment(keysProp)) continue;
3052
2600
  const keysInit = keysProp.getInitializer();
3053
- if (!keysInit || !Node6.isArrayLiteralExpression(keysInit)) continue;
2601
+ if (!keysInit || !Node5.isArrayLiteralExpression(keysInit)) continue;
3054
2602
  for (const el of keysInit.getElements()) {
3055
- if (Node6.isStringLiteral(el)) {
2603
+ if (Node5.isStringLiteral(el)) {
3056
2604
  fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
3057
2605
  }
3058
2606
  }
@@ -3062,267 +2610,65 @@ function extractFilterableEntityFields(filterClass, project) {
3062
2610
  return fields;
3063
2611
  }
3064
2612
 
3065
- // src/discovery/contracts-fast.ts
3066
- async function discoverContractsFast(opts) {
3067
- const { cwd, glob, tsconfig } = opts;
3068
- const tsconfigPath = tsconfig ? resolve3(tsconfig) : join10(cwd, "tsconfig.json");
3069
- let project;
3070
- try {
3071
- project = new Project3({
3072
- tsConfigFilePath: tsconfigPath,
3073
- skipAddingFilesFromTsConfig: true,
3074
- skipLoadingLibFiles: true,
3075
- skipFileDependencyResolution: true
3076
- });
3077
- } catch {
3078
- project = new Project3({
3079
- skipAddingFilesFromTsConfig: true,
3080
- skipLoadingLibFiles: true,
3081
- skipFileDependencyResolution: true,
3082
- compilerOptions: {
3083
- allowJs: true,
3084
- resolveJsonModule: false,
3085
- strict: false
3086
- }
3087
- });
2613
+ // src/discovery/dto-type-resolver.ts
2614
+ var WRAPPER_TYPES = {
2615
+ // MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
2616
+ // wrappers around related entities; the wire shape is just the referenced
2617
+ // entity. Unwrap to the type argument.
2618
+ Ref: "unwrap",
2619
+ Reference: "unwrap",
2620
+ LoadedReference: "unwrap",
2621
+ IdentifiedReference: "unwrap",
2622
+ // MikroORM Opt<T> is a marker, Loaded<T, ...> is a wrapper; both reduce to T.
2623
+ Opt: "unwrap",
2624
+ Loaded: "unwrap",
2625
+ // Promise<T> — unwrap
2626
+ Promise: "unwrap",
2627
+ // MikroORM Collection<T> serializes as an array of T on the wire.
2628
+ Collection: "arrayOf",
2629
+ // Array<T> generic form
2630
+ Array: "arrayOf"
2631
+ };
2632
+ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
2633
+ "Record",
2634
+ "Omit",
2635
+ "Pick",
2636
+ "Partial",
2637
+ "Required",
2638
+ "Readonly",
2639
+ "Map",
2640
+ "Set"
2641
+ ]);
2642
+ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
2643
+ if (depth <= 0) return "unknown";
2644
+ if (Node6.isArrayTypeNode(typeNode)) {
2645
+ const elementType = typeNode.getElementTypeNode();
2646
+ return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
3088
2647
  }
3089
- const files = await fg2(glob, { cwd, absolute: true, onlyFiles: true });
3090
- for (const f of files) {
3091
- project.addSourceFileAtPath(f);
2648
+ if (Node6.isUnionTypeNode(typeNode)) {
2649
+ return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
3092
2650
  }
3093
- const routes = [];
3094
- const prevCtx = setDiscoveryContext({
3095
- projectRoot: cwd,
3096
- tsconfigPaths: loadTsconfigPaths(tsconfigPath)
3097
- });
3098
- try {
3099
- for (const sourceFile of project.getSourceFiles()) {
3100
- routes.push(...extractFromSourceFile(sourceFile, project));
3101
- }
3102
- } finally {
3103
- restoreDiscoveryContext(prevCtx);
2651
+ if (Node6.isIntersectionTypeNode(typeNode)) {
2652
+ return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
3104
2653
  }
3105
- return routes;
3106
- }
3107
- function zodAstToTs(node) {
3108
- if (!Node7.isCallExpression(node)) return "unknown";
3109
- const expr = node.getExpression();
3110
- if (Node7.isPropertyAccessExpression(expr)) {
3111
- const methodName = expr.getName();
3112
- const receiver = expr.getExpression();
3113
- if (methodName === "optional") {
3114
- return `${zodAstToTs(receiver)} | undefined`;
3115
- }
3116
- if (methodName === "nullable") {
3117
- return `${zodAstToTs(receiver)} | null`;
3118
- }
3119
- const args = node.getArguments();
3120
- switch (methodName) {
3121
- case "string":
3122
- return "string";
3123
- case "number":
3124
- return "number";
3125
- case "boolean":
3126
- return "boolean";
3127
- case "unknown":
3128
- return "unknown";
3129
- case "any":
3130
- return "unknown";
3131
- case "literal": {
3132
- const lit = args[0];
3133
- if (!lit) return "unknown";
3134
- if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
3135
- if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
3136
- if (lit.getKind() === SyntaxKind3.TrueKeyword) return "true";
3137
- if (lit.getKind() === SyntaxKind3.FalseKeyword) return "false";
3138
- return "unknown";
3139
- }
3140
- case "enum": {
3141
- const arrArg = args[0];
3142
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
3143
- const members = arrArg.getElements().map(
3144
- (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
3145
- );
3146
- return members.join(" | ");
3147
- }
3148
- case "array": {
3149
- const inner = args[0];
3150
- if (!inner) return "unknown";
3151
- return `Array<${zodAstToTs(inner)}>`;
3152
- }
3153
- case "object": {
3154
- const objArg = args[0];
3155
- if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
3156
- const lines = [];
3157
- for (const prop of objArg.getProperties()) {
3158
- if (!Node7.isPropertyAssignment(prop)) continue;
3159
- const key = prop.getName();
3160
- const valNode = prop.getInitializer();
3161
- if (!valNode) continue;
3162
- const tsType = zodAstToTs(valNode);
3163
- const isOpt = isOptionalChain(valNode);
3164
- lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
3165
- }
3166
- return `{ ${lines.join("; ")} }`;
3167
- }
3168
- case "union": {
3169
- const arrArg = args[0];
3170
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
3171
- return arrArg.getElements().map(zodAstToTs).join(" | ");
3172
- }
3173
- case "record": {
3174
- const valArg = args.length === 1 ? args[0] : args[1];
3175
- if (!valArg) return "unknown";
3176
- return `Record<string, ${zodAstToTs(valArg)}>`;
3177
- }
3178
- case "tuple": {
3179
- const arrArg = args[0];
3180
- if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
3181
- return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
3182
- }
3183
- default:
3184
- return "unknown";
3185
- }
3186
- }
3187
- return "unknown";
3188
- }
3189
- function isOptionalChain(node) {
3190
- if (!Node7.isCallExpression(node)) return false;
3191
- const expr = node.getExpression();
3192
- return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
3193
- }
3194
- function decoratorStringArg(decoratorExpr) {
3195
- if (!decoratorExpr) return void 0;
3196
- if (Node7.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3197
- if (Node7.isArrayLiteralExpression(decoratorExpr)) {
3198
- const first = decoratorExpr.getElements()[0];
3199
- if (first && Node7.isStringLiteral(first)) return first.getLiteralValue();
3200
- }
3201
- return void 0;
3202
- }
3203
- function parseDefineContractCall(callExpr) {
3204
- if (!Node7.isCallExpression(callExpr)) return null;
3205
- const callee = callExpr.getExpression();
3206
- const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
3207
- if (calleeName !== "defineContract") return null;
3208
- const args = callExpr.getArguments();
3209
- const optsArg = args[0];
3210
- if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
3211
- let query = null;
3212
- let body = null;
3213
- let response = "unknown";
3214
- let bodyZodText = null;
3215
- let queryZodText = null;
3216
- for (const prop of optsArg.getProperties()) {
3217
- if (!Node7.isPropertyAssignment(prop)) continue;
3218
- const propName = prop.getName();
3219
- const val = prop.getInitializer();
3220
- if (!val) continue;
3221
- if (propName === "query") {
3222
- query = zodAstToTs(val);
3223
- queryZodText = val.getText();
3224
- } else if (propName === "body") {
3225
- body = zodAstToTs(val);
3226
- bodyZodText = val.getText();
3227
- } else if (propName === "response") {
3228
- response = zodAstToTs(val);
3229
- }
3230
- }
3231
- return { query, body, response, bodyZodText, queryZodText };
3232
- }
3233
- function deriveClassSegment(className) {
3234
- const noSuffix = className.replace(/Controller$/, "");
3235
- if (!noSuffix) {
3236
- throw new Error(
3237
- `Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
3238
- );
3239
- }
3240
- return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
3241
- }
3242
- function resolveRouteName(className, methodName, classAs, methodAs) {
3243
- const classPortion = classAs ?? deriveClassSegment(className);
3244
- const methodPortion = methodAs ?? methodName;
3245
- return `${classPortion}.${methodPortion}`;
3246
- }
3247
- function joinPaths(prefix, suffix) {
3248
- if (!prefix && !suffix) return "/";
3249
- if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
3250
- if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
3251
- const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
3252
- const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
3253
- const combined = p + s;
3254
- return combined === "" ? "/" : combined;
3255
- }
3256
- function extractParams(path) {
3257
- const matches = path.matchAll(/:(\w+)/g);
3258
- return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
3259
- }
3260
- function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
3261
- if (depth <= 0) return "unknown";
3262
- if (Node7.isArrayTypeNode(typeNode)) {
3263
- const elementType = typeNode.getElementTypeNode();
3264
- return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
3265
- }
3266
- if (Node7.isUnionTypeNode(typeNode)) {
3267
- return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
3268
- }
3269
- if (Node7.isIntersectionTypeNode(typeNode)) {
3270
- return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
3271
- }
3272
- if (Node7.isParenthesizedTypeNode(typeNode)) {
2654
+ if (Node6.isParenthesizedTypeNode(typeNode)) {
3273
2655
  return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
3274
2656
  }
3275
- if (Node7.isTypeReference(typeNode)) {
2657
+ if (Node6.isTypeReference(typeNode)) {
3276
2658
  const typeName = typeNode.getTypeName();
3277
- const name = Node7.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
2659
+ const name = Node6.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
3278
2660
  if (name === "string" || name === "number" || name === "boolean") return name;
3279
2661
  if (name === "Date") return "string";
3280
2662
  if (name === "unknown" || name === "any" || name === "void") return "unknown";
3281
2663
  if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
3282
2664
  return "unknown";
3283
- if (name === "Ref" || name === "Reference" || name === "LoadedReference" || name === "IdentifiedReference") {
3284
- const typeArgs = typeNode.getTypeArguments();
3285
- const firstTypeArg = typeArgs[0];
3286
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
3287
- return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
3288
- }
3289
- return "unknown";
2665
+ const wrapperMode = WRAPPER_TYPES[name];
2666
+ if (wrapperMode) {
2667
+ return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
3290
2668
  }
3291
- if (name === "Collection") {
3292
- const typeArgs = typeNode.getTypeArguments();
3293
- const firstTypeArg = typeArgs[0];
3294
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
3295
- return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
3296
- }
3297
- return "Array<unknown>";
3298
- }
3299
- if (name === "Opt" || name === "Loaded") {
3300
- const typeArgs = typeNode.getTypeArguments();
3301
- const firstTypeArg = typeArgs[0];
3302
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
3303
- return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
3304
- }
3305
- return "unknown";
3306
- }
3307
- if (name === "Array") {
3308
- const typeArgs = typeNode.getTypeArguments();
3309
- const firstTypeArg = typeArgs[0];
3310
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
3311
- return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
3312
- }
3313
- return "Array<unknown>";
3314
- }
3315
- if (["Record", "Omit", "Pick", "Partial", "Required", "Readonly", "Map", "Set"].includes(name)) {
2669
+ if (PASSTHROUGH_UTILITY.has(name)) {
3316
2670
  return typeNode.getText();
3317
2671
  }
3318
- if (name === "Promise") {
3319
- const typeArgs = typeNode.getTypeArguments();
3320
- const firstTypeArg = typeArgs[0];
3321
- if (typeArgs.length > 0 && firstTypeArg !== void 0) {
3322
- return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
3323
- }
3324
- return "unknown";
3325
- }
3326
2672
  const resolved = findType(name, sourceFile, project);
3327
2673
  if (resolved) {
3328
2674
  return expandTypeDecl(resolved, project, depth - 1);
@@ -3338,6 +2684,15 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
3338
2684
  if (kind === SyntaxKind3.AnyKeyword) return "unknown";
3339
2685
  return typeNode.getText();
3340
2686
  }
2687
+ function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
2688
+ const typeArgs = typeNode.getTypeArguments();
2689
+ const firstTypeArg = typeArgs[0];
2690
+ if (typeArgs.length > 0 && firstTypeArg !== void 0) {
2691
+ const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
2692
+ return mode === "arrayOf" ? `Array<${inner}>` : inner;
2693
+ }
2694
+ return mode === "arrayOf" ? "Array<unknown>" : "unknown";
2695
+ }
3341
2696
  function expandTypeDecl(result, project, depth) {
3342
2697
  if (depth < 0) return "unknown";
3343
2698
  switch (result.kind) {
@@ -3403,7 +2758,7 @@ function extractParamsType(method, sourceFile, project) {
3403
2758
  const paramArgs = paramDecorator.getArguments();
3404
2759
  if (paramArgs.length === 0) continue;
3405
2760
  const nameArg = paramArgs[0];
3406
- if (!Node7.isStringLiteral(nameArg)) continue;
2761
+ if (!Node6.isStringLiteral(nameArg)) continue;
3407
2762
  const paramName = nameArg.getLiteralValue();
3408
2763
  const typeNode = param.getTypeNode();
3409
2764
  const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
@@ -3416,13 +2771,13 @@ function extractResponseType(method, sourceFile, project) {
3416
2771
  if (apiResponseDecorator) {
3417
2772
  const args = apiResponseDecorator.getArguments();
3418
2773
  const optsArg = args[0];
3419
- if (optsArg && Node7.isObjectLiteralExpression(optsArg)) {
2774
+ if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
3420
2775
  for (const prop of optsArg.getProperties()) {
3421
- if (!Node7.isPropertyAssignment(prop)) continue;
2776
+ if (!Node6.isPropertyAssignment(prop)) continue;
3422
2777
  if (prop.getName() !== "type") continue;
3423
2778
  const val = prop.getInitializer();
3424
2779
  if (!val) continue;
3425
- if (Node7.isArrayLiteralExpression(val)) {
2780
+ if (Node6.isArrayLiteralExpression(val)) {
3426
2781
  const elements = val.getElements();
3427
2782
  const firstEl = elements[0];
3428
2783
  if (elements.length > 0 && firstEl !== void 0) {
@@ -3442,7 +2797,7 @@ function extractResponseType(method, sourceFile, project) {
3442
2797
  return "unknown";
3443
2798
  }
3444
2799
  function resolveIdentifierToClassType(node, sourceFile, project, depth) {
3445
- if (!Node7.isIdentifier(node)) return "unknown";
2800
+ if (!Node6.isIdentifier(node)) return "unknown";
3446
2801
  const name = node.getText();
3447
2802
  const resolved = findType(name, sourceFile, project);
3448
2803
  if (resolved) {
@@ -3489,11 +2844,11 @@ function extractDtoContract(method, sourceFile, project) {
3489
2844
  if (apiResp) {
3490
2845
  const args = apiResp.getArguments();
3491
2846
  const optsArg = args[0];
3492
- if (optsArg && Node7.isObjectLiteralExpression(optsArg)) {
2847
+ if (optsArg && Node6.isObjectLiteralExpression(optsArg)) {
3493
2848
  for (const prop of optsArg.getProperties()) {
3494
- if (Node7.isPropertyAssignment(prop) && prop.getName() === "type") {
2849
+ if (Node6.isPropertyAssignment(prop) && prop.getName() === "type") {
3495
2850
  const val = prop.getInitializer();
3496
- if (val && Node7.isIdentifier(val)) {
2851
+ if (val && Node6.isIdentifier(val)) {
3497
2852
  const name = val.getText();
3498
2853
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
3499
2854
  if (localDecl?.isExported()) {
@@ -3510,27 +2865,18 @@ function extractDtoContract(method, sourceFile, project) {
3510
2865
  }
3511
2866
  }
3512
2867
  }
3513
- let bodyZodText = null;
3514
- let queryZodText = null;
3515
2868
  let bodySchema = null;
3516
2869
  let querySchema = null;
3517
- const formNested = {};
3518
2870
  const formWarnings = [];
3519
2871
  const bodyClass = resolveParamClass(method, "Body", sourceFile, project);
3520
2872
  if (bodyClass) {
3521
- const result = extractZodFromDto(bodyClass.decl, bodyClass.file, project);
3522
- bodyZodText = result.schemaText;
3523
- for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
3524
- formWarnings.push(...result.warnings);
3525
2873
  bodySchema = extractSchemaFromDto(bodyClass.decl, bodyClass.file, project);
2874
+ formWarnings.push(...bodySchema.warnings);
3526
2875
  }
3527
2876
  const queryClass = resolveParamClass(method, "Query", sourceFile, project);
3528
2877
  if (queryClass) {
3529
- const result = extractZodFromDto(queryClass.decl, queryClass.file, project);
3530
- queryZodText = result.schemaText;
3531
- for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
3532
- formWarnings.push(...result.warnings);
3533
2878
  querySchema = extractSchemaFromDto(queryClass.decl, queryClass.file, project);
2879
+ formWarnings.push(...querySchema.warnings);
3534
2880
  }
3535
2881
  return {
3536
2882
  query,
@@ -3543,9 +2889,6 @@ function extractDtoContract(method, sourceFile, project) {
3543
2889
  filterFields: filterInfo?.fieldNames ?? null,
3544
2890
  filterFieldTypes: filterInfo?.fieldTypes ?? null,
3545
2891
  filterSource: filterInfo?.source ?? null,
3546
- bodyZodText,
3547
- queryZodText,
3548
- formNestedSchemas: Object.keys(formNested).length > 0 ? formNested : null,
3549
2892
  formWarnings,
3550
2893
  bodySchema,
3551
2894
  querySchema
@@ -3565,6 +2908,201 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
3565
2908
  }
3566
2909
  return null;
3567
2910
  }
2911
+
2912
+ // src/discovery/zod-ast-to-ts.ts
2913
+ import { Node as Node7, SyntaxKind as SyntaxKind4 } from "ts-morph";
2914
+ function zodAstToTs(node) {
2915
+ if (!Node7.isCallExpression(node)) return "unknown";
2916
+ const expr = node.getExpression();
2917
+ if (Node7.isPropertyAccessExpression(expr)) {
2918
+ const methodName = expr.getName();
2919
+ const receiver = expr.getExpression();
2920
+ if (methodName === "optional") {
2921
+ return `${zodAstToTs(receiver)} | undefined`;
2922
+ }
2923
+ if (methodName === "nullable") {
2924
+ return `${zodAstToTs(receiver)} | null`;
2925
+ }
2926
+ const args = node.getArguments();
2927
+ switch (methodName) {
2928
+ case "string":
2929
+ return "string";
2930
+ case "number":
2931
+ return "number";
2932
+ case "boolean":
2933
+ return "boolean";
2934
+ case "unknown":
2935
+ return "unknown";
2936
+ case "any":
2937
+ return "unknown";
2938
+ case "literal": {
2939
+ const lit = args[0];
2940
+ if (!lit) return "unknown";
2941
+ if (Node7.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
2942
+ if (Node7.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
2943
+ if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
2944
+ if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
2945
+ return "unknown";
2946
+ }
2947
+ case "enum": {
2948
+ const arrArg = args[0];
2949
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2950
+ const members = arrArg.getElements().map(
2951
+ (el) => Node7.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
2952
+ );
2953
+ return members.join(" | ");
2954
+ }
2955
+ case "array": {
2956
+ const inner = args[0];
2957
+ if (!inner) return "unknown";
2958
+ return `Array<${zodAstToTs(inner)}>`;
2959
+ }
2960
+ case "object": {
2961
+ const objArg = args[0];
2962
+ if (!objArg || !Node7.isObjectLiteralExpression(objArg)) return "unknown";
2963
+ const lines = [];
2964
+ for (const prop of objArg.getProperties()) {
2965
+ if (!Node7.isPropertyAssignment(prop)) continue;
2966
+ const key = prop.getName();
2967
+ const valNode = prop.getInitializer();
2968
+ if (!valNode) continue;
2969
+ const tsType = zodAstToTs(valNode);
2970
+ const isOpt = isOptionalChain(valNode);
2971
+ lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
2972
+ }
2973
+ return `{ ${lines.join("; ")} }`;
2974
+ }
2975
+ case "union": {
2976
+ const arrArg = args[0];
2977
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2978
+ return arrArg.getElements().map(zodAstToTs).join(" | ");
2979
+ }
2980
+ case "record": {
2981
+ const valArg = args.length === 1 ? args[0] : args[1];
2982
+ if (!valArg) return "unknown";
2983
+ return `Record<string, ${zodAstToTs(valArg)}>`;
2984
+ }
2985
+ case "tuple": {
2986
+ const arrArg = args[0];
2987
+ if (!arrArg || !Node7.isArrayLiteralExpression(arrArg)) return "unknown";
2988
+ return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
2989
+ }
2990
+ default:
2991
+ return "unknown";
2992
+ }
2993
+ }
2994
+ return "unknown";
2995
+ }
2996
+ function isOptionalChain(node) {
2997
+ if (!Node7.isCallExpression(node)) return false;
2998
+ const expr = node.getExpression();
2999
+ return Node7.isPropertyAccessExpression(expr) && expr.getName() === "optional";
3000
+ }
3001
+ function parseDefineContractCall(callExpr) {
3002
+ if (!Node7.isCallExpression(callExpr)) return null;
3003
+ const callee = callExpr.getExpression();
3004
+ const calleeName = Node7.isIdentifier(callee) ? callee.getText() : Node7.isPropertyAccessExpression(callee) ? callee.getName() : "";
3005
+ if (calleeName !== "defineContract") return null;
3006
+ const args = callExpr.getArguments();
3007
+ const optsArg = args[0];
3008
+ if (!optsArg || !Node7.isObjectLiteralExpression(optsArg)) return null;
3009
+ let query = null;
3010
+ let body = null;
3011
+ let response = "unknown";
3012
+ let bodyZodText = null;
3013
+ let queryZodText = null;
3014
+ for (const prop of optsArg.getProperties()) {
3015
+ if (!Node7.isPropertyAssignment(prop)) continue;
3016
+ const propName = prop.getName();
3017
+ const val = prop.getInitializer();
3018
+ if (!val) continue;
3019
+ if (propName === "query") {
3020
+ query = zodAstToTs(val);
3021
+ queryZodText = val.getText();
3022
+ } else if (propName === "body") {
3023
+ body = zodAstToTs(val);
3024
+ bodyZodText = val.getText();
3025
+ } else if (propName === "response") {
3026
+ response = zodAstToTs(val);
3027
+ }
3028
+ }
3029
+ return { query, body, response, bodyZodText, queryZodText };
3030
+ }
3031
+
3032
+ // src/discovery/contracts-fast.ts
3033
+ async function discoverContractsFast(opts) {
3034
+ const { cwd, glob, tsconfig } = opts;
3035
+ const tsconfigPath = tsconfig ? resolve3(tsconfig) : join11(cwd, "tsconfig.json");
3036
+ let project;
3037
+ try {
3038
+ project = new Project3({
3039
+ tsConfigFilePath: tsconfigPath,
3040
+ skipAddingFilesFromTsConfig: true,
3041
+ skipLoadingLibFiles: true,
3042
+ skipFileDependencyResolution: true
3043
+ });
3044
+ } catch {
3045
+ project = new Project3({
3046
+ skipAddingFilesFromTsConfig: true,
3047
+ skipLoadingLibFiles: true,
3048
+ skipFileDependencyResolution: true,
3049
+ compilerOptions: {
3050
+ allowJs: true,
3051
+ resolveJsonModule: false,
3052
+ strict: false
3053
+ }
3054
+ });
3055
+ }
3056
+ const files = await fg2(glob, { cwd, absolute: true, onlyFiles: true });
3057
+ for (const f of files) {
3058
+ project.addSourceFileAtPath(f);
3059
+ }
3060
+ const routes = [];
3061
+ setDiscoveryContext(project, {
3062
+ projectRoot: cwd,
3063
+ tsconfigPaths: loadTsconfigPaths(tsconfigPath)
3064
+ });
3065
+ for (const sourceFile of project.getSourceFiles()) {
3066
+ routes.push(...extractFromSourceFile(sourceFile, project));
3067
+ }
3068
+ return routes;
3069
+ }
3070
+ function decoratorStringArg(decoratorExpr) {
3071
+ if (!decoratorExpr) return void 0;
3072
+ if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
3073
+ if (Node8.isArrayLiteralExpression(decoratorExpr)) {
3074
+ const first = decoratorExpr.getElements()[0];
3075
+ if (first && Node8.isStringLiteral(first)) return first.getLiteralValue();
3076
+ }
3077
+ return void 0;
3078
+ }
3079
+ function deriveClassSegment(className) {
3080
+ const noSuffix = className.replace(/Controller$/, "");
3081
+ if (!noSuffix) {
3082
+ throw new Error(
3083
+ `Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
3084
+ );
3085
+ }
3086
+ return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
3087
+ }
3088
+ function resolveRouteName(className, methodName, classAs, methodAs) {
3089
+ const classPortion = classAs ?? deriveClassSegment(className);
3090
+ const methodPortion = methodAs ?? methodName;
3091
+ return `${classPortion}.${methodPortion}`;
3092
+ }
3093
+ function joinPaths(prefix, suffix) {
3094
+ if (!prefix && !suffix) return "/";
3095
+ if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
3096
+ if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
3097
+ const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
3098
+ const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
3099
+ const combined = p + s;
3100
+ return combined === "" ? "/" : combined;
3101
+ }
3102
+ function extractParams(path) {
3103
+ const matches = path.matchAll(/:(\w+)/g);
3104
+ return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
3105
+ }
3568
3106
  var HTTP_METHOD_DECORATORS = {
3569
3107
  Get: "GET",
3570
3108
  Post: "POST",
@@ -3575,176 +3113,186 @@ var HTTP_METHOD_DECORATORS = {
3575
3113
  Head: "HEAD",
3576
3114
  All: "ALL"
3577
3115
  };
3116
+ function resolveVerb(method) {
3117
+ for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
3118
+ const httpDecorator = method.getDecorator(decoratorName);
3119
+ if (httpDecorator) {
3120
+ const httpArgs = httpDecorator.getArguments();
3121
+ const pathArg = httpArgs[0];
3122
+ return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
3123
+ }
3124
+ }
3125
+ return null;
3126
+ }
3127
+ function readAsDecorator(node, label) {
3128
+ const asDecorator = node.getDecorator("As");
3129
+ if (!asDecorator) return void 0;
3130
+ const asName = decoratorStringArg(asDecorator.getArguments()[0]);
3131
+ if (!asName) {
3132
+ throw new Error(`@As decorator on ${label} must have a non-empty string argument.`);
3133
+ }
3134
+ return asName;
3135
+ }
3136
+ function buildRoute(args) {
3137
+ const {
3138
+ className,
3139
+ methodName,
3140
+ resolvedMethod,
3141
+ combinedPath,
3142
+ classAs,
3143
+ methodAs,
3144
+ sourceFile,
3145
+ seenNames,
3146
+ contractSource
3147
+ } = args;
3148
+ const routeName = resolveRouteName(className, methodName, classAs, methodAs);
3149
+ const qualifiedRef = `${className}.${methodName}`;
3150
+ const existing = seenNames.get(routeName);
3151
+ if (existing !== void 0) {
3152
+ throw new Error(
3153
+ `Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
3154
+ );
3155
+ }
3156
+ seenNames.set(routeName, qualifiedRef);
3157
+ return {
3158
+ method: resolvedMethod,
3159
+ path: combinedPath,
3160
+ name: routeName,
3161
+ params: extractParams(combinedPath),
3162
+ controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
3163
+ contract: { contractSource }
3164
+ };
3165
+ }
3166
+ function extractContractRoute(args) {
3167
+ const { cls, method, applyContractDecorator, verb, prefix, className, sourceFile, seenNames } = args;
3168
+ const firstDecoratorArg = applyContractDecorator.getArguments()[0];
3169
+ if (!firstDecoratorArg) return null;
3170
+ let contractDef = null;
3171
+ let bodyZodRef = null;
3172
+ let queryZodRef = null;
3173
+ if (Node8.isCallExpression(firstDecoratorArg)) {
3174
+ contractDef = parseDefineContractCall(firstDecoratorArg);
3175
+ } else if (Node8.isIdentifier(firstDecoratorArg)) {
3176
+ const identName = firstDecoratorArg.getText();
3177
+ const varDecl = sourceFile.getVariableDeclaration(identName);
3178
+ if (!varDecl) {
3179
+ console.warn(
3180
+ `[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
3181
+ );
3182
+ return null;
3183
+ }
3184
+ const initializer = varDecl.getInitializer();
3185
+ if (!initializer) return null;
3186
+ contractDef = parseDefineContractCall(initializer);
3187
+ if (contractDef && varDecl.isExported()) {
3188
+ const filePath = sourceFile.getFilePath();
3189
+ if (contractDef.body !== null) {
3190
+ bodyZodRef = { name: `${identName}.body`, filePath };
3191
+ }
3192
+ if (contractDef.query !== null) {
3193
+ queryZodRef = { name: `${identName}.query`, filePath };
3194
+ }
3195
+ }
3196
+ } else {
3197
+ console.warn(
3198
+ `[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
3199
+ );
3200
+ return null;
3201
+ }
3202
+ if (!contractDef) return null;
3203
+ if (!verb) return null;
3204
+ const resolvedPath = joinPaths(prefix, verb.handlerPath);
3205
+ const methodName = method.getName();
3206
+ const classAs = readAsDecorator(cls, `class ${className}`);
3207
+ const methodAs = readAsDecorator(method, `${className}.${methodName}`);
3208
+ return buildRoute({
3209
+ className,
3210
+ methodName,
3211
+ resolvedMethod: verb.httpMethod,
3212
+ combinedPath: resolvedPath,
3213
+ classAs,
3214
+ methodAs,
3215
+ sourceFile,
3216
+ seenNames,
3217
+ contractSource: {
3218
+ query: contractDef.query,
3219
+ body: contractDef.body,
3220
+ response: contractDef.response,
3221
+ // Path A: capture both the importable ref and the raw text. The emitter
3222
+ // prefers inlining the text (client-safe — re-exporting from a controller
3223
+ // would drag server-only deps into the client bundle).
3224
+ bodyZodRef,
3225
+ bodyZodText: contractDef.bodyZodText,
3226
+ queryZodRef,
3227
+ queryZodText: contractDef.queryZodText
3228
+ }
3229
+ });
3230
+ }
3231
+ function extractDtoRoute(args) {
3232
+ const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
3233
+ if (!verb) return null;
3234
+ const combined = joinPaths(prefix, verb.handlerPath);
3235
+ const methodName = method.getName();
3236
+ const classAs = readAsDecorator(cls, `class ${className}`);
3237
+ const methodAs = readAsDecorator(method, `${className}.${methodName}`);
3238
+ const dtoContract = extractDtoContract(method, sourceFile, project);
3239
+ return buildRoute({
3240
+ className,
3241
+ methodName,
3242
+ resolvedMethod: verb.httpMethod,
3243
+ combinedPath: combined,
3244
+ classAs,
3245
+ methodAs,
3246
+ sourceFile,
3247
+ seenNames,
3248
+ contractSource: {
3249
+ query: dtoContract?.query ?? null,
3250
+ body: dtoContract?.body ?? null,
3251
+ response: dtoContract?.response ?? "unknown",
3252
+ queryRef: dtoContract?.queryRef ?? null,
3253
+ bodyRef: dtoContract?.bodyRef ?? null,
3254
+ responseRef: dtoContract?.responseRef ?? null,
3255
+ filterFields: dtoContract?.filterFields ?? null,
3256
+ filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
3257
+ filterSource: dtoContract?.filterSource ?? null,
3258
+ formWarnings: dtoContract?.formWarnings ?? [],
3259
+ bodySchema: dtoContract?.bodySchema ?? null,
3260
+ querySchema: dtoContract?.querySchema ?? null
3261
+ }
3262
+ });
3263
+ }
3578
3264
  function extractFromSourceFile(sourceFile, project) {
3579
3265
  const routes = [];
3580
3266
  const seenNames = /* @__PURE__ */ new Map();
3581
- const classes = sourceFile.getClasses();
3582
- for (const cls of classes) {
3267
+ for (const cls of sourceFile.getClasses()) {
3583
3268
  const controllerDecorator = cls.getDecorator("Controller");
3584
3269
  if (!controllerDecorator) continue;
3585
- const controllerArgs = controllerDecorator.getArguments();
3586
- const firstArg3 = controllerArgs[0];
3587
- const prefix = decoratorStringArg(firstArg3) ?? "";
3270
+ const firstArg2 = controllerDecorator.getArguments()[0];
3271
+ const prefix = decoratorStringArg(firstArg2) ?? "";
3588
3272
  const className = cls.getName() ?? "Unknown";
3589
3273
  for (const method of cls.getMethods()) {
3590
- let httpMethod;
3591
- let handlerPath = "";
3592
- for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
3593
- const httpDecorator = method.getDecorator(decoratorName);
3594
- if (httpDecorator) {
3595
- httpMethod = verb;
3596
- const httpArgs = httpDecorator.getArguments();
3597
- const pathArg = httpArgs[0];
3598
- handlerPath = decoratorStringArg(pathArg) ?? "";
3599
- break;
3600
- }
3601
- }
3274
+ const verb = resolveVerb(method);
3602
3275
  const applyContractDecorator = method.getDecorator("ApplyContract");
3603
- if (applyContractDecorator) {
3604
- const decoratorArgs = applyContractDecorator.getArguments();
3605
- const firstDecoratorArg = decoratorArgs[0];
3606
- if (!firstDecoratorArg) continue;
3607
- let contractDef = null;
3608
- let bodyZodRef = null;
3609
- let queryZodRef = null;
3610
- if (Node7.isCallExpression(firstDecoratorArg)) {
3611
- contractDef = parseDefineContractCall(firstDecoratorArg);
3612
- } else if (Node7.isIdentifier(firstDecoratorArg)) {
3613
- const identName = firstDecoratorArg.getText();
3614
- const varDecl = sourceFile.getVariableDeclaration(identName);
3615
- if (!varDecl) {
3616
- console.warn(
3617
- `[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
3618
- );
3619
- continue;
3620
- }
3621
- const initializer = varDecl.getInitializer();
3622
- if (!initializer) continue;
3623
- contractDef = parseDefineContractCall(initializer);
3624
- if (contractDef && varDecl.isExported()) {
3625
- const filePath = sourceFile.getFilePath();
3626
- if (contractDef.body !== null) {
3627
- bodyZodRef = { name: `${identName}.body`, filePath };
3628
- }
3629
- if (contractDef.query !== null) {
3630
- queryZodRef = { name: `${identName}.query`, filePath };
3631
- }
3632
- }
3633
- } else {
3634
- console.warn(
3635
- `[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
3636
- );
3637
- continue;
3638
- }
3639
- if (!contractDef) continue;
3640
- if (!httpMethod) continue;
3641
- const resolvedMethod = httpMethod;
3642
- const resolvedPath = joinPaths(prefix, handlerPath);
3643
- const combined = resolvedPath;
3644
- const params = extractParams(combined);
3645
- const methodName = method.getName();
3646
- const classAsDecorator = cls.getDecorator("As");
3647
- let classAs;
3648
- if (classAsDecorator) {
3649
- const classAsArgs = classAsDecorator.getArguments();
3650
- const classAsName = decoratorStringArg(classAsArgs[0]);
3651
- if (!classAsName) {
3652
- throw new Error(
3653
- `@As decorator on class ${className} must have a non-empty string argument.`
3654
- );
3655
- }
3656
- classAs = classAsName;
3657
- }
3658
- const methodAsDecorator = method.getDecorator("As");
3659
- let methodAs;
3660
- if (methodAsDecorator) {
3661
- const methodAsArgs = methodAsDecorator.getArguments();
3662
- const methodAsName = decoratorStringArg(methodAsArgs[0]);
3663
- if (!methodAsName) {
3664
- throw new Error(
3665
- `@As decorator on ${className}.${methodName} must have a non-empty string argument.`
3666
- );
3667
- }
3668
- methodAs = methodAsName;
3669
- }
3670
- const routeName = resolveRouteName(className, methodName, classAs, methodAs);
3671
- const qualifiedRef = `${className}.${methodName}`;
3672
- const existing = seenNames.get(routeName);
3673
- if (existing !== void 0) {
3674
- throw new Error(
3675
- `Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
3676
- );
3677
- }
3678
- seenNames.set(routeName, qualifiedRef);
3679
- routes.push({
3680
- method: resolvedMethod,
3681
- path: combined,
3682
- name: routeName,
3683
- params,
3684
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
3685
- contract: {
3686
- contractSource: {
3687
- query: contractDef.query,
3688
- body: contractDef.body,
3689
- response: contractDef.response,
3690
- // Path A: capture both the importable ref and the raw text. The
3691
- // emitter prefers inlining the text (client-safe — re-exporting from
3692
- // a controller would drag server-only deps into the client bundle).
3693
- bodyZodRef,
3694
- bodyZodText: contractDef.bodyZodText,
3695
- queryZodRef,
3696
- queryZodText: contractDef.queryZodText
3697
- }
3698
- }
3699
- });
3700
- } else {
3701
- if (!httpMethod) continue;
3702
- const combined = joinPaths(prefix, handlerPath);
3703
- const params = extractParams(combined);
3704
- const methodName = method.getName();
3705
- const classAsDecorator = cls.getDecorator("As");
3706
- let classAs;
3707
- if (classAsDecorator) {
3708
- const classAsArgs = classAsDecorator.getArguments();
3709
- const classAsName = decoratorStringArg(classAsArgs[0]);
3710
- if (classAsName) classAs = classAsName;
3711
- }
3712
- const methodAsDecorator = method.getDecorator("As");
3713
- let methodAs;
3714
- if (methodAsDecorator) {
3715
- const methodAsArgs = methodAsDecorator.getArguments();
3716
- const methodAsName = decoratorStringArg(methodAsArgs[0]);
3717
- if (methodAsName) methodAs = methodAsName;
3718
- }
3719
- const routeName = resolveRouteName(className, methodName, classAs, methodAs);
3720
- const dtoContract = extractDtoContract(method, sourceFile, project);
3721
- routes.push({
3722
- method: httpMethod,
3723
- path: combined,
3724
- name: routeName,
3725
- params,
3726
- controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
3727
- contract: {
3728
- contractSource: {
3729
- query: dtoContract?.query ?? null,
3730
- body: dtoContract?.body ?? null,
3731
- response: dtoContract?.response ?? "unknown",
3732
- queryRef: dtoContract?.queryRef ?? null,
3733
- bodyRef: dtoContract?.bodyRef ?? null,
3734
- responseRef: dtoContract?.responseRef ?? null,
3735
- filterFields: dtoContract?.filterFields ?? null,
3736
- filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
3737
- filterSource: dtoContract?.filterSource ?? null,
3738
- bodyZodText: dtoContract?.bodyZodText ?? null,
3739
- queryZodText: dtoContract?.queryZodText ?? null,
3740
- formNestedSchemas: dtoContract?.formNestedSchemas ?? null,
3741
- formWarnings: dtoContract?.formWarnings ?? [],
3742
- bodySchema: dtoContract?.bodySchema ?? null,
3743
- querySchema: dtoContract?.querySchema ?? null
3744
- }
3745
- }
3746
- });
3747
- }
3276
+ const route = applyContractDecorator ? extractContractRoute({
3277
+ cls,
3278
+ method,
3279
+ applyContractDecorator,
3280
+ verb,
3281
+ prefix,
3282
+ className,
3283
+ sourceFile,
3284
+ seenNames
3285
+ }) : extractDtoRoute({
3286
+ cls,
3287
+ method,
3288
+ verb,
3289
+ prefix,
3290
+ className,
3291
+ sourceFile,
3292
+ project,
3293
+ seenNames
3294
+ });
3295
+ if (route) routes.push(route);
3748
3296
  }
3749
3297
  }
3750
3298
  return routes;
@@ -3753,7 +3301,7 @@ function extractFromSourceFile(sourceFile, project) {
3753
3301
  // src/watch/lock-file.ts
3754
3302
  import { open } from "fs/promises";
3755
3303
  import { mkdir as mkdir8, readFile as readFile2, unlink } from "fs/promises";
3756
- import { join as join11 } from "path";
3304
+ import { join as join12 } from "path";
3757
3305
  var LOCK_FILE = ".watcher.lock";
3758
3306
  function isProcessAlive(pid) {
3759
3307
  try {
@@ -3765,7 +3313,7 @@ function isProcessAlive(pid) {
3765
3313
  }
3766
3314
  async function acquireLock(outDir) {
3767
3315
  await mkdir8(outDir, { recursive: true });
3768
- const lockPath = join11(outDir, LOCK_FILE);
3316
+ const lockPath = join12(outDir, LOCK_FILE);
3769
3317
  const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
3770
3318
  try {
3771
3319
  const fd = await open(lockPath, "wx");
@@ -3805,7 +3353,7 @@ async function watch(config, onChange) {
3805
3353
  if (lock === null) {
3806
3354
  let holderPid = "unknown";
3807
3355
  try {
3808
- const raw = await readFile3(join12(config.codegen.outDir, ".watcher.lock"), "utf8");
3356
+ const raw = await readFile3(join13(config.codegen.outDir, ".watcher.lock"), "utf8");
3809
3357
  const data = JSON.parse(raw);
3810
3358
  if (data.pid !== void 0) holderPid = String(data.pid);
3811
3359
  } catch {
@@ -3833,7 +3381,7 @@ async function watch(config, onChange) {
3833
3381
  }
3834
3382
  let pagesDebounceTimer;
3835
3383
  const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
3836
- const pagesWatcher = chokidar.watch(join12(config.codegen.cwd, pagesGlob), {
3384
+ const pagesWatcher = chokidar.watch(join13(config.codegen.cwd, pagesGlob), {
3837
3385
  ignoreInitial: true,
3838
3386
  persistent: true,
3839
3387
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -3859,7 +3407,7 @@ async function watch(config, onChange) {
3859
3407
  pagesWatcher.on("change", schedulePagesRegenerate);
3860
3408
  pagesWatcher.on("unlink", schedulePagesRegenerate);
3861
3409
  let contractsDebounceTimer;
3862
- const contractsWatcher = chokidar.watch(join12(config.codegen.cwd, config.contracts.glob), {
3410
+ const contractsWatcher = chokidar.watch(join13(config.codegen.cwd, config.contracts.glob), {
3863
3411
  ignoreInitial: true,
3864
3412
  persistent: true,
3865
3413
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -3889,7 +3437,7 @@ async function watch(config, onChange) {
3889
3437
  contractsWatcher.on("add", scheduleContractsRegenerate);
3890
3438
  contractsWatcher.on("change", scheduleContractsRegenerate);
3891
3439
  contractsWatcher.on("unlink", scheduleContractsRegenerate);
3892
- const formsWatcher = chokidar.watch(join12(config.codegen.cwd, config.forms.watch), {
3440
+ const formsWatcher = chokidar.watch(join13(config.codegen.cwd, config.forms.watch), {
3893
3441
  ignoreInitial: true,
3894
3442
  persistent: true,
3895
3443
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -3916,7 +3464,7 @@ async function watch(config, onChange) {
3916
3464
  }
3917
3465
 
3918
3466
  // src/index.ts
3919
- var VERSION = "0.2.1";
3467
+ var VERSION = "0.3.0";
3920
3468
  export {
3921
3469
  CodegenError,
3922
3470
  ConfigError,
@@ -3932,7 +3480,6 @@ export {
3932
3480
  loadConfig,
3933
3481
  resolveAdapter,
3934
3482
  resolveConfig,
3935
- watch,
3936
- zodAdapter
3483
+ watch
3937
3484
  };
3938
3485
  //# sourceMappingURL=index.js.map