@mmapp/react-compiler 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (251) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +113 -6
  4. package/dist/babel/index.mjs +2 -2
  5. package/dist/chunk-3USIFFE4.mjs +2190 -0
  6. package/dist/chunk-45YMGEVT.mjs +186 -0
  7. package/dist/chunk-4FN2AISW.mjs +148 -0
  8. package/dist/chunk-4OPI5L7G.mjs +2593 -0
  9. package/dist/chunk-4RYTKOOJ.mjs +186 -0
  10. package/dist/chunk-5RKTOVR5.mjs +244 -0
  11. package/dist/chunk-5YDMOO4X.mjs +214 -0
  12. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  13. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  14. package/dist/chunk-72QWL54I.mjs +175 -0
  15. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  16. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  17. package/dist/chunk-CIESM3BP.mjs +33 -0
  18. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  19. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  20. package/dist/chunk-DPIK3PJS.mjs +244 -0
  21. package/dist/chunk-E5IVH4RE.mjs +186 -0
  22. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  23. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  24. package/dist/chunk-ELO4TXJL.mjs +186 -0
  25. package/dist/chunk-FKRO52XH.mjs +3446 -0
  26. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  27. package/dist/chunk-FYT47UBU.mjs +5076 -0
  28. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  29. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  30. package/dist/chunk-HFXOUMTD.mjs +175 -0
  31. package/dist/chunk-HWIZ47US.mjs +214 -0
  32. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  33. package/dist/chunk-ICSIHQCG.mjs +148 -0
  34. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  35. package/dist/chunk-JQLWFCTM.mjs +214 -0
  36. package/dist/chunk-KFJJCQAL.mjs +148 -0
  37. package/dist/chunk-KJUIIEQE.mjs +186 -0
  38. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  39. package/dist/chunk-KSG4XSZF.mjs +175 -0
  40. package/dist/chunk-LF5N6DOU.mjs +175 -0
  41. package/dist/chunk-LJQCM2IM.mjs +214 -0
  42. package/dist/chunk-NW6555WJ.mjs +186 -0
  43. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  44. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  45. package/dist/chunk-QQHVYH2X.mjs +244 -0
  46. package/dist/chunk-S5QLWLLT.mjs +186 -0
  47. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  48. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  49. package/dist/chunk-VCAY2KGM.mjs +175 -0
  50. package/dist/chunk-WECAV6QB.mjs +148 -0
  51. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  52. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  53. package/dist/chunk-XG2X7AEA.mjs +175 -0
  54. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  55. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  56. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  57. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  58. package/dist/chunk-ZA37XTGA.mjs +175 -0
  59. package/dist/cli/index.js +1616 -366
  60. package/dist/cli/index.mjs +8 -8
  61. package/dist/codemod/cli.mjs +1 -1
  62. package/dist/codemod/index.mjs +1 -1
  63. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  64. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  65. package/dist/dev-server.d.mts +1 -1
  66. package/dist/dev-server.d.ts +1 -1
  67. package/dist/dev-server.js +982 -53
  68. package/dist/dev-server.mjs +5 -5
  69. package/dist/envelope.js +113 -6
  70. package/dist/envelope.mjs +3 -3
  71. package/dist/index.d.mts +5 -1
  72. package/dist/index.d.ts +5 -1
  73. package/dist/index.js +992 -63
  74. package/dist/index.mjs +8 -8
  75. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  76. package/dist/init-EHO4VQ22.mjs +369 -0
  77. package/dist/init-UC3FWPIW.mjs +367 -0
  78. package/dist/init-UNSMVKIK.mjs +366 -0
  79. package/dist/init-UNV5XIDE.mjs +367 -0
  80. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  81. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  82. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  83. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  84. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  85. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  86. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  87. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  88. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  89. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  90. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  91. package/dist/pull-FUS5QYZS.mjs +109 -0
  92. package/dist/pull-LD5ENLGY.mjs +109 -0
  93. package/dist/testing/index.js +113 -6
  94. package/dist/testing/index.mjs +2 -2
  95. package/dist/vite/index.js +113 -6
  96. package/dist/vite/index.mjs +3 -3
  97. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  98. package/package.json +4 -3
  99. package/compile-blueprint-chat.mjs +0 -99
  100. package/compile-blueprint-glass-console.mjs +0 -98
  101. package/compile-chat-defs.mjs +0 -92
  102. package/examples/uber-app/tests/payment.test.tsx +0 -129
  103. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  104. package/package.json.backup +0 -86
  105. package/scripts/decompile.ts +0 -226
  106. package/scripts/seed-auth.ts +0 -267
  107. package/scripts/seed-uber.ts +0 -248
  108. package/scripts/validate-uber.ts +0 -119
  109. package/seed-blueprint-chat.mjs +0 -444
  110. package/seed-blueprint-glass-console.mjs +0 -445
  111. package/seed-compiled.mjs +0 -318
  112. package/src/RoundTripValidator.ts +0 -400
  113. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  114. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  115. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  116. package/src/__tests__/change-extractor.test.ts +0 -142
  117. package/src/__tests__/cli-pull.test.ts +0 -73
  118. package/src/__tests__/cli-test.test.ts +0 -72
  119. package/src/__tests__/component-extractor.test.ts +0 -331
  120. package/src/__tests__/context-extractor.test.ts +0 -145
  121. package/src/__tests__/decompiler.test.ts +0 -718
  122. package/src/__tests__/define-blueprint.test.ts +0 -133
  123. package/src/__tests__/definition-validator.test.ts +0 -519
  124. package/src/__tests__/during-extractor.test.ts +0 -152
  125. package/src/__tests__/effect-extractor.test.ts +0 -107
  126. package/src/__tests__/event-emission.test.ts +0 -127
  127. package/src/__tests__/examples.test.ts +0 -236
  128. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  129. package/src/__tests__/golden-suite.test.ts +0 -403
  130. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  131. package/src/__tests__/instance-key.test.ts +0 -82
  132. package/src/__tests__/ir-migration.test.ts +0 -255
  133. package/src/__tests__/lock-file.test.ts +0 -117
  134. package/src/__tests__/model-extractor.test.ts +0 -195
  135. package/src/__tests__/model-field-acl.test.ts +0 -237
  136. package/src/__tests__/model-hooks.test.ts +0 -130
  137. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  138. package/src/__tests__/model-roundtrip.test.ts +0 -502
  139. package/src/__tests__/model-runtime.test.ts +0 -112
  140. package/src/__tests__/model-transitions.test.ts +0 -183
  141. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  142. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  143. package/src/__tests__/project-compiler.test.ts +0 -546
  144. package/src/__tests__/project-decompiler.test.ts +0 -343
  145. package/src/__tests__/query-compilation.test.ts +0 -145
  146. package/src/__tests__/round-trip/PLAN.md +0 -158
  147. package/src/__tests__/round-trip/README.md +0 -52
  148. package/src/__tests__/round-trip/RESULTS.md +0 -86
  149. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  150. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  151. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  152. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  153. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  154. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  155. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  156. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  157. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  158. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  159. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  160. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  161. package/src/__tests__/round-trip-ir.test.ts +0 -300
  162. package/src/__tests__/round-trip.test.ts +0 -1212
  163. package/src/__tests__/route-merging.test.ts +0 -372
  164. package/src/__tests__/router-composition.test.ts +0 -489
  165. package/src/__tests__/router-extractor.test.ts +0 -176
  166. package/src/__tests__/server-action-extractor.test.ts +0 -128
  167. package/src/__tests__/smart-type-inference.test.ts +0 -365
  168. package/src/__tests__/source-envelope.test.ts +0 -284
  169. package/src/__tests__/source-fidelity.test.ts +0 -516
  170. package/src/__tests__/state-extractor.test.ts +0 -115
  171. package/src/__tests__/strict-mode.test.ts +0 -227
  172. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  173. package/src/__tests__/transition-extractor.test.ts +0 -68
  174. package/src/__tests__/ts-to-expression.test.ts +0 -462
  175. package/src/__tests__/type-generator.test.ts +0 -201
  176. package/src/__tests__/uber-validation.test.ts +0 -502
  177. package/src/action-compiler.ts +0 -361
  178. package/src/babel/emitters/experience-transform.ts +0 -199
  179. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  180. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  181. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  182. package/src/babel/extractors/change-extractor.ts +0 -199
  183. package/src/babel/extractors/component-extractor.ts +0 -907
  184. package/src/babel/extractors/computed-extractor.ts +0 -262
  185. package/src/babel/extractors/context-extractor.ts +0 -277
  186. package/src/babel/extractors/during-extractor.ts +0 -295
  187. package/src/babel/extractors/effect-extractor.ts +0 -340
  188. package/src/babel/extractors/event-extractor.ts +0 -235
  189. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  190. package/src/babel/extractors/model-extractor.ts +0 -1018
  191. package/src/babel/extractors/router-extractor.ts +0 -303
  192. package/src/babel/extractors/server-action-extractor.ts +0 -173
  193. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  194. package/src/babel/extractors/server-state-extractor.ts +0 -88
  195. package/src/babel/extractors/state-extractor.ts +0 -214
  196. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  197. package/src/babel/extractors/transition-extractor.ts +0 -143
  198. package/src/babel/index.ts +0 -24
  199. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  200. package/src/babel/visitor.ts +0 -807
  201. package/src/cli/auth.ts +0 -255
  202. package/src/cli/build.ts +0 -288
  203. package/src/cli/deploy.ts +0 -206
  204. package/src/cli/index.ts +0 -328
  205. package/src/cli/installer.ts +0 -261
  206. package/src/cli/lock-file.ts +0 -94
  207. package/src/cli/mmrc.ts +0 -22
  208. package/src/cli/pull.ts +0 -172
  209. package/src/cli/registry-client.ts +0 -175
  210. package/src/cli/test.ts +0 -397
  211. package/src/cli/type-generator.ts +0 -243
  212. package/src/codemod/__tests__/forward.test.ts +0 -239
  213. package/src/codemod/__tests__/reverse.test.ts +0 -145
  214. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  215. package/src/codemod/annotation.ts +0 -97
  216. package/src/codemod/classify.ts +0 -197
  217. package/src/codemod/cli.ts +0 -207
  218. package/src/codemod/control-flow.ts +0 -409
  219. package/src/codemod/forward.ts +0 -244
  220. package/src/codemod/import-manager.ts +0 -171
  221. package/src/codemod/index.ts +0 -120
  222. package/src/codemod/reverse.ts +0 -197
  223. package/src/codemod/rules.ts +0 -174
  224. package/src/codemod/state-transform.ts +0 -126
  225. package/src/decompiler/ast-builder.ts +0 -538
  226. package/src/decompiler/config-generator.ts +0 -151
  227. package/src/decompiler/index.ts +0 -315
  228. package/src/decompiler/project-decompiler.ts +0 -1776
  229. package/src/decompiler/project.ts +0 -862
  230. package/src/decompiler/split-strategy.ts +0 -140
  231. package/src/decompiler/state-emitter.ts +0 -1053
  232. package/src/decompiler/sx-emitter.ts +0 -318
  233. package/src/decompiler/workspace-hydrator.ts +0 -189
  234. package/src/dev-server.ts +0 -238
  235. package/src/envelope/fs-tree.ts +0 -217
  236. package/src/envelope/source-envelope.ts +0 -264
  237. package/src/envelope.ts +0 -315
  238. package/src/incremental-compiler.ts +0 -401
  239. package/src/index.ts +0 -99
  240. package/src/model-compiler.ts +0 -277
  241. package/src/project-compiler.ts +0 -1629
  242. package/src/route-extractor.ts +0 -333
  243. package/src/testing/index.ts +0 -32
  244. package/src/testing/snapshot.ts +0 -252
  245. package/src/testing/test-utils.ts +0 -226
  246. package/src/types.ts +0 -68
  247. package/src/vite/index.ts +0 -288
  248. package/test-compile.mjs +0 -142
  249. package/tsconfig.json +0 -25
  250. package/tsup.config.ts +0 -23
  251. package/vitest.config.ts +0 -9
@@ -1,807 +0,0 @@
1
- /**
2
- * Babel Visitor — main AST traversal logic for extracting workflow definitions.
3
- *
4
- * Handles all @mindmatrix/react hooks:
5
- * - useState → fields
6
- * - useOnEnter / useOnExit → state enter/exit actions
7
- * - useTransition → transitions
8
- * - useOnEvent → event subscriptions
9
- * - useWhileIn → during actions (interval while in state)
10
- * - useOnChange → field watchers
11
- * - useOnTransition → transition effects
12
- *
13
- * Supports strict/infer mode enforcement.
14
- */
15
-
16
- import type { Visitor } from '@babel/traverse';
17
- import * as t from '@babel/types';
18
- import type { CompilerState, CompilerOptions, ReactCompilerError } from '../types';
19
- import { extractStates } from './extractors/state-extractor';
20
- import { extractEffects } from './extractors/effect-extractor';
21
- import { extractTransitions } from './extractors/transition-extractor';
22
- import { extractEvents } from './extractors/event-extractor';
23
- import { extractComponents, resetNodeIdCounter, registerDerivedVar } from './extractors/component-extractor';
24
- import { extractDuring, resetDuringIdCounter } from './extractors/during-extractor';
25
- import { extractChangeWatcher, resetWatcherIdCounter } from './extractors/change-extractor';
26
- import { extractComputed } from './extractors/computed-extractor';
27
- import { extractTransitionEffect, resetTransitionEffectIdCounter } from './extractors/transition-effect-extractor';
28
- import { isModelFile, extractModelFile } from './extractors/model-extractor';
29
- import { isServerActionFile, extractServerActions } from './extractors/server-action-extractor';
30
- import { extractServerActionHook } from './extractors/server-action-hook-extractor';
31
- import { extractServerState } from './extractors/server-state-extractor';
32
- import { extractGrammarIslands } from './extractors/grammar-island-extractor';
33
- import { hasContextCreation, extractContextWorkflows } from './extractors/context-extractor';
34
- import { emitIR, emitCanonical, emitWorkflowDefinition, compilerStateToWorkflow } from './emitters/pure-form-emitter';
35
- import type { IRDataSource, IRWorkflowDataSource } from '@mindmatrix/player-core';
36
- import type { NodePath } from '@babel/traverse';
37
-
38
- /**
39
- * Extracts useQuery() calls into IRDataSource declarations.
40
- * useQuery('channel', { state: 'active' }) → IRDataSource with type 'workflow'
41
- */
42
- /**
43
- * Resolves a useQuery/useMutation slug argument.
44
- * Supports string literals ('channel') and identifier references to imported defineModel results.
45
- *
46
- * When the argument is an identifier (e.g. `useQuery(ChannelModel)`), we:
47
- * 1. Look up the import source in __modelImports
48
- * 2. Derive the slug from the import path (e.g. '../models/channel' → 'channel')
49
- * 3. Also check __modelImportSlugs for slugs resolved from actual file contents
50
- */
51
- function resolveSlugArg(args: t.Node[], state: any): string | null {
52
- if (args.length < 1) return null;
53
- const slugArg = args[0];
54
-
55
- // Direct string literal: useQuery('channel')
56
- if (t.isStringLiteral(slugArg)) return slugArg.value;
57
-
58
- // Identifier reference: useQuery(ChannelModel)
59
- // Resolve via tracked model imports
60
- if (t.isIdentifier(slugArg)) {
61
- const compilerState = state as CompilerState;
62
- const meta = compilerState.metadata as Record<string, unknown>;
63
-
64
- // Check pre-resolved slugs first (from file content analysis)
65
- const resolvedSlugs = meta.__modelImportSlugs as Record<string, string> | undefined;
66
- if (resolvedSlugs?.[slugArg.name]) return resolvedSlugs[slugArg.name];
67
-
68
- // Fall back to slug derived from import path
69
- const importSources = meta.__modelImports as Record<string, string> | undefined;
70
- if (importSources?.[slugArg.name]) {
71
- const importPath = importSources[slugArg.name];
72
- // Derive slug from the import path's filename
73
- // e.g. '../models/channel' → 'channel'
74
- // e.g. '../models/user-profile' → 'user-profile'
75
- // e.g. './models/OrderItem' → 'order-item'
76
- const filename = importPath.split('/').pop() || '';
77
- return filename
78
- .replace(/\.(ts|tsx|js|jsx)$/, '')
79
- .replace(/\.model$/, '')
80
- .replace(/([A-Z])/g, '-$1')
81
- .toLowerCase()
82
- .replace(/^-/, '');
83
- }
84
- }
85
-
86
- return null;
87
- }
88
-
89
- function extractQueryDataSource(path: NodePath<t.CallExpression>, state: any): void {
90
- const args = path.node.arguments;
91
- if (args.length < 1) return;
92
-
93
- const slug = resolveSlugArg(args, state);
94
- if (!slug) return;
95
- const compilerState = state as CompilerState;
96
-
97
- // Build data source from useQuery params
98
- const dataSource: IRWorkflowDataSource = {
99
- type: 'workflow',
100
- name: slug,
101
- slug,
102
- query: 'list',
103
- };
104
-
105
- // Extract options if provided — maps QueryParams → IRWorkflowDataSource fields
106
- if (args.length > 1 && t.isObjectExpression(args[1])) {
107
- for (const prop of args[1].properties) {
108
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue;
109
- const key = prop.key.name;
110
- const val = prop.value;
111
-
112
- switch (key) {
113
- case 'limit':
114
- if (t.isNumericLiteral(val)) {
115
- dataSource.pageSize = val.value;
116
- dataSource.paginated = true;
117
- }
118
- break;
119
- case 'orderBy':
120
- if (t.isStringLiteral(val)) {
121
- dataSource.sort = val.value;
122
- }
123
- break;
124
- case 'order':
125
- if (t.isStringLiteral(val) && dataSource.sort) {
126
- dataSource.sort = `${dataSource.sort}:${val.value}`;
127
- }
128
- break;
129
- case 'search':
130
- if (t.isStringLiteral(val)) {
131
- dataSource.search = val.value;
132
- }
133
- break;
134
- case 'searchFields':
135
- if (t.isArrayExpression(val)) {
136
- dataSource.searchFields = val.elements
137
- .filter((el): el is t.StringLiteral => t.isStringLiteral(el))
138
- .map(el => el.value);
139
- }
140
- break;
141
- case 'filter':
142
- if (t.isObjectExpression(val)) {
143
- const filter: Record<string, string> = {};
144
- for (const fp of val.properties) {
145
- if (t.isObjectProperty(fp) && t.isIdentifier(fp.key) && t.isStringLiteral(fp.value)) {
146
- filter[fp.key.name] = fp.value.value;
147
- }
148
- }
149
- if (Object.keys(filter).length > 0) {
150
- dataSource.filter = filter;
151
- }
152
- }
153
- break;
154
- case 'state':
155
- // Shorthand: filter by workflow state
156
- if (t.isStringLiteral(val)) {
157
- if (!dataSource.filter) dataSource.filter = {};
158
- dataSource.filter.current_state = val.value;
159
- }
160
- break;
161
- case 'groupBy':
162
- if (t.isStringLiteral(val)) {
163
- dataSource.groupBy = val.value;
164
- }
165
- break;
166
- case 'facets':
167
- if (t.isArrayExpression(val)) {
168
- dataSource.facets = val.elements
169
- .filter((el): el is t.StringLiteral => t.isStringLiteral(el))
170
- .map(el => el.value);
171
- }
172
- break;
173
- }
174
- }
175
- }
176
-
177
- // Store data sources on the metadata for later emission
178
- if (!compilerState.metadata) compilerState.metadata = {};
179
- const meta = compilerState.metadata as Record<string, unknown>;
180
- if (!meta.dataSources) meta.dataSources = [];
181
- (meta.dataSources as IRDataSource[]).push(dataSource);
182
- }
183
-
184
- /**
185
- * Extracts useMutation() calls into mutation action stubs.
186
- * useMutation('channel') → records the slug for transition/create capability
187
- */
188
- function extractMutationDataSource(path: NodePath<t.CallExpression>, state: any): void {
189
- const args = path.node.arguments;
190
- if (args.length < 1) return;
191
-
192
- const slug = resolveSlugArg(args, state);
193
- if (!slug) return;
194
- const compilerState = state as CompilerState;
195
-
196
- // Store mutation targets on metadata
197
- if (!compilerState.metadata) compilerState.metadata = {};
198
- const meta = compilerState.metadata as Record<string, unknown>;
199
- if (!meta.mutationTargets) meta.mutationTargets = [];
200
- (meta.mutationTargets as string[]).push(slug);
201
- }
202
-
203
- /**
204
- * Extracts useDuringAction({ state, action, intervalMs }) into during actions.
205
- * Alternate API for useWhileIn — config object instead of positional args.
206
- */
207
- function extractDuringAction(path: NodePath<t.CallExpression>, state: any): void {
208
- const args = path.node.arguments;
209
- if (args.length < 1 || !t.isObjectExpression(args[0])) return;
210
-
211
- const compilerState = state as CompilerState;
212
- const config = args[0];
213
-
214
- let stateName: string | undefined;
215
- let intervalMs = 1000;
216
-
217
- for (const prop of config.properties) {
218
- if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue;
219
- if (prop.key.name === 'state' && t.isStringLiteral(prop.value)) {
220
- stateName = prop.value.value;
221
- }
222
- if (prop.key.name === 'intervalMs' && t.isNumericLiteral(prop.value)) {
223
- intervalMs = prop.value.value;
224
- }
225
- }
226
-
227
- if (!stateName) return;
228
-
229
- // Ensure state exists
230
- if (!compilerState.states.has(stateName)) {
231
- compilerState.states.set(stateName, {
232
- name: stateName,
233
- type: 'REGULAR',
234
- on_enter: [],
235
- during: [],
236
- on_exit: [],
237
- });
238
- }
239
-
240
- const stateEntry = compilerState.states.get(stateName)!;
241
- if (!stateEntry.during) stateEntry.during = [];
242
-
243
- compilerState.actionCounter++;
244
- stateEntry.during.push({
245
- id: `during_${compilerState.actionCounter}`,
246
- type: 'interval',
247
- interval_ms: intervalMs,
248
- actions: [],
249
- });
250
- }
251
-
252
- /**
253
- * Extracts useRole('roleName') into metadata.roleDependencies.
254
- */
255
- function extractRoleDependency(path: NodePath<t.CallExpression>, state: any): void {
256
- const args = path.node.arguments;
257
- if (args.length < 1 || !t.isStringLiteral(args[0])) return;
258
-
259
- const compilerState = state as CompilerState;
260
- if (!compilerState.metadata) compilerState.metadata = {};
261
- const meta = compilerState.metadata as Record<string, unknown>;
262
- if (!meta.roleDependencies) meta.roleDependencies = [];
263
- const roles = meta.roleDependencies as string[];
264
- const role = args[0].value;
265
- if (!roles.includes(role)) roles.push(role);
266
- }
267
-
268
- /**
269
- * Extracts useView('slug') into metadata.viewDependencies.
270
- */
271
- function extractViewDependency(path: NodePath<t.CallExpression>, state: any): void {
272
- const args = path.node.arguments;
273
- if (args.length < 1 || !t.isStringLiteral(args[0])) return;
274
-
275
- const compilerState = state as CompilerState;
276
- if (!compilerState.metadata) compilerState.metadata = {};
277
- const meta = compilerState.metadata as Record<string, unknown>;
278
- if (!meta.viewDependencies) meta.viewDependencies = [];
279
- const views = meta.viewDependencies as string[];
280
- const slug = args[0].value;
281
- if (!views.includes(slug)) views.push(slug);
282
- }
283
-
284
- /**
285
- * Extracts useParams() into metadata.acceptsParams = true.
286
- */
287
- function extractParamsUsage(_path: NodePath<t.CallExpression>, state: any): void {
288
- const compilerState = state as CompilerState;
289
- if (!compilerState.metadata) compilerState.metadata = {};
290
- const meta = compilerState.metadata as Record<string, unknown>;
291
- meta.acceptsParams = true;
292
- }
293
-
294
- /**
295
- * Extracts useExpressionLibrary('slug') into metadata.libraryDependencies.
296
- */
297
- function extractLibraryDependency(path: NodePath<t.CallExpression>, state: any): void {
298
- const args = path.node.arguments;
299
- if (args.length < 1 || !t.isStringLiteral(args[0])) return;
300
-
301
- const compilerState = state as CompilerState;
302
- if (!compilerState.metadata) compilerState.metadata = {};
303
- const meta = compilerState.metadata as Record<string, unknown>;
304
- if (!meta.libraryDependencies) meta.libraryDependencies = [];
305
- const libs = meta.libraryDependencies as string[];
306
- const slug = args[0].value;
307
- if (!libs.includes(slug)) libs.push(slug);
308
- }
309
-
310
- /** Hooks banned in strict mode */
311
- const STRICT_BANNED_HOOKS: Record<string, string> = {
312
- useEffect: 'STRICT_USE_EFFECT',
313
- useLayoutEffect: 'STRICT_USE_LAYOUT_EFFECT',
314
- useRef: 'STRICT_USE_REF',
315
- useMemo: 'STRICT_USE_MEMO',
316
- useCallback: 'STRICT_USE_CALLBACK',
317
- };
318
-
319
- /**
320
- * Extracts metadata from JSDoc comments.
321
- * Shared between Program.enter and FunctionDeclaration.
322
- */
323
- function extractMetadataFromComments(comments: any[], metadata: CompilerState['metadata']): void {
324
- for (const comment of comments) {
325
- if (comment.type !== 'CommentBlock') continue;
326
- const lines = comment.value.split('\n');
327
- for (const line of lines) {
328
- // Match @workflow with inline key=value pairs:
329
- // @workflow slug="mm-chat-app" version="1.0.0" category="blueprint"
330
- const workflowMatch = line.match(/@workflow\s+(.+)/);
331
- if (workflowMatch) {
332
- const rest = workflowMatch[1].trim();
333
- const kvRegex = /(\w+)="([^"]+)"/g;
334
- let kvMatch;
335
- let hasKV = false;
336
- while ((kvMatch = kvRegex.exec(rest)) !== null) {
337
- hasKV = true;
338
- const [, key, val] = kvMatch;
339
- if (key === 'slug') metadata.slug = val;
340
- else if (key === 'version') metadata.version = val;
341
- else if (key === 'category') metadata.category = val;
342
- else if (key === 'description') metadata.description = val;
343
- }
344
- // Fallback: if no key=value pairs, treat whole value as slug
345
- if (!hasKV) {
346
- metadata.slug = rest;
347
- }
348
- continue;
349
- }
350
- // Match standalone @version, @category, @description tags
351
- const match = line.match(/@(\w+)\s+(.+)/);
352
- if (match) {
353
- const [, tag, value] = match;
354
- if (tag === 'version') metadata.version = value.trim();
355
- if (tag === 'category') metadata.category = value.trim();
356
- if (tag === 'description') metadata.description = value.trim();
357
- }
358
- }
359
- }
360
- }
361
-
362
- /**
363
- * Creates the Babel visitor for the plugin.
364
- */
365
- export function createVisitor(options: CompilerOptions = {}): Visitor {
366
- const mode = options.mode;
367
-
368
- return {
369
- Program: {
370
- // Initialize compiler state at program entry
371
- enter(_path, state: any) {
372
- // Reset per-file counters to prevent ID leaks across compilations
373
- resetNodeIdCounter();
374
- resetDuringIdCounter();
375
- resetWatcherIdCounter();
376
- resetTransitionEffectIdCounter();
377
-
378
- const compilerState: CompilerState = {
379
- fields: [],
380
- states: new Map(),
381
- transitions: [],
382
- events: [],
383
- actionCounter: 0,
384
- metadata: {},
385
- errors: [],
386
- warnings: [],
387
- };
388
-
389
- // Extract metadata from JSDoc comments
390
- const program = _path.node;
391
- const leadingComments = program.leadingComments || [];
392
- extractMetadataFromComments(leadingComments, compilerState.metadata);
393
-
394
- // Also check leadingComments on first body statement (Babel attaches
395
- // top-of-file comments to the first statement, not the program node)
396
- if (program.body.length > 0) {
397
- const firstStmt = program.body[0];
398
- if (firstStmt.leadingComments) {
399
- extractMetadataFromComments(firstStmt.leadingComments, compilerState.metadata);
400
- }
401
- }
402
-
403
- // Extract component name from export and check for JSDoc on function
404
- const exportDeclaration = program.body.find((node) =>
405
- t.isExportNamedDeclaration(node) || t.isExportDefaultDeclaration(node)
406
- );
407
-
408
- if (exportDeclaration) {
409
- // Check the export statement's own leading comments (JSDoc may be on the export)
410
- if (exportDeclaration.leadingComments) {
411
- extractMetadataFromComments(exportDeclaration.leadingComments, compilerState.metadata);
412
- }
413
-
414
- if (t.isExportNamedDeclaration(exportDeclaration)) {
415
- const declaration = exportDeclaration.declaration;
416
- if (t.isFunctionDeclaration(declaration)) {
417
- if (declaration.id) {
418
- compilerState.metadata.name = declaration.id.name;
419
- }
420
- if (declaration.leadingComments) {
421
- extractMetadataFromComments(declaration.leadingComments, compilerState.metadata);
422
- }
423
- }
424
- } else if (t.isExportDefaultDeclaration(exportDeclaration)) {
425
- const declaration = exportDeclaration.declaration;
426
- if (t.isFunctionDeclaration(declaration)) {
427
- if (declaration.id) {
428
- compilerState.metadata.name = declaration.id.name;
429
- }
430
- if (declaration.leadingComments) {
431
- extractMetadataFromComments(declaration.leadingComments, compilerState.metadata);
432
- }
433
- } else if (t.isIdentifier(declaration)) {
434
- compilerState.metadata.name = declaration.name;
435
- }
436
- }
437
- }
438
-
439
- // Infer slug from file name if not set by JSDoc
440
- if (!compilerState.metadata.slug && state.filename) {
441
- const fileName = state.filename.split('/').pop() || '';
442
- compilerState.metadata.slug = fileName
443
- .replace(/\.workflow\.(tsx?|jsx?)$/, '')
444
- .replace(/\.(tsx?|jsx?)$/, '')
445
- .replace(/([A-Z])/g, '-$1')
446
- .toLowerCase()
447
- .replace(/^-/, '');
448
- // Track that this was auto-derived (model extractor may override)
449
- (compilerState.metadata as any).__slugAutoFallback = true;
450
- }
451
-
452
- // Store in state for later phases
453
- Object.assign(state, compilerState);
454
-
455
- // Model file detection — extract immediately since all declarations are top-level
456
- if (isModelFile(_path, state.filename)) {
457
- extractModelFile(_path, state);
458
- // Mark as model file so CallExpression/ReturnStatement visitors can skip
459
- (state as any).__isModelFile = true;
460
- // Model files default to category 'data' (creates a data workflow definition)
461
- if (!compilerState.metadata.category) {
462
- compilerState.metadata.category = 'data';
463
- }
464
- }
465
-
466
- // Grammar island extraction — detect tagged templates (cedar, sql, cron, etc.)
467
- // Works in both model files and regular workflow files
468
- extractGrammarIslands(_path, state);
469
-
470
- // Context workflow extraction — detect createContext() calls
471
- if (hasContextCreation(_path)) {
472
- extractContextWorkflows(_path, state);
473
- }
474
-
475
- // Server action file detection — extract exported functions
476
- if (isServerActionFile(state.filename)) {
477
- extractServerActions(_path, state);
478
- (state as any).__isServerActionFile = true;
479
- }
480
- },
481
-
482
- // After all extraction, emit Pure Form JSON
483
- exit(_path, state: any) {
484
- const compilerState = state as CompilerState;
485
-
486
- // Convert to ExtractedWorkflow and emit IR, engine definition, and canonical tree
487
- const extracted = compilerStateToWorkflow(compilerState, compilerState.metadata);
488
- const ir = emitIR(extracted);
489
- const definition = emitWorkflowDefinition(extracted);
490
- const canonical = emitCanonical(extracted, state.filename);
491
-
492
- // Attach errors/warnings to IR
493
- if (compilerState.errors && compilerState.errors.length > 0) {
494
- if (!ir.metadata) ir.metadata = {};
495
- (ir.metadata as any).errors = compilerState.errors;
496
- }
497
- if (compilerState.warnings && compilerState.warnings.length > 0) {
498
- if (!ir.metadata) ir.metadata = {};
499
- (ir.metadata as any).warnings = compilerState.warnings;
500
- }
501
-
502
- // Attach all outputs to file metadata for compile script to consume
503
- if (!state.file.metadata) state.file.metadata = {};
504
- state.file.metadata.mindmatrixIR = ir;
505
- state.file.metadata.mindmatrixDefinition = definition;
506
- state.file.metadata.mindmatrixCanonical = canonical;
507
- },
508
- },
509
-
510
- // Extract metadata from function declarations with JSDoc
511
- FunctionDeclaration(path, state: any) {
512
- const comments = path.node.leadingComments || [];
513
- const compilerState = state as CompilerState;
514
- extractMetadataFromComments(comments, compilerState.metadata);
515
- },
516
-
517
- // Main hook extraction dispatcher
518
- CallExpression(path, state) {
519
- const callee = path.node.callee;
520
- if (!t.isIdentifier(callee)) return;
521
-
522
- const compilerState = state as CompilerState;
523
- const hookName = callee.name;
524
-
525
- // Strict mode enforcement — ban forbidden hooks
526
- if (mode === 'strict' && STRICT_BANNED_HOOKS[hookName]) {
527
- const error: ReactCompilerError = {
528
- code: STRICT_BANNED_HOOKS[hookName] as any,
529
- message: `${hookName}() is not allowed in strict mode. Use @mindmatrix/react effect hooks instead.`,
530
- line: path.node.loc?.start.line,
531
- column: path.node.loc?.start.column,
532
- severity: 'error',
533
- };
534
- if (!compilerState.errors) compilerState.errors = [];
535
- compilerState.errors.push(error);
536
- return;
537
- }
538
-
539
- // Infer mode: warn about useEffect (component becomes opaque)
540
- if (mode === 'infer' && hookName === 'useEffect') {
541
- const warning: ReactCompilerError = {
542
- code: 'INFER_RAW_JSX',
543
- message: `useEffect() found in infer mode — component will be treated as an opaque [raw jsx] grammar island.`,
544
- line: path.node.loc?.start.line,
545
- column: path.node.loc?.start.column,
546
- severity: 'warning',
547
- };
548
- if (!compilerState.warnings) compilerState.warnings = [];
549
- compilerState.warnings.push(warning);
550
- return;
551
- }
552
-
553
- switch (hookName) {
554
- case 'useState':
555
- extractStates(path, state);
556
- break;
557
- case 'useOnEnter':
558
- case 'useOnExit':
559
- extractEffects(path, state);
560
- break;
561
- case 'useTransition':
562
- extractTransitions(path, state);
563
- break;
564
- case 'useOnEvent':
565
- extractEvents(path, state);
566
- break;
567
- case 'useWhileIn':
568
- extractDuring(path, state);
569
- break;
570
- case 'useOnChange':
571
- extractChangeWatcher(path, state);
572
- break;
573
- case 'useComputed':
574
- extractComputed(path, state);
575
- break;
576
- case 'useOnTransition':
577
- extractTransitionEffect(path, state);
578
- break;
579
- case 'useQuery':
580
- extractQueryDataSource(path, state);
581
- break;
582
- case 'useMutation':
583
- extractMutationDataSource(path, state);
584
- break;
585
- case 'useDuringAction':
586
- // useDuringAction({ state, action, intervalMs }) → during action (alias for useWhileIn)
587
- extractDuringAction(path, state);
588
- break;
589
- case 'useRole':
590
- // useRole('admin') → records role dependency in metadata
591
- extractRoleDependency(path, state);
592
- break;
593
- case 'useView':
594
- // useView('my-view') → records view dependency in metadata
595
- extractViewDependency(path, state);
596
- break;
597
- case 'useParams':
598
- // useParams() → records that the workflow accepts invocation params
599
- extractParamsUsage(path, state);
600
- break;
601
- case 'useExpressionLibrary':
602
- // useExpressionLibrary('tax-calc') → records library dependency
603
- extractLibraryDependency(path, state);
604
- break;
605
- case 'useServerAction':
606
- // useServerAction('approve', { instanceId }) → records server action dependency
607
- extractServerActionHook(path, state);
608
- break;
609
- case 'useServerState':
610
- // useServerState(instanceId) → records SSE state subscription
611
- extractServerState(path, state);
612
- break;
613
- }
614
- },
615
-
616
- // Track imports: model imports for useQuery(modelRef) resolution + strict mode validation
617
- ImportDeclaration(path, state) {
618
- const compilerState = state as CompilerState;
619
- const source = path.node.source.value;
620
-
621
- // Track model imports: imports from paths containing /models/ or ending with .model
622
- // e.g. import ChannelModel from '../models/channel'
623
- // e.g. import { Channel } from './models/channel.model'
624
- if (source.match(/\/models\/|\.model(?:\.[tj]sx?)?$/)) {
625
- if (!compilerState.metadata) compilerState.metadata = {};
626
- const meta = compilerState.metadata as Record<string, unknown>;
627
- if (!meta.__modelImports) meta.__modelImports = {};
628
- const imports = meta.__modelImports as Record<string, string>;
629
-
630
- for (const specifier of path.node.specifiers) {
631
- if (t.isImportDefaultSpecifier(specifier) || t.isImportSpecifier(specifier)) {
632
- imports[specifier.local.name] = source;
633
- }
634
- }
635
- }
636
-
637
- // Strict mode: validate imports
638
- if (mode !== 'strict') return;
639
-
640
- // Allow @mindmatrix/* imports and react itself (for types)
641
- if (
642
- source.startsWith('@mindmatrix/') ||
643
- source === 'react' ||
644
- source.startsWith('react/') ||
645
- source.startsWith('.') ||
646
- source.startsWith('/')
647
- ) {
648
- return;
649
- }
650
-
651
- const error: ReactCompilerError = {
652
- code: 'STRICT_FORBIDDEN_IMPORT',
653
- message: `Import from '${source}' is not allowed in strict mode. Only @mindmatrix/* and relative imports are permitted.`,
654
- line: path.node.loc?.start.line,
655
- column: path.node.loc?.start.column,
656
- severity: 'error',
657
- };
658
- if (!compilerState.errors) compilerState.errors = [];
659
- compilerState.errors.push(error);
660
- },
661
-
662
- // Track derived/computed variable declarations for expression inlining.
663
- //
664
- // Three cases:
665
- // 1. `const hasContent = expr` → registerDerivedVar (inline the expression)
666
- // 2. `const users = useQuery(...)` → registerDerivedVar as $instance.{slug}
667
- // (data source results are available on $instance by their slug)
668
- // 3. `const sendMsg = useMutation(...)` → registerDerivedVar as $action.transition
669
- // (mutation results are action callbacks)
670
- // 4. `const memo = useMemo(() => expr, [])` → registerDerivedVar with body expr
671
- VariableDeclarator(path, state) {
672
- const compilerState = state as CompilerState;
673
- if (!compilerState.metadata) return;
674
-
675
- const id = path.node.id;
676
- const init = path.node.init;
677
-
678
- // Skip array destructuring (useState pattern: const [x, setX] = useState(...))
679
- if (t.isArrayPattern(id)) return;
680
- if (!t.isIdentifier(id) || !init || !t.isExpression(init)) return;
681
-
682
- // Only track declarations inside the exported component function body
683
- const parentFn = path.getFunctionParent();
684
- if (!parentFn) return;
685
- const parentNode = parentFn.parentPath;
686
- const isExportedDirectly =
687
- parentNode?.isExportDefaultDeclaration() ||
688
- parentNode?.isExportNamedDeclaration();
689
- let isExportedByName = false;
690
- if (!isExportedDirectly && parentNode?.isVariableDeclarator()) {
691
- const varId = parentNode.node.id;
692
- if (t.isIdentifier(varId)) {
693
- isExportedByName = varId.name === compilerState.metadata?.name;
694
- }
695
- }
696
- if (!isExportedDirectly && !isExportedByName) return;
697
-
698
- // Handle hook call results
699
- if (t.isCallExpression(init) && t.isIdentifier(init.callee)) {
700
- const callee = init.callee.name;
701
-
702
- // useQuery result → reference as $instance.{varName}
703
- // The data source is already extracted by extractQueryDataSource.
704
- // At runtime, the CTR makes query results available on $instance.
705
- if (callee === 'useQuery') {
706
- // Use optional member expression: $instance?.{varName}
707
- // Data sources may not be loaded yet on initial render.
708
- registerDerivedVar(id.name, t.optionalMemberExpression(
709
- t.identifier('$instance'),
710
- t.identifier(id.name),
711
- false, // computed
712
- true, // optional
713
- ));
714
- return;
715
- }
716
-
717
- // useMutation result → reference as $action.transition
718
- if (callee === 'useMutation') {
719
- registerDerivedVar(id.name, t.memberExpression(
720
- t.identifier('$action'),
721
- t.identifier('transition'),
722
- ));
723
- return;
724
- }
725
-
726
- // useServerAction result → reference as $action.serverAction
727
- if (callee === 'useServerAction') {
728
- registerDerivedVar(id.name, t.memberExpression(
729
- t.identifier('$action'),
730
- t.identifier('serverAction'),
731
- ));
732
- return;
733
- }
734
-
735
- // useServerState result → reference as $instance.serverState
736
- if (callee === 'useServerState') {
737
- registerDerivedVar(id.name, t.optionalMemberExpression(
738
- t.identifier('$instance'),
739
- t.identifier('serverState'),
740
- false,
741
- true,
742
- ));
743
- return;
744
- }
745
-
746
- // useMemo → extract the callback body expression
747
- if (callee === 'useMemo' && init.arguments.length >= 1) {
748
- const callback = init.arguments[0];
749
- if (t.isArrowFunctionExpression(callback)) {
750
- if (t.isExpression(callback.body)) {
751
- // () => expression
752
- registerDerivedVar(id.name, callback.body);
753
- } else if (t.isBlockStatement(callback.body)) {
754
- // () => { return expression; }
755
- const retStmt = callback.body.body.find(s => t.isReturnStatement(s)) as t.ReturnStatement | undefined;
756
- if (retStmt?.argument && t.isExpression(retStmt.argument)) {
757
- registerDerivedVar(id.name, retStmt.argument);
758
- }
759
- }
760
- }
761
- return;
762
- }
763
-
764
- // Skip other hooks (useState handled by extractStates, etc.)
765
- if (callee.startsWith('use')) return;
766
- }
767
-
768
- // Regular const declaration: inline the expression
769
- registerDerivedVar(id.name, init);
770
- },
771
-
772
- // Extract JSX from function component return.
773
- // Only extract from the exported component function's direct return,
774
- // not from nested callbacks (Each render functions, helper components, etc.).
775
- ReturnStatement(path, state) {
776
- if (!t.isJSXElement(path.node.argument) && !t.isJSXFragment(path.node.argument)) return;
777
-
778
- // Walk up to find the nearest enclosing function
779
- const parentFn = path.getFunctionParent();
780
- if (!parentFn) return;
781
-
782
- // Check if this function is the exported component:
783
- // export default function Foo() { ... }
784
- // export function Foo() { ... }
785
- // export default Foo (where Foo is defined as function above)
786
- const parentNode = parentFn.parentPath;
787
- const isExportedDirectly =
788
- parentNode?.isExportDefaultDeclaration() ||
789
- parentNode?.isExportNamedDeclaration();
790
-
791
- // Also handle: const Foo = () => { return <JSX/> }; export default Foo;
792
- // In this case parentFn is the arrow/function expression assigned to a variable.
793
- // We check if the variable name matches the exported identifier.
794
- let isExportedByName = false;
795
- if (!isExportedDirectly && parentNode?.isVariableDeclarator()) {
796
- const varId = parentNode.node.id;
797
- if (t.isIdentifier(varId)) {
798
- isExportedByName = varId.name === (state as CompilerState).metadata?.name;
799
- }
800
- }
801
-
802
- if (isExportedDirectly || isExportedByName) {
803
- extractComponents(path, state);
804
- }
805
- },
806
- };
807
- }