@mantajs/cli 0.2.0-beta.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.
Files changed (282) hide show
  1. package/bin/manta.mjs +20 -0
  2. package/bin/manta.ts +13 -0
  3. package/dist/admin/generate-admin-config.d.ts +8 -0
  4. package/dist/admin/generate-admin-config.d.ts.map +1 -0
  5. package/dist/admin/generate-admin-config.js +127 -0
  6. package/dist/admin/generate-admin-config.js.map +1 -0
  7. package/dist/ai/chat-handler.d.ts +10 -0
  8. package/dist/ai/chat-handler.d.ts.map +1 -0
  9. package/dist/ai/chat-handler.js +1353 -0
  10. package/dist/ai/chat-handler.js.map +1 -0
  11. package/dist/bootstrap/boot.d.ts +25 -0
  12. package/dist/bootstrap/boot.d.ts.map +1 -0
  13. package/dist/bootstrap/boot.js +344 -0
  14. package/dist/bootstrap/boot.js.map +1 -0
  15. package/dist/bootstrap/bootstrap-app.d.ts +71 -0
  16. package/dist/bootstrap/bootstrap-app.d.ts.map +1 -0
  17. package/dist/bootstrap/bootstrap-app.js +308 -0
  18. package/dist/bootstrap/bootstrap-app.js.map +1 -0
  19. package/dist/bootstrap/bootstrap-context.d.ts +76 -0
  20. package/dist/bootstrap/bootstrap-context.d.ts.map +1 -0
  21. package/dist/bootstrap/bootstrap-context.js +4 -0
  22. package/dist/bootstrap/bootstrap-context.js.map +1 -0
  23. package/dist/bootstrap/bootstrap-helpers.d.ts +71 -0
  24. package/dist/bootstrap/bootstrap-helpers.d.ts.map +1 -0
  25. package/dist/bootstrap/bootstrap-helpers.js +373 -0
  26. package/dist/bootstrap/bootstrap-helpers.js.map +1 -0
  27. package/dist/bootstrap/generate-types.d.ts +7 -0
  28. package/dist/bootstrap/generate-types.d.ts.map +1 -0
  29. package/dist/bootstrap/generate-types.js +832 -0
  30. package/dist/bootstrap/generate-types.js.map +1 -0
  31. package/dist/bootstrap/phases/assemble/index.d.ts +5 -0
  32. package/dist/bootstrap/phases/assemble/index.d.ts.map +1 -0
  33. package/dist/bootstrap/phases/assemble/index.js +5 -0
  34. package/dist/bootstrap/phases/assemble/index.js.map +1 -0
  35. package/dist/bootstrap/phases/assemble/load-links.d.ts +3 -0
  36. package/dist/bootstrap/phases/assemble/load-links.d.ts.map +1 -0
  37. package/dist/bootstrap/phases/assemble/load-links.js +160 -0
  38. package/dist/bootstrap/phases/assemble/load-links.js.map +1 -0
  39. package/dist/bootstrap/phases/assemble/load-modules.d.ts +3 -0
  40. package/dist/bootstrap/phases/assemble/load-modules.d.ts.map +1 -0
  41. package/dist/bootstrap/phases/assemble/load-modules.js +163 -0
  42. package/dist/bootstrap/phases/assemble/load-modules.js.map +1 -0
  43. package/dist/bootstrap/phases/assemble/load-resources.d.ts +3 -0
  44. package/dist/bootstrap/phases/assemble/load-resources.d.ts.map +1 -0
  45. package/dist/bootstrap/phases/assemble/load-resources.js +270 -0
  46. package/dist/bootstrap/phases/assemble/load-resources.js.map +1 -0
  47. package/dist/bootstrap/phases/assemble/wire-commands.d.ts +3 -0
  48. package/dist/bootstrap/phases/assemble/wire-commands.d.ts.map +1 -0
  49. package/dist/bootstrap/phases/assemble/wire-commands.js +408 -0
  50. package/dist/bootstrap/phases/assemble/wire-commands.js.map +1 -0
  51. package/dist/bootstrap/phases/assemble-modules.d.ts +3 -0
  52. package/dist/bootstrap/phases/assemble-modules.d.ts.map +1 -0
  53. package/dist/bootstrap/phases/assemble-modules.js +14 -0
  54. package/dist/bootstrap/phases/assemble-modules.js.map +1 -0
  55. package/dist/bootstrap/phases/build-app.d.ts +3 -0
  56. package/dist/bootstrap/phases/build-app.d.ts.map +1 -0
  57. package/dist/bootstrap/phases/build-app.js +15 -0
  58. package/dist/bootstrap/phases/build-app.js.map +1 -0
  59. package/dist/bootstrap/phases/discover-resources.d.ts +3 -0
  60. package/dist/bootstrap/phases/discover-resources.d.ts.map +1 -0
  61. package/dist/bootstrap/phases/discover-resources.js +60 -0
  62. package/dist/bootstrap/phases/discover-resources.js.map +1 -0
  63. package/dist/bootstrap/phases/index.d.ts +7 -0
  64. package/dist/bootstrap/phases/index.d.ts.map +1 -0
  65. package/dist/bootstrap/phases/index.js +7 -0
  66. package/dist/bootstrap/phases/index.js.map +1 -0
  67. package/dist/bootstrap/phases/init-infra.d.ts +3 -0
  68. package/dist/bootstrap/phases/init-infra.d.ts.map +1 -0
  69. package/dist/bootstrap/phases/init-infra.js +193 -0
  70. package/dist/bootstrap/phases/init-infra.js.map +1 -0
  71. package/dist/bootstrap/phases/seed-dev-users.d.ts +3 -0
  72. package/dist/bootstrap/phases/seed-dev-users.d.ts.map +1 -0
  73. package/dist/bootstrap/phases/seed-dev-users.js +93 -0
  74. package/dist/bootstrap/phases/seed-dev-users.js.map +1 -0
  75. package/dist/bootstrap/phases/wire/auth-helpers.d.ts +12 -0
  76. package/dist/bootstrap/phases/wire/auth-helpers.d.ts.map +1 -0
  77. package/dist/bootstrap/phases/wire/auth-helpers.js +25 -0
  78. package/dist/bootstrap/phases/wire/auth-helpers.js.map +1 -0
  79. package/dist/bootstrap/phases/wire/contexts/context-registry.d.ts +4 -0
  80. package/dist/bootstrap/phases/wire/contexts/context-registry.d.ts.map +1 -0
  81. package/dist/bootstrap/phases/wire/contexts/context-registry.js +96 -0
  82. package/dist/bootstrap/phases/wire/contexts/context-registry.js.map +1 -0
  83. package/dist/bootstrap/phases/wire/contexts/cqrs-routes.d.ts +3 -0
  84. package/dist/bootstrap/phases/wire/contexts/cqrs-routes.d.ts.map +1 -0
  85. package/dist/bootstrap/phases/wire/contexts/cqrs-routes.js +138 -0
  86. package/dist/bootstrap/phases/wire/contexts/cqrs-routes.js.map +1 -0
  87. package/dist/bootstrap/phases/wire/contexts/index.d.ts +6 -0
  88. package/dist/bootstrap/phases/wire/contexts/index.d.ts.map +1 -0
  89. package/dist/bootstrap/phases/wire/contexts/index.js +6 -0
  90. package/dist/bootstrap/phases/wire/contexts/index.js.map +1 -0
  91. package/dist/bootstrap/phases/wire/contexts/query-endpoints.d.ts +3 -0
  92. package/dist/bootstrap/phases/wire/contexts/query-endpoints.d.ts.map +1 -0
  93. package/dist/bootstrap/phases/wire/contexts/query-endpoints.js +116 -0
  94. package/dist/bootstrap/phases/wire/contexts/query-endpoints.js.map +1 -0
  95. package/dist/bootstrap/phases/wire/contexts/spa-warnings.d.ts +3 -0
  96. package/dist/bootstrap/phases/wire/contexts/spa-warnings.d.ts.map +1 -0
  97. package/dist/bootstrap/phases/wire/contexts/spa-warnings.js +17 -0
  98. package/dist/bootstrap/phases/wire/contexts/spa-warnings.js.map +1 -0
  99. package/dist/bootstrap/phases/wire/contexts/user-routes.d.ts +3 -0
  100. package/dist/bootstrap/phases/wire/contexts/user-routes.d.ts.map +1 -0
  101. package/dist/bootstrap/phases/wire/contexts/user-routes.js +83 -0
  102. package/dist/bootstrap/phases/wire/contexts/user-routes.js.map +1 -0
  103. package/dist/bootstrap/phases/wire/index.d.ts +5 -0
  104. package/dist/bootstrap/phases/wire/index.d.ts.map +1 -0
  105. package/dist/bootstrap/phases/wire/index.js +5 -0
  106. package/dist/bootstrap/phases/wire/index.js.map +1 -0
  107. package/dist/bootstrap/phases/wire/wire-adapter.d.ts +12 -0
  108. package/dist/bootstrap/phases/wire/wire-adapter.d.ts.map +1 -0
  109. package/dist/bootstrap/phases/wire/wire-adapter.js +156 -0
  110. package/dist/bootstrap/phases/wire/wire-adapter.js.map +1 -0
  111. package/dist/bootstrap/phases/wire/wire-auth.d.ts +3 -0
  112. package/dist/bootstrap/phases/wire/wire-auth.d.ts.map +1 -0
  113. package/dist/bootstrap/phases/wire/wire-auth.js +46 -0
  114. package/dist/bootstrap/phases/wire/wire-auth.js.map +1 -0
  115. package/dist/bootstrap/phases/wire/wire-contexts.d.ts +3 -0
  116. package/dist/bootstrap/phases/wire/wire-contexts.d.ts.map +1 -0
  117. package/dist/bootstrap/phases/wire/wire-contexts.js +15 -0
  118. package/dist/bootstrap/phases/wire/wire-contexts.js.map +1 -0
  119. package/dist/bootstrap/phases/wire/wire-cron-routes.d.ts +3 -0
  120. package/dist/bootstrap/phases/wire/wire-cron-routes.d.ts.map +1 -0
  121. package/dist/bootstrap/phases/wire/wire-cron-routes.js +102 -0
  122. package/dist/bootstrap/phases/wire/wire-cron-routes.js.map +1 -0
  123. package/dist/bootstrap/phases/wire/wire-extras.d.ts +3 -0
  124. package/dist/bootstrap/phases/wire/wire-extras.d.ts.map +1 -0
  125. package/dist/bootstrap/phases/wire/wire-extras.js +305 -0
  126. package/dist/bootstrap/phases/wire/wire-extras.js.map +1 -0
  127. package/dist/bootstrap/phases/wire/wire-workflow-routes.d.ts +3 -0
  128. package/dist/bootstrap/phases/wire/wire-workflow-routes.d.ts.map +1 -0
  129. package/dist/bootstrap/phases/wire/wire-workflow-routes.js +212 -0
  130. package/dist/bootstrap/phases/wire/wire-workflow-routes.js.map +1 -0
  131. package/dist/bootstrap/phases/wire-http.d.ts +3 -0
  132. package/dist/bootstrap/phases/wire-http.d.ts.map +1 -0
  133. package/dist/bootstrap/phases/wire-http.js +10 -0
  134. package/dist/bootstrap/phases/wire-http.js.map +1 -0
  135. package/dist/bootstrap/validate-generated-ts.d.ts +6 -0
  136. package/dist/bootstrap/validate-generated-ts.d.ts.map +1 -0
  137. package/dist/bootstrap/validate-generated-ts.js +26 -0
  138. package/dist/bootstrap/validate-generated-ts.js.map +1 -0
  139. package/dist/build/generate-manifest.d.ts +10 -0
  140. package/dist/build/generate-manifest.d.ts.map +1 -0
  141. package/dist/build/generate-manifest.js +138 -0
  142. package/dist/build/generate-manifest.js.map +1 -0
  143. package/dist/cli.d.ts +3 -0
  144. package/dist/cli.d.ts.map +1 -0
  145. package/dist/cli.js +421 -0
  146. package/dist/cli.js.map +1 -0
  147. package/dist/commands/build.d.ts +21 -0
  148. package/dist/commands/build.d.ts.map +1 -0
  149. package/dist/commands/build.js +399 -0
  150. package/dist/commands/build.js.map +1 -0
  151. package/dist/commands/db/create.d.ts +17 -0
  152. package/dist/commands/db/create.d.ts.map +1 -0
  153. package/dist/commands/db/create.js +94 -0
  154. package/dist/commands/db/create.js.map +1 -0
  155. package/dist/commands/db/diff.d.ts +39 -0
  156. package/dist/commands/db/diff.d.ts.map +1 -0
  157. package/dist/commands/db/diff.js +81 -0
  158. package/dist/commands/db/diff.js.map +1 -0
  159. package/dist/commands/db/generate.d.ts +58 -0
  160. package/dist/commands/db/generate.d.ts.map +1 -0
  161. package/dist/commands/db/generate.js +138 -0
  162. package/dist/commands/db/generate.js.map +1 -0
  163. package/dist/commands/db/migrate.d.ts +29 -0
  164. package/dist/commands/db/migrate.d.ts.map +1 -0
  165. package/dist/commands/db/migrate.js +118 -0
  166. package/dist/commands/db/migrate.js.map +1 -0
  167. package/dist/commands/db/pg-deps.d.ts +30 -0
  168. package/dist/commands/db/pg-deps.d.ts.map +1 -0
  169. package/dist/commands/db/pg-deps.js +178 -0
  170. package/dist/commands/db/pg-deps.js.map +1 -0
  171. package/dist/commands/db/rollback.d.ts +21 -0
  172. package/dist/commands/db/rollback.d.ts.map +1 -0
  173. package/dist/commands/db/rollback.js +85 -0
  174. package/dist/commands/db/rollback.js.map +1 -0
  175. package/dist/commands/db/types.d.ts +113 -0
  176. package/dist/commands/db/types.d.ts.map +1 -0
  177. package/dist/commands/db/types.js +4 -0
  178. package/dist/commands/db/types.js.map +1 -0
  179. package/dist/commands/dev.d.ts +12 -0
  180. package/dist/commands/dev.d.ts.map +1 -0
  181. package/dist/commands/dev.js +79 -0
  182. package/dist/commands/dev.js.map +1 -0
  183. package/dist/commands/exec.d.ts +21 -0
  184. package/dist/commands/exec.d.ts.map +1 -0
  185. package/dist/commands/exec.js +148 -0
  186. package/dist/commands/exec.js.map +1 -0
  187. package/dist/commands/generate.d.ts +11 -0
  188. package/dist/commands/generate.d.ts.map +1 -0
  189. package/dist/commands/generate.js +19 -0
  190. package/dist/commands/generate.js.map +1 -0
  191. package/dist/commands/init.d.ts +14 -0
  192. package/dist/commands/init.d.ts.map +1 -0
  193. package/dist/commands/init.js +476 -0
  194. package/dist/commands/init.js.map +1 -0
  195. package/dist/commands/start.d.ts +15 -0
  196. package/dist/commands/start.d.ts.map +1 -0
  197. package/dist/commands/start.js +121 -0
  198. package/dist/commands/start.js.map +1 -0
  199. package/dist/commands/user.d.ts +19 -0
  200. package/dist/commands/user.d.ts.map +1 -0
  201. package/dist/commands/user.js +125 -0
  202. package/dist/commands/user.js.map +1 -0
  203. package/dist/config/load-config.d.ts +23 -0
  204. package/dist/config/load-config.d.ts.map +1 -0
  205. package/dist/config/load-config.js +105 -0
  206. package/dist/config/load-config.js.map +1 -0
  207. package/dist/config/load-env.d.ts +11 -0
  208. package/dist/config/load-env.d.ts.map +1 -0
  209. package/dist/config/load-env.js +61 -0
  210. package/dist/config/load-env.js.map +1 -0
  211. package/dist/config/resolve-adapters.d.ts +23 -0
  212. package/dist/config/resolve-adapters.d.ts.map +1 -0
  213. package/dist/config/resolve-adapters.js +96 -0
  214. package/dist/config/resolve-adapters.js.map +1 -0
  215. package/dist/index.d.ts +18 -0
  216. package/dist/index.d.ts.map +1 -0
  217. package/dist/index.js +19 -0
  218. package/dist/index.js.map +1 -0
  219. package/dist/jiti.d.ts +2 -0
  220. package/dist/jiti.d.ts.map +1 -0
  221. package/dist/jiti.js +2 -0
  222. package/dist/jiti.js.map +1 -0
  223. package/dist/openapi/generate-spec.d.ts +56 -0
  224. package/dist/openapi/generate-spec.d.ts.map +1 -0
  225. package/dist/openapi/generate-spec.js +491 -0
  226. package/dist/openapi/generate-spec.js.map +1 -0
  227. package/dist/openapi/index.d.ts +4 -0
  228. package/dist/openapi/index.d.ts.map +1 -0
  229. package/dist/openapi/index.js +3 -0
  230. package/dist/openapi/index.js.map +1 -0
  231. package/dist/openapi/swagger-html.d.ts +2 -0
  232. package/dist/openapi/swagger-html.d.ts.map +1 -0
  233. package/dist/openapi/swagger-html.js +29 -0
  234. package/dist/openapi/swagger-html.js.map +1 -0
  235. package/dist/plugins/merge-resources.d.ts +18 -0
  236. package/dist/plugins/merge-resources.d.ts.map +1 -0
  237. package/dist/plugins/merge-resources.js +76 -0
  238. package/dist/plugins/merge-resources.js.map +1 -0
  239. package/dist/plugins/resolve-plugins.d.ts +14 -0
  240. package/dist/plugins/resolve-plugins.d.ts.map +1 -0
  241. package/dist/plugins/resolve-plugins.js +73 -0
  242. package/dist/plugins/resolve-plugins.js.map +1 -0
  243. package/dist/resource-loader.d.ts +151 -0
  244. package/dist/resource-loader.d.ts.map +1 -0
  245. package/dist/resource-loader.js +456 -0
  246. package/dist/resource-loader.js.map +1 -0
  247. package/dist/route-discovery.d.ts +33 -0
  248. package/dist/route-discovery.d.ts.map +1 -0
  249. package/dist/route-discovery.js +69 -0
  250. package/dist/route-discovery.js.map +1 -0
  251. package/dist/server-bootstrap.d.ts +38 -0
  252. package/dist/server-bootstrap.d.ts.map +1 -0
  253. package/dist/server-bootstrap.js +21 -0
  254. package/dist/server-bootstrap.js.map +1 -0
  255. package/dist/spa/generate-spa.d.ts +15 -0
  256. package/dist/spa/generate-spa.d.ts.map +1 -0
  257. package/dist/spa/generate-spa.js +357 -0
  258. package/dist/spa/generate-spa.js.map +1 -0
  259. package/dist/templates/agent/nextjs.md +129 -0
  260. package/dist/templates/agent/nuxt.md +98 -0
  261. package/dist/templates/agent/standalone.md +498 -0
  262. package/dist/types.d.ts +145 -0
  263. package/dist/types.d.ts.map +1 -0
  264. package/dist/types.js +3 -0
  265. package/dist/types.js.map +1 -0
  266. package/dist/utils/colors.d.ts +7 -0
  267. package/dist/utils/colors.d.ts.map +1 -0
  268. package/dist/utils/colors.js +8 -0
  269. package/dist/utils/colors.js.map +1 -0
  270. package/dist/utils/logger.d.ts +10 -0
  271. package/dist/utils/logger.d.ts.map +1 -0
  272. package/dist/utils/logger.js +27 -0
  273. package/dist/utils/logger.js.map +1 -0
  274. package/dist/utils/prompts.d.ts +6 -0
  275. package/dist/utils/prompts.d.ts.map +1 -0
  276. package/dist/utils/prompts.js +28 -0
  277. package/dist/utils/prompts.js.map +1 -0
  278. package/dist/utils/spinner.d.ts +7 -0
  279. package/dist/utils/spinner.d.ts.map +1 -0
  280. package/dist/utils/spinner.js +20 -0
  281. package/dist/utils/spinner.js.map +1 -0
  282. package/package.json +80 -0
@@ -0,0 +1,832 @@
1
+ // Generate .manta/generated.d.ts from discovered DML entities, services, commands, and subscribers.
2
+ // Runs in the CLI process (before Nitro starts), not in the Nitro worker.
3
+ //
4
+ // Generates a SINGLE file:
5
+ // .manta/generated.d.ts — all type declarations merged
6
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { relative, resolve } from 'node:path';
8
+ import { MantaError } from '@mantajs/core';
9
+ import { discoverResources } from '../resource-loader';
10
+ import { validateGeneratedTypeScript } from './validate-generated-ts';
11
+ // ── Identifier sanitization ──────────────────────────────────────────
12
+ // SPEC TS-04 — Reject invalid identifiers at the source with clear
13
+ // MantaError messages. Pairs with the output validator (belt + braces).
14
+ function assertSafeIdentifierComponent(value, kind, where) {
15
+ const pattern = kind === 'PascalCase' ? /^[A-Z][A-Za-z0-9]*$/ : /^[a-z][A-Za-z0-9]*$/;
16
+ if (!pattern.test(value)) {
17
+ throw new MantaError('INVALID_DATA', `Invalid ${kind} identifier "${value}" in ${where}. Must match ${pattern}.`);
18
+ }
19
+ }
20
+ // ── Inject globals so model imports work in standalone codegen ────────
21
+ async function injectGlobals() {
22
+ const g = globalThis;
23
+ if (g.defineModel)
24
+ return; // Already injected
25
+ const core = await import('@mantajs/core');
26
+ g.defineModel = core.defineModel;
27
+ g.defineService = core.defineService;
28
+ g.defineLink = core.defineLink;
29
+ g.defineCommand = core.defineCommand;
30
+ g.defineAgent = core.defineAgent;
31
+ g.defineSubscriber = core.defineSubscriber;
32
+ g.defineJob = core.defineJob;
33
+ g.defineUserModel = core.defineUserModel;
34
+ g.defineQuery = core.defineQuery;
35
+ g.defineQueryGraph = core.defineQueryGraph;
36
+ g.extendQueryGraph = core.extendQueryGraph;
37
+ g.defineWorkflow = core.defineWorkflow;
38
+ g.defineConfig = core.defineConfig;
39
+ g.definePreset = core.definePreset;
40
+ g.defineMiddleware = core.defineMiddleware;
41
+ g.field = core.field;
42
+ g.many = core.many;
43
+ const { z } = await import('zod');
44
+ g.z = z;
45
+ }
46
+ // ── Pluralization (must match instantiate.ts) ────────────────────────
47
+ function pluralize(name) {
48
+ if (name.endsWith('s') || name.endsWith('x') || name.endsWith('ch') || name.endsWith('sh'))
49
+ return `${name}es`;
50
+ if (name.endsWith('y') && !/[aeiou]y$/i.test(name))
51
+ return `${name.slice(0, -1)}ies`;
52
+ return `${name}s`;
53
+ }
54
+ async function collectModuleEntries(resources) {
55
+ const entries = [];
56
+ // Also include user models (defineUserModel) as module entities
57
+ for (const userInfo of resources.users) {
58
+ try {
59
+ const mod = (await import(`${userInfo.path}?t=${Date.now()}`));
60
+ const def = (mod.default ?? mod);
61
+ if (def?.__type === 'user' && def?.model) {
62
+ const model = def.model;
63
+ const contextName = def.contextName;
64
+ assertSafeIdentifierComponent(model.name, 'PascalCase', `defineUserModel in ${userInfo.path}`);
65
+ // Find the module this user belongs to
66
+ const parentModule = resources.modules.find((m) => m.entities.some((e) => e.modelPath === userInfo.path)) ??
67
+ resources.modules.find((m) => m.name === contextName);
68
+ const moduleName = parentModule?.name ?? contextName;
69
+ // Use a unique export alias for the user model
70
+ const alias = `${model.name}UserDef`;
71
+ entries.push({
72
+ moduleName,
73
+ entityName: model.name,
74
+ entityExportName: alias,
75
+ modulePath: userInfo.path,
76
+ customMethods: [],
77
+ isUserModel: true,
78
+ });
79
+ }
80
+ }
81
+ catch (err) {
82
+ // Propagate validation errors (bad identifiers); swallow only true import failures.
83
+ if (MantaError.is(err))
84
+ throw err;
85
+ // User model not importable
86
+ }
87
+ }
88
+ for (const modInfo of resources.modules) {
89
+ for (const entity of modInfo.entities) {
90
+ try {
91
+ const mod = (await import(`${entity.modelPath}?t=${Date.now()}`));
92
+ // Find the DML entity export — support both DmlEntity and UserDefinition
93
+ let entityExportName = '';
94
+ let entityName = '';
95
+ let isExternal = false;
96
+ for (const [exportName, value] of Object.entries(mod)) {
97
+ // Standard DmlEntity
98
+ if (typeof value === 'object' &&
99
+ value !== null &&
100
+ 'name' in value &&
101
+ 'schema' in value &&
102
+ typeof value.name === 'string' &&
103
+ typeof value.getOptions === 'function') {
104
+ const v = value;
105
+ entityExportName = exportName;
106
+ entityName = v.name;
107
+ const opts = v.getOptions?.();
108
+ isExternal = opts?.external === true;
109
+ break;
110
+ }
111
+ // UserDefinition — skip in entity loop, handled by user model loop above
112
+ if (typeof value === 'object' && value !== null && value.__type === 'user') {
113
+ break; // Skip — already added by user model loop
114
+ }
115
+ }
116
+ if (!entityExportName)
117
+ continue;
118
+ assertSafeIdentifierComponent(entityName, 'PascalCase', `defineModel in ${entity.modelPath}`);
119
+ // Fix: 'default' is a reserved word — alias it to the entity name
120
+ let isDefaultExport = false;
121
+ if (entityExportName === 'default') {
122
+ entityExportName = `${entityName}Model`;
123
+ isDefaultExport = true;
124
+ }
125
+ // Find custom compensable methods from the service.ts (if it exists)
126
+ const customMethods = [];
127
+ if (entity.servicePath) {
128
+ try {
129
+ const serviceMod = (await import(`${entity.servicePath}?t=${Date.now()}`));
130
+ const defaultExport = serviceMod.default;
131
+ if (defaultExport?.__type && typeof defaultExport.factory === 'function') {
132
+ const fakeRepo = new Proxy({}, {
133
+ get: () => async () => [],
134
+ });
135
+ const methods = defaultExport.factory(fakeRepo);
136
+ for (const [methodName, fn] of Object.entries(methods)) {
137
+ if (typeof fn === 'function') {
138
+ customMethods.push(methodName);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ catch {
144
+ // Service may fail with fake repo — skip custom methods
145
+ }
146
+ }
147
+ entries.push({
148
+ moduleName: modInfo.name,
149
+ entityName,
150
+ entityExportName,
151
+ modulePath: entity.modelPath,
152
+ customMethods,
153
+ isDefaultExport,
154
+ isExternal,
155
+ });
156
+ }
157
+ catch (err) {
158
+ // Propagate validation errors (bad identifiers); swallow only true import failures.
159
+ if (MantaError.is(err))
160
+ throw err;
161
+ // Entity model may not be importable — skip
162
+ }
163
+ }
164
+ }
165
+ return entries;
166
+ }
167
+ /**
168
+ * Try to extract the object literal from a step.emit() / app.emit() call.
169
+ * Best-effort: parses `{ key: value, key2: value2 }` after the event name.
170
+ * Returns field names with inferred TS types, or undefined if unparseable.
171
+ */
172
+ function extractEmitDataShape(content, eventName) {
173
+ // Match .emit('eventName', { ... })
174
+ const escaped = eventName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
175
+ const pattern = new RegExp(`\\.emit\\(\\s*['"]${escaped}['"]\\s*,\\s*\\{([^}]+)\\}`, 's');
176
+ const match = pattern.exec(content);
177
+ if (!match)
178
+ return undefined;
179
+ const body = match[1];
180
+ const fields = {};
181
+ // Match property patterns: `key: expr` or `key,` (shorthand)
182
+ const propPattern = /(\w+)\s*(?::\s*([^,\n}]+?))?(?:\s*[,\n}])/g;
183
+ let propMatch = null;
184
+ // biome-ignore lint/suspicious/noAssignInExpressions: regex iteration
185
+ while ((propMatch = propPattern.exec(body)) !== null) {
186
+ const key = propMatch[1];
187
+ const value = propMatch[2]?.trim();
188
+ // Infer type from the value expression
189
+ if (!value || value.startsWith('input.')) {
190
+ fields[key] = 'unknown';
191
+ }
192
+ else if (/^['"]/.test(value)) {
193
+ fields[key] = 'string';
194
+ }
195
+ else if (/^\d+/.test(value)) {
196
+ fields[key] = 'number';
197
+ }
198
+ else if (value === 'true' || value === 'false') {
199
+ fields[key] = 'boolean';
200
+ }
201
+ else {
202
+ fields[key] = 'unknown';
203
+ }
204
+ }
205
+ return Object.keys(fields).length > 0 ? fields : undefined;
206
+ }
207
+ async function collectEvents(resources) {
208
+ const events = [];
209
+ const seen = new Set();
210
+ // Extract event names from subscriber definitions
211
+ for (const subInfo of resources.subscribers) {
212
+ try {
213
+ const mod = (await import(`${subInfo.path}?t=${Date.now()}`));
214
+ const sub = mod.default;
215
+ if (sub?.event) {
216
+ const eventNames = Array.isArray(sub.event) ? sub.event : [sub.event];
217
+ for (const name of eventNames) {
218
+ const eventName = name;
219
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/.test(eventName)) {
220
+ throw new MantaError('INVALID_DATA', `Invalid subscriber event name "${eventName}" in ${subInfo.path}. Must be alphanumeric with dots/hyphens.`);
221
+ }
222
+ if (!seen.has(eventName)) {
223
+ seen.add(eventName);
224
+ events.push({ eventName, sourceFile: subInfo.path, sourceType: 'subscriber' });
225
+ }
226
+ }
227
+ }
228
+ }
229
+ catch (err) {
230
+ if (MantaError.is(err))
231
+ throw err;
232
+ // Skip
233
+ }
234
+ }
235
+ // Extract event names + data shapes from command files (step.emit calls)
236
+ for (const cmdInfo of resources.commands) {
237
+ try {
238
+ const content = readFileSync(cmdInfo.path, 'utf-8');
239
+ // Match step.emit('event.name', ...) and app.emit('event.name', ...)
240
+ const emitPattern = /\.emit\(\s*['"]([a-z][a-z0-9.-]+)['"]/g;
241
+ let match = null;
242
+ // biome-ignore lint/suspicious/noAssignInExpressions: regex iteration
243
+ while ((match = emitPattern.exec(content)) !== null) {
244
+ const name = match[1];
245
+ if (!seen.has(name)) {
246
+ seen.add(name);
247
+ const dataFields = extractEmitDataShape(content, name);
248
+ events.push({ eventName: name, sourceFile: cmdInfo.path, sourceType: 'command', dataFields });
249
+ }
250
+ }
251
+ }
252
+ catch {
253
+ // Skip
254
+ }
255
+ }
256
+ // Also scan subscriber files for app.emit() calls (cascading events)
257
+ for (const subInfo of resources.subscribers) {
258
+ try {
259
+ const content = readFileSync(subInfo.path, 'utf-8');
260
+ const emitPattern = /\.emit\(\s*['"]([a-z][a-z0-9.-]+)['"]/g;
261
+ let match = null;
262
+ // biome-ignore lint/suspicious/noAssignInExpressions: regex iteration
263
+ while ((match = emitPattern.exec(content)) !== null) {
264
+ const name = match[1];
265
+ if (!seen.has(name)) {
266
+ seen.add(name);
267
+ const dataFields = extractEmitDataShape(content, name);
268
+ events.push({ eventName: name, sourceFile: subInfo.path, sourceType: 'subscriber', dataFields });
269
+ }
270
+ }
271
+ }
272
+ catch {
273
+ // Skip
274
+ }
275
+ }
276
+ return events;
277
+ }
278
+ // ── Main entry point ─────────────────────────────────────────────────
279
+ /**
280
+ * Scan src/ for modules, commands, subscribers and generate typed declarations.
281
+ *
282
+ * Generates a single file: .manta/generated.d.ts
283
+ */
284
+ export async function generateTypesFromModules(cwd) {
285
+ const mantaDir = resolve(cwd, '.manta');
286
+ if (!existsSync(mantaDir))
287
+ mkdirSync(mantaDir, { recursive: true });
288
+ // Clean up old codegen directories if they exist
289
+ for (const old of ['types', '../.manta-types']) {
290
+ const oldDir = resolve(mantaDir, old);
291
+ if (existsSync(oldDir))
292
+ rmSync(oldDir, { recursive: true, force: true });
293
+ }
294
+ // Inject globals (defineModel, defineService, etc.) so model imports work standalone
295
+ await injectGlobals();
296
+ // Load config + resolve plugins, so plugin-contributed modules/entities show up in codegen
297
+ let resources = await discoverResources(cwd);
298
+ try {
299
+ const { loadConfig } = await import('../config/load-config');
300
+ const { resolvePlugins } = await import('../plugins/resolve-plugins');
301
+ const { mergePluginResources } = await import('../plugins/merge-resources');
302
+ const config = await loadConfig(cwd);
303
+ if (config) {
304
+ const plugins = resolvePlugins(config, cwd);
305
+ if (plugins.length > 0) {
306
+ resources = await mergePluginResources(plugins, resources);
307
+ }
308
+ }
309
+ }
310
+ catch {
311
+ // Config may not exist yet (e.g. during postinstall of the CLI itself) — ignore
312
+ }
313
+ const entries = await collectModuleEntries(resources);
314
+ const events = await collectEvents(resources);
315
+ if (entries.length === 0)
316
+ return;
317
+ const outPath = resolve(mantaDir, 'generated.d.ts');
318
+ const lines = [
319
+ '// Auto-generated by manta dev — DO NOT EDIT',
320
+ '// Provides typed app.modules.*, step.catalog.*, event.data, step.command.*, step.agent.*, etc.',
321
+ '// Regenerated on every boot when modules/commands/subscribers/agents change.',
322
+ '',
323
+ ];
324
+ // ── Imports ─────────────────────────────────────────────────────────
325
+ lines.push("import type { InferEntity } from '@mantajs/core'");
326
+ lines.push("import type { ServiceConfig } from '@mantajs/core'");
327
+ // Entity imports (deduplicated by export name)
328
+ const seenImports = new Set();
329
+ for (const entry of entries) {
330
+ const key = `${entry.entityExportName}@${entry.modulePath}`;
331
+ if (seenImports.has(key))
332
+ continue;
333
+ seenImports.add(key);
334
+ const relPath = relative(mantaDir, entry.modulePath).replace(/\.ts$/, '');
335
+ const isUser = entry.isUserModel;
336
+ if (isUser || entry.isDefaultExport) {
337
+ // Default export: use default import with alias
338
+ lines.push(`import type ${entry.entityExportName} from '${relPath}'`);
339
+ }
340
+ else {
341
+ lines.push(`import type { ${entry.entityExportName} } from '${relPath}'`);
342
+ }
343
+ }
344
+ // Agent imports
345
+ const agentEntries = resources.agents.map((a) => {
346
+ const camel = a.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
347
+ assertSafeIdentifierComponent(camel, 'camelCase', `defineAgent in ${a.path}`);
348
+ return {
349
+ kebab: a.id,
350
+ camel,
351
+ path: a.path,
352
+ };
353
+ });
354
+ if (agentEntries.length > 0) {
355
+ lines.push("import type { z } from 'zod'");
356
+ for (const agent of agentEntries) {
357
+ const relPath = relative(mantaDir, agent.path).replace(/\.ts$/, '');
358
+ lines.push(`import type ${agent.camel}Def from '${relPath}'`);
359
+ }
360
+ }
361
+ // ── Module-scoped entity types ──────────────────────────────────────
362
+ lines.push('');
363
+ // Group entries by module to fix the duplicate interface issue
364
+ const moduleMap = new Map();
365
+ for (const entry of entries) {
366
+ const existing = moduleMap.get(entry.moduleName) ?? [];
367
+ existing.push(entry);
368
+ moduleMap.set(entry.moduleName, existing);
369
+ }
370
+ // Generate entity types and per-module interfaces
371
+ for (const [moduleName, moduleEntries] of moduleMap) {
372
+ for (const entry of moduleEntries) {
373
+ const E = entry.entityExportName;
374
+ const name = entry.entityName;
375
+ const isUser = entry.isUserModel;
376
+ if (isUser) {
377
+ // For user models, generate the type manually from the DML schema
378
+ // because InferEntity can't resolve the generic UserDefinition.model type
379
+ try {
380
+ const mod = (await import(`${entry.modulePath}?t=${Date.now()}`));
381
+ const def = (mod.default ?? mod);
382
+ const schema = def?.model?.schema;
383
+ if (schema) {
384
+ const fields = [];
385
+ for (const [key, prop] of Object.entries(schema)) {
386
+ const meta = typeof prop.parse === 'function' ? prop.parse(key) : null;
387
+ if (!meta)
388
+ continue;
389
+ const nullable = meta.nullable;
390
+ const dt = meta.dataType?.name ?? 'unknown';
391
+ let tsType = 'unknown';
392
+ switch (dt) {
393
+ case 'text':
394
+ tsType = 'string';
395
+ break;
396
+ case 'number':
397
+ case 'bigNumber':
398
+ case 'float':
399
+ case 'autoincrement':
400
+ tsType = 'number';
401
+ break;
402
+ case 'boolean':
403
+ tsType = 'boolean';
404
+ break;
405
+ case 'dateTime':
406
+ tsType = 'Date';
407
+ break;
408
+ case 'json':
409
+ tsType = 'any';
410
+ break;
411
+ case 'enum':
412
+ tsType = meta.values ? meta.values.map((v) => `'${v}'`).join(' | ') : 'string';
413
+ break;
414
+ case 'array':
415
+ tsType = 'unknown[]';
416
+ break;
417
+ default:
418
+ tsType = 'unknown';
419
+ }
420
+ fields.push(` ${key}${nullable ? '?' : ''}: ${tsType}${nullable ? ' | null' : ''}`);
421
+ }
422
+ lines.push(`type ${name}Entity = { ${fields.join('; ')} } & { id: string; created_at: Date; updated_at: Date }`);
423
+ }
424
+ else {
425
+ lines.push(`type ${name}Entity = Record<string, unknown> & { id: string; created_at: Date; updated_at: Date }`);
426
+ }
427
+ }
428
+ catch {
429
+ lines.push(`type ${name}Entity = Record<string, unknown> & { id: string; created_at: Date; updated_at: Date }`);
430
+ }
431
+ }
432
+ else {
433
+ lines.push(`type ${name}Entity = InferEntity<typeof ${E}> & { id: string; created_at: Date; updated_at: Date }`);
434
+ }
435
+ }
436
+ lines.push('');
437
+ // Single interface per module with ALL entity methods
438
+ lines.push(`interface ${moduleName}Module {`);
439
+ for (const entry of moduleEntries) {
440
+ const name = entry.entityName;
441
+ const plural = pluralize(name);
442
+ // External entities: no local service, only query.graph() routing via extendQueryGraph.
443
+ // Skip CRUD method generation — they would fail at runtime (no table).
444
+ if (entry.isExternal) {
445
+ lines.push(` // ${name} is external — queried via query.graph({ entity: '${name.charAt(0).toLowerCase() + name.slice(1)}', ... })`);
446
+ continue;
447
+ }
448
+ lines.push(` retrieve${name}(id: string, config?: ServiceConfig): Promise<${name}Entity>`);
449
+ lines.push(` list${plural}(filters?: Partial<${name}Entity>, config?: ServiceConfig): Promise<${name}Entity[]>`);
450
+ lines.push(` listAndCount${plural}(filters?: Partial<${name}Entity>, config?: ServiceConfig): Promise<[${name}Entity[], number]>`);
451
+ lines.push(` create${plural}(data: Partial<${name}Entity> | Partial<${name}Entity>[]): Promise<${name}Entity | ${name}Entity[]>`);
452
+ lines.push(` update${plural}(data: (Partial<${name}Entity> & { id: string }) | (Partial<${name}Entity> & { id: string })[]): Promise<${name}Entity | ${name}Entity[]>`);
453
+ lines.push(` delete${plural}(ids: string | string[]): Promise<void>`);
454
+ lines.push(` softDelete${plural}(ids: string | string[]): Promise<Record<string, string[]>>`);
455
+ lines.push(` restore${plural}(ids: string | string[]): Promise<void>`);
456
+ lines.push(` list(): Promise<${name}Entity[]>`);
457
+ lines.push(` findById(id: string): Promise<${name}Entity | null>`);
458
+ for (const method of entry.customMethods) {
459
+ lines.push(` ${method}(...args: unknown[]): Promise<unknown>`);
460
+ }
461
+ }
462
+ // Add shorthand methods for step.service proxy (create, update, delete)
463
+ // These match what createModuleProxy exposes at runtime
464
+ if (moduleEntries.length > 0) {
465
+ const first = moduleEntries[0];
466
+ const name = first.entityName;
467
+ lines.push(` /** Shorthand: step.service.${moduleName}.create(data) */`);
468
+ lines.push(` create(data: Partial<${name}Entity>): Promise<${name}Entity>`);
469
+ lines.push(` /** Shorthand: step.service.${moduleName}.update(id, data) */`);
470
+ lines.push(` update(id: string, data?: Partial<${name}Entity>): Promise<${name}Entity>`);
471
+ lines.push(` /** Shorthand: step.service.${moduleName}.delete(id) */`);
472
+ lines.push(` delete(id: string): Promise<void>`);
473
+ }
474
+ lines.push('}');
475
+ lines.push('');
476
+ }
477
+ // ── Build event map ─────────────────────────────────────────────────
478
+ const eventMap = new Map();
479
+ // Auto-generated CRUD events from entities (skip external — they have no local CRUD)
480
+ for (const entry of entries) {
481
+ if (entry.isExternal)
482
+ continue;
483
+ const lower = entry.entityName.toLowerCase();
484
+ eventMap.set(`${lower}.created`, '{ id: string }');
485
+ eventMap.set(`${lower}.updated`, '{ id: string }');
486
+ eventMap.set(`${lower}.deleted`, '{ id: string }');
487
+ }
488
+ // Explicit events from commands/subscribers
489
+ for (const event of events) {
490
+ if (!eventMap.has(event.eventName)) {
491
+ if (event.dataFields && Object.keys(event.dataFields).length > 0) {
492
+ const fields = Object.entries(event.dataFields)
493
+ .map(([k, t]) => `${k}: ${t}`)
494
+ .join('; ');
495
+ eventMap.set(event.eventName, `{ ${fields} }`);
496
+ }
497
+ else {
498
+ eventMap.set(event.eventName, 'Record<string, unknown>');
499
+ }
500
+ }
501
+ }
502
+ const sortedEvents = [...eventMap.keys()].sort();
503
+ // ── Collect commands ────────────────────────────────────────────────
504
+ const commandNames = resources.commands.map((c) => c.id).sort();
505
+ const commandEntries = [];
506
+ for (const cmd of resources.commands) {
507
+ const camel = cmd.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
508
+ assertSafeIdentifierComponent(camel, 'camelCase', `defineCommand in ${cmd.path}`);
509
+ commandEntries.push({ kebab: cmd.id, camel });
510
+ }
511
+ // ── Collect actors ──────────────────────────────────────────────────
512
+ const actorSet = new Set(['user']);
513
+ for (const ctxInfo of resources.contexts) {
514
+ try {
515
+ const ctxMod = (await import(`${ctxInfo.path}?t=${Date.now()}`));
516
+ const def = ctxMod.default;
517
+ if (def?.actors) {
518
+ const actors = Array.isArray(def.actors) ? def.actors : [def.actors];
519
+ for (const a of actors) {
520
+ assertSafeIdentifierComponent(a, 'camelCase', `defineContext actor in ${ctxInfo.path}`);
521
+ actorSet.add(a);
522
+ }
523
+ }
524
+ }
525
+ catch (err) {
526
+ if (MantaError.is(err))
527
+ throw err;
528
+ console.warn(` [codegen] Warning: failed to import context '${ctxInfo.id}': ${err.message}`);
529
+ }
530
+ }
531
+ const sortedActors = [...actorSet].sort();
532
+ // ── Deduplicated module names ───────────────────────────────────────
533
+ const moduleNames = [...new Set(entries.map((e) => e.moduleName))].sort();
534
+ // ── declare global block ────────────────────────────────────────────
535
+ lines.push('declare global {');
536
+ // MantaGeneratedEntities — camelCase keys only
537
+ lines.push(' interface MantaGeneratedEntities {');
538
+ const seenEntityKeys = new Set();
539
+ const entityTypeRef = (entry) => {
540
+ const isUser = entry.isUserModel;
541
+ return isUser ? `typeof ${entry.entityExportName}['model']` : `typeof ${entry.entityExportName}`;
542
+ };
543
+ // Entity keys: camelCase derived from PascalCase entity name
544
+ for (const entry of entries) {
545
+ const camel = entry.entityName.charAt(0).toLowerCase() + entry.entityName.slice(1);
546
+ if (!seenEntityKeys.has(camel)) {
547
+ lines.push(` ${camel}: ${entityTypeRef(entry)}`);
548
+ seenEntityKeys.add(camel);
549
+ }
550
+ }
551
+ // Module name aliases (only if different from entity camelCase)
552
+ for (const [modName, modEntries] of moduleMap) {
553
+ if (!seenEntityKeys.has(modName)) {
554
+ lines.push(` ${modName}: ${entityTypeRef(modEntries[0])}`);
555
+ seenEntityKeys.add(modName);
556
+ }
557
+ }
558
+ lines.push(' }');
559
+ lines.push('');
560
+ // MantaGeneratedEntityRegistry — camelCase keys only
561
+ lines.push(' interface MantaGeneratedEntityRegistry {');
562
+ for (const entry of entries) {
563
+ const camel = entry.entityName.charAt(0).toLowerCase() + entry.entityName.slice(1);
564
+ lines.push(` ${camel}: ${entityTypeRef(entry)}`);
565
+ }
566
+ lines.push(' }');
567
+ lines.push('');
568
+ // MantaGeneratedCommands — includes explicit commands + auto-generated CRUD + link/unlink
569
+ // All names are camelCase for ergonomic step.command.xxx() usage.
570
+ {
571
+ /** Convert any casing to camelCase: 'CustomerGroup' → 'customerGroup', 'customer_group' → 'customerGroup' */
572
+ const toCamel = (name) => name
573
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
574
+ .replace(/[\s_]+/g, '-')
575
+ .toLowerCase()
576
+ .replace(/-([a-z])/g, (_, c) => c.toUpperCase());
577
+ const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
578
+ lines.push(' interface MantaGeneratedCommands {');
579
+ // Explicit commands (from src/commands/)
580
+ for (const cmd of commandEntries) {
581
+ if (cmd.kebab === 'graph')
582
+ continue;
583
+ lines.push(` ${cmd.camel}(input: unknown): Promise<unknown>`);
584
+ }
585
+ // Auto-generated CRUD commands per entity (camelCase)
586
+ for (const entry of entries) {
587
+ const camel = toCamel(entry.entityName);
588
+ const cap = capitalize(camel);
589
+ const capPlural = pluralize(cap);
590
+ lines.push(` create${cap}(input: Record<string, unknown>): Promise<unknown>`);
591
+ lines.push(` update${cap}(input: { id: string } & Record<string, unknown>): Promise<unknown>`);
592
+ lines.push(` delete${cap}(input: { id: string }): Promise<{ id: string; deleted: true }>`);
593
+ lines.push(` retrieve${cap}(input: { id: string }): Promise<unknown>`);
594
+ lines.push(` list${capPlural}(input?: { filters?: Record<string, unknown>; limit?: number; offset?: number }): Promise<unknown[]>`);
595
+ }
596
+ // Auto-generated link/unlink commands (camelCase)
597
+ const emitLinkTypes = (link) => {
598
+ if (!link?.leftEntity || !link?.rightEntity)
599
+ return;
600
+ const leftCap = capitalize(toCamel(link.leftEntity));
601
+ const rightCap = capitalize(toCamel(link.rightEntity));
602
+ const leftFk = link.leftFk ?? `${toCamel(link.leftEntity)}_id`;
603
+ const rightFk = link.rightFk ?? `${toCamel(link.rightEntity)}_id`;
604
+ lines.push(` link${leftCap}${rightCap}(input: { ${leftFk}: string; ${rightFk}: string }): Promise<{ success: true }>`);
605
+ lines.push(` unlink${leftCap}${rightCap}(input: { ${leftFk}: string; ${rightFk}: string }): Promise<{ success: true }>`);
606
+ };
607
+ for (const linkInfo of resources.links) {
608
+ try {
609
+ const mod = (await import(`${linkInfo.path}?t=${Date.now()}`));
610
+ const link = (mod.default ?? mod);
611
+ if (!link?.isDirectFk)
612
+ emitLinkTypes(link);
613
+ }
614
+ catch {
615
+ /* skip */
616
+ }
617
+ }
618
+ for (const modInfo of resources.modules) {
619
+ for (const linkInfo of modInfo.intraLinks) {
620
+ try {
621
+ const mod = (await import(`${linkInfo.path}?t=${Date.now()}`));
622
+ const link = (mod.default ?? mod);
623
+ if (link?.cardinality === 'M:N' && !link?.isDirectFk)
624
+ emitLinkTypes(link);
625
+ }
626
+ catch {
627
+ /* skip */
628
+ }
629
+ }
630
+ }
631
+ lines.push(' }');
632
+ lines.push('');
633
+ }
634
+ // MantaGeneratedQueries
635
+ if (resources.queries.length > 0) {
636
+ lines.push(' interface MantaGeneratedQueries {');
637
+ for (const q of resources.queries) {
638
+ if (q.id === 'graph')
639
+ continue; // skip defineQueryGraph entries
640
+ const _camel = q.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
641
+ lines.push(` '${q.id}': (params?: Record<string, unknown>) => Promise<unknown>`);
642
+ }
643
+ lines.push(' }');
644
+ lines.push('');
645
+ }
646
+ // MantaGeneratedAgents
647
+ if (agentEntries.length > 0) {
648
+ lines.push(' interface MantaGeneratedAgents {');
649
+ for (const agent of agentEntries) {
650
+ lines.push(` ${agent.camel}(input: z.infer<typeof ${agent.camel}Def.input>): Promise<z.infer<typeof ${agent.camel}Def.output>>`);
651
+ }
652
+ lines.push(' }');
653
+ lines.push('');
654
+ }
655
+ // MantaGeneratedEventMap
656
+ lines.push(' /** Event data map — typed event.data for defineSubscriber() and step.emit(). */');
657
+ lines.push(' interface MantaGeneratedEventMap {');
658
+ for (const name of sortedEvents) {
659
+ lines.push(` '${name}': ${eventMap.get(name)}`);
660
+ }
661
+ lines.push(' }');
662
+ lines.push('');
663
+ // DefineSubscriberFn — merge event-specific overloads for autocomplete
664
+ // Interface call signatures with explicit string literals give IDE autocomplete.
665
+ if (sortedEvents.length > 0) {
666
+ lines.push(' /** Codegen overloads — gives autocomplete for event names in defineSubscriber(). */');
667
+ lines.push(' interface DefineSubscriberFn {');
668
+ for (const name of sortedEvents) {
669
+ const dataType = eventMap.get(name);
670
+ lines.push(` (event: '${name}', handler: (event: import('@mantajs/core').Message<${dataType}>, scope: import('@mantajs/core').SubscriberScope) => void | Promise<void>): import('@mantajs/core').SubscriberDefinition<${dataType}> & { __type: 'subscriber' }`);
671
+ }
672
+ lines.push(' }');
673
+ lines.push('');
674
+ }
675
+ // MantaGeneratedAppModules (deduplicated — one entry per module)
676
+ lines.push(' interface MantaGeneratedAppModules {');
677
+ for (const moduleName of moduleNames) {
678
+ lines.push(` ${moduleName}: ${moduleName}Module`);
679
+ }
680
+ lines.push(' }');
681
+ lines.push('');
682
+ // MantaGeneratedRegistry (deduplicated module names)
683
+ lines.push(' interface MantaGeneratedRegistry {');
684
+ if (moduleNames.length > 0) {
685
+ lines.push(' modules: {');
686
+ for (const name of moduleNames) {
687
+ lines.push(` ${name}: true`);
688
+ }
689
+ lines.push(' }');
690
+ }
691
+ if (commandNames.length > 0) {
692
+ lines.push(' commands: {');
693
+ for (const name of commandNames) {
694
+ lines.push(` '${name}': true`);
695
+ }
696
+ lines.push(' }');
697
+ }
698
+ if (sortedActors.length > 0) {
699
+ lines.push(' actors: {');
700
+ for (const actor of sortedActors) {
701
+ lines.push(` ${actor}: true`);
702
+ }
703
+ lines.push(' }');
704
+ }
705
+ lines.push(' }');
706
+ lines.push('}');
707
+ lines.push('');
708
+ lines.push('export {}');
709
+ lines.push('');
710
+ const source = lines.join('\n');
711
+ validateGeneratedTypeScript(source, 'generated.d.ts');
712
+ writeFileSync(outPath, source);
713
+ // ── Generate command schemas for frontend (runtime metadata) ────────
714
+ await generateCommandSchemas(cwd, resources, mantaDir);
715
+ console.log(` [codegen] .manta/generated.d.ts (${entries.length} entities, ${commandNames.length} commands, ${agentEntries.length} agents, ${sortedActors.length} actors, ${sortedEvents.length} events)`);
716
+ }
717
+ /**
718
+ * Extract field metadata from a Zod schema (introspects _def).
719
+ */
720
+ function extractZodFields(schema) {
721
+ const fields = [];
722
+ // biome-ignore lint/suspicious/noExplicitAny: Zod internal structure
723
+ const s = schema;
724
+ if (!s?._def?.typeName)
725
+ return fields;
726
+ // Must be a ZodObject
727
+ if (s._def.typeName !== 'ZodObject')
728
+ return fields;
729
+ const shape = s._def.shape?.() ?? s._def.shape ?? {};
730
+ for (const [key, fieldSchema] of Object.entries(shape)) {
731
+ // biome-ignore lint/suspicious/noExplicitAny: Zod internal
732
+ let inner = fieldSchema;
733
+ let required = true;
734
+ // Unwrap ZodOptional / ZodDefault
735
+ while (inner?._def) {
736
+ if (inner._def.typeName === 'ZodOptional' || inner._def.typeName === 'ZodNullable') {
737
+ required = false;
738
+ inner = inner._def.innerType;
739
+ }
740
+ else if (inner._def.typeName === 'ZodDefault') {
741
+ required = false;
742
+ inner = inner._def.innerType;
743
+ }
744
+ else {
745
+ break;
746
+ }
747
+ }
748
+ const typeName = inner?._def?.typeName ?? '';
749
+ let type = 'unknown';
750
+ const checks = [];
751
+ let options;
752
+ switch (typeName) {
753
+ case 'ZodString':
754
+ type = 'string';
755
+ // Extract string checks (email, url, min, max, etc.)
756
+ for (const check of inner._def.checks ?? []) {
757
+ if (check.kind === 'email')
758
+ checks.push('email');
759
+ else if (check.kind === 'url')
760
+ checks.push('url');
761
+ else if (check.kind === 'min')
762
+ checks.push(`min:${check.value}`);
763
+ else if (check.kind === 'max')
764
+ checks.push(`max:${check.value}`);
765
+ }
766
+ break;
767
+ case 'ZodNumber':
768
+ type = 'number';
769
+ for (const check of inner._def.checks ?? []) {
770
+ if (check.kind === 'min')
771
+ checks.push(`min:${check.value}`);
772
+ else if (check.kind === 'max')
773
+ checks.push(`max:${check.value}`);
774
+ else if (check.kind === 'int')
775
+ checks.push('int');
776
+ }
777
+ break;
778
+ case 'ZodBoolean':
779
+ type = 'boolean';
780
+ break;
781
+ case 'ZodDate':
782
+ type = 'date';
783
+ break;
784
+ case 'ZodEnum':
785
+ case 'ZodNativeEnum':
786
+ type = 'enum';
787
+ options = inner._def.values;
788
+ break;
789
+ case 'ZodArray':
790
+ type = 'array';
791
+ break;
792
+ default:
793
+ type = 'unknown';
794
+ }
795
+ fields.push({ key, required, type, checks: checks.length > 0 ? checks : undefined, options });
796
+ }
797
+ return fields;
798
+ }
799
+ /**
800
+ * Generate .manta/command-schemas.ts — runtime metadata for frontend form validation.
801
+ */
802
+ async function generateCommandSchemas(_cwd, resources, mantaDir) {
803
+ const schemas = {};
804
+ for (const cmdInfo of resources.commands) {
805
+ try {
806
+ const mod = (await import(`${cmdInfo.path}?t=${Date.now()}`));
807
+ const cmd = mod.default;
808
+ if (!cmd?.name || !cmd?.input)
809
+ continue;
810
+ const fields = extractZodFields(cmd.input);
811
+ if (fields.length > 0) {
812
+ schemas[cmd.name] = fields;
813
+ }
814
+ }
815
+ catch {
816
+ // Command may not be importable — skip
817
+ }
818
+ }
819
+ if (Object.keys(schemas).length === 0)
820
+ return;
821
+ const outPath = resolve(mantaDir, 'command-schemas.ts');
822
+ const content = [
823
+ '// Auto-generated by manta dev — DO NOT EDIT',
824
+ '// Command input schemas for frontend form validation.',
825
+ '',
826
+ `export const commandSchemas: Record<string, Array<{ key: string; required: boolean; type: string; checks?: string[]; options?: string[] }>> = ${JSON.stringify(schemas, null, 2)}`,
827
+ '',
828
+ ].join('\n');
829
+ validateGeneratedTypeScript(content, 'command-schemas.ts');
830
+ writeFileSync(outPath, content);
831
+ }
832
+ //# sourceMappingURL=generate-types.js.map