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

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 (265) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +2814 -277
  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-52XHYD2V.mjs +214 -0
  11. package/dist/chunk-5GUFFFGL.mjs +148 -0
  12. package/dist/chunk-5RKTOVR5.mjs +244 -0
  13. package/dist/chunk-5YDMOO4X.mjs +214 -0
  14. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  15. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  16. package/dist/chunk-72QWL54I.mjs +175 -0
  17. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  18. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  19. package/dist/chunk-CIESM3BP.mjs +33 -0
  20. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  21. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  22. package/dist/chunk-DPIK3PJS.mjs +244 -0
  23. package/dist/chunk-E5IVH4RE.mjs +186 -0
  24. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  25. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  26. package/dist/chunk-ELO4TXJL.mjs +186 -0
  27. package/dist/chunk-EO6SYNCG.mjs +175 -0
  28. package/dist/chunk-FKRO52XH.mjs +3446 -0
  29. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  30. package/dist/chunk-FYT47UBU.mjs +5076 -0
  31. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  32. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  33. package/dist/chunk-HFXOUMTD.mjs +175 -0
  34. package/dist/chunk-HWIZ47US.mjs +214 -0
  35. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  36. package/dist/chunk-ICSIHQCG.mjs +148 -0
  37. package/dist/chunk-J7JUAHS4.mjs +186 -0
  38. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  39. package/dist/chunk-JQLWFCTM.mjs +214 -0
  40. package/dist/chunk-KFJJCQAL.mjs +148 -0
  41. package/dist/chunk-KJUIIEQE.mjs +186 -0
  42. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  43. package/dist/chunk-KSG4XSZF.mjs +175 -0
  44. package/dist/chunk-LF5N6DOU.mjs +175 -0
  45. package/dist/chunk-LJQCM2IM.mjs +214 -0
  46. package/dist/chunk-NTB7OEX2.mjs +2918 -0
  47. package/dist/chunk-NW6555WJ.mjs +186 -0
  48. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  49. package/dist/chunk-OPJKP747.mjs +7506 -0
  50. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  51. package/dist/chunk-QQHVYH2X.mjs +244 -0
  52. package/dist/chunk-S5QLWLLT.mjs +186 -0
  53. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  54. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  55. package/dist/chunk-THFYE5ZX.mjs +244 -0
  56. package/dist/chunk-VCAY2KGM.mjs +175 -0
  57. package/dist/chunk-WBYMW4NQ.mjs +3450 -0
  58. package/dist/chunk-WECAV6QB.mjs +148 -0
  59. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  60. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  61. package/dist/chunk-XG2X7AEA.mjs +175 -0
  62. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  63. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  64. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  65. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  66. package/dist/chunk-ZA37XTGA.mjs +175 -0
  67. package/dist/cli/index.js +13189 -6838
  68. package/dist/cli/index.mjs +140 -22
  69. package/dist/codemod/cli.mjs +1 -1
  70. package/dist/codemod/index.mjs +1 -1
  71. package/dist/config-PL24KEWL.mjs +219 -0
  72. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  73. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  74. package/dist/dev-server.d.mts +1 -1
  75. package/dist/dev-server.d.ts +1 -1
  76. package/dist/dev-server.js +4135 -440
  77. package/dist/dev-server.mjs +5 -5
  78. package/dist/envelope.js +2812 -275
  79. package/dist/envelope.mjs +3 -3
  80. package/dist/index.d.mts +161 -2
  81. package/dist/index.d.ts +161 -2
  82. package/dist/index.js +4429 -428
  83. package/dist/index.mjs +217 -9
  84. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  85. package/dist/init-DQDX3QK6.mjs +369 -0
  86. package/dist/init-EHO4VQ22.mjs +369 -0
  87. package/dist/init-UC3FWPIW.mjs +367 -0
  88. package/dist/init-UNSMVKIK.mjs +366 -0
  89. package/dist/init-UNV5XIDE.mjs +367 -0
  90. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  91. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  92. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  93. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  94. package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
  95. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  96. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  97. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  98. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  99. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  100. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  101. package/dist/project-decompiler-US7GAVIC.mjs +7 -0
  102. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  103. package/dist/pull-FUS5QYZS.mjs +109 -0
  104. package/dist/pull-LD5ENLGY.mjs +109 -0
  105. package/dist/pull-P44LDRWB.mjs +109 -0
  106. package/dist/testing/index.js +2822 -285
  107. package/dist/testing/index.mjs +2 -2
  108. package/dist/verify-SEIXUGN4.mjs +1833 -0
  109. package/dist/vite/index.js +2815 -278
  110. package/dist/vite/index.mjs +3 -3
  111. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  112. package/package.json +16 -6
  113. package/compile-blueprint-chat.mjs +0 -99
  114. package/compile-blueprint-glass-console.mjs +0 -98
  115. package/compile-chat-defs.mjs +0 -92
  116. package/examples/uber-app/tests/payment.test.tsx +0 -129
  117. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  118. package/package.json.backup +0 -86
  119. package/scripts/decompile.ts +0 -226
  120. package/scripts/seed-auth.ts +0 -267
  121. package/scripts/seed-uber.ts +0 -248
  122. package/scripts/validate-uber.ts +0 -119
  123. package/seed-blueprint-chat.mjs +0 -444
  124. package/seed-blueprint-glass-console.mjs +0 -445
  125. package/seed-compiled.mjs +0 -318
  126. package/src/RoundTripValidator.ts +0 -400
  127. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  128. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  129. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  130. package/src/__tests__/change-extractor.test.ts +0 -142
  131. package/src/__tests__/cli-pull.test.ts +0 -73
  132. package/src/__tests__/cli-test.test.ts +0 -72
  133. package/src/__tests__/component-extractor.test.ts +0 -331
  134. package/src/__tests__/context-extractor.test.ts +0 -145
  135. package/src/__tests__/decompiler.test.ts +0 -718
  136. package/src/__tests__/define-blueprint.test.ts +0 -133
  137. package/src/__tests__/definition-validator.test.ts +0 -519
  138. package/src/__tests__/during-extractor.test.ts +0 -152
  139. package/src/__tests__/effect-extractor.test.ts +0 -107
  140. package/src/__tests__/event-emission.test.ts +0 -127
  141. package/src/__tests__/examples.test.ts +0 -236
  142. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  143. package/src/__tests__/golden-suite.test.ts +0 -403
  144. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  145. package/src/__tests__/instance-key.test.ts +0 -82
  146. package/src/__tests__/ir-migration.test.ts +0 -255
  147. package/src/__tests__/lock-file.test.ts +0 -117
  148. package/src/__tests__/model-extractor.test.ts +0 -195
  149. package/src/__tests__/model-field-acl.test.ts +0 -237
  150. package/src/__tests__/model-hooks.test.ts +0 -130
  151. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  152. package/src/__tests__/model-roundtrip.test.ts +0 -502
  153. package/src/__tests__/model-runtime.test.ts +0 -112
  154. package/src/__tests__/model-transitions.test.ts +0 -183
  155. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  156. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  157. package/src/__tests__/project-compiler.test.ts +0 -546
  158. package/src/__tests__/project-decompiler.test.ts +0 -343
  159. package/src/__tests__/query-compilation.test.ts +0 -145
  160. package/src/__tests__/round-trip/PLAN.md +0 -158
  161. package/src/__tests__/round-trip/README.md +0 -52
  162. package/src/__tests__/round-trip/RESULTS.md +0 -86
  163. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  164. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  165. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  166. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  167. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  168. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  169. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  170. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  171. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  172. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  173. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  174. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  175. package/src/__tests__/round-trip-ir.test.ts +0 -300
  176. package/src/__tests__/round-trip.test.ts +0 -1212
  177. package/src/__tests__/route-merging.test.ts +0 -372
  178. package/src/__tests__/router-composition.test.ts +0 -489
  179. package/src/__tests__/router-extractor.test.ts +0 -176
  180. package/src/__tests__/server-action-extractor.test.ts +0 -128
  181. package/src/__tests__/smart-type-inference.test.ts +0 -365
  182. package/src/__tests__/source-envelope.test.ts +0 -284
  183. package/src/__tests__/source-fidelity.test.ts +0 -516
  184. package/src/__tests__/state-extractor.test.ts +0 -115
  185. package/src/__tests__/strict-mode.test.ts +0 -227
  186. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  187. package/src/__tests__/transition-extractor.test.ts +0 -68
  188. package/src/__tests__/ts-to-expression.test.ts +0 -462
  189. package/src/__tests__/type-generator.test.ts +0 -201
  190. package/src/__tests__/uber-validation.test.ts +0 -502
  191. package/src/action-compiler.ts +0 -361
  192. package/src/babel/emitters/experience-transform.ts +0 -199
  193. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  194. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  195. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  196. package/src/babel/extractors/change-extractor.ts +0 -199
  197. package/src/babel/extractors/component-extractor.ts +0 -907
  198. package/src/babel/extractors/computed-extractor.ts +0 -262
  199. package/src/babel/extractors/context-extractor.ts +0 -277
  200. package/src/babel/extractors/during-extractor.ts +0 -295
  201. package/src/babel/extractors/effect-extractor.ts +0 -340
  202. package/src/babel/extractors/event-extractor.ts +0 -235
  203. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  204. package/src/babel/extractors/model-extractor.ts +0 -1018
  205. package/src/babel/extractors/router-extractor.ts +0 -303
  206. package/src/babel/extractors/server-action-extractor.ts +0 -173
  207. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  208. package/src/babel/extractors/server-state-extractor.ts +0 -88
  209. package/src/babel/extractors/state-extractor.ts +0 -214
  210. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  211. package/src/babel/extractors/transition-extractor.ts +0 -143
  212. package/src/babel/index.ts +0 -24
  213. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  214. package/src/babel/visitor.ts +0 -807
  215. package/src/cli/auth.ts +0 -255
  216. package/src/cli/build.ts +0 -288
  217. package/src/cli/deploy.ts +0 -206
  218. package/src/cli/index.ts +0 -328
  219. package/src/cli/installer.ts +0 -261
  220. package/src/cli/lock-file.ts +0 -94
  221. package/src/cli/mmrc.ts +0 -22
  222. package/src/cli/pull.ts +0 -172
  223. package/src/cli/registry-client.ts +0 -175
  224. package/src/cli/test.ts +0 -397
  225. package/src/cli/type-generator.ts +0 -243
  226. package/src/codemod/__tests__/forward.test.ts +0 -239
  227. package/src/codemod/__tests__/reverse.test.ts +0 -145
  228. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  229. package/src/codemod/annotation.ts +0 -97
  230. package/src/codemod/classify.ts +0 -197
  231. package/src/codemod/cli.ts +0 -207
  232. package/src/codemod/control-flow.ts +0 -409
  233. package/src/codemod/forward.ts +0 -244
  234. package/src/codemod/import-manager.ts +0 -171
  235. package/src/codemod/index.ts +0 -120
  236. package/src/codemod/reverse.ts +0 -197
  237. package/src/codemod/rules.ts +0 -174
  238. package/src/codemod/state-transform.ts +0 -126
  239. package/src/decompiler/ast-builder.ts +0 -538
  240. package/src/decompiler/config-generator.ts +0 -151
  241. package/src/decompiler/index.ts +0 -315
  242. package/src/decompiler/project-decompiler.ts +0 -1776
  243. package/src/decompiler/project.ts +0 -862
  244. package/src/decompiler/split-strategy.ts +0 -140
  245. package/src/decompiler/state-emitter.ts +0 -1053
  246. package/src/decompiler/sx-emitter.ts +0 -318
  247. package/src/decompiler/workspace-hydrator.ts +0 -189
  248. package/src/dev-server.ts +0 -238
  249. package/src/envelope/fs-tree.ts +0 -217
  250. package/src/envelope/source-envelope.ts +0 -264
  251. package/src/envelope.ts +0 -315
  252. package/src/incremental-compiler.ts +0 -401
  253. package/src/index.ts +0 -99
  254. package/src/model-compiler.ts +0 -277
  255. package/src/project-compiler.ts +0 -1629
  256. package/src/route-extractor.ts +0 -333
  257. package/src/testing/index.ts +0 -32
  258. package/src/testing/snapshot.ts +0 -252
  259. package/src/testing/test-utils.ts +0 -226
  260. package/src/types.ts +0 -68
  261. package/src/vite/index.ts +0 -288
  262. package/test-compile.mjs +0 -142
  263. package/tsconfig.json +0 -25
  264. package/tsup.config.ts +0 -23
  265. package/vitest.config.ts +0 -9
@@ -1,862 +0,0 @@
1
- /**
2
- * project.ts — Multi-file project decompiler.
3
- *
4
- * Takes a full IRWorkflowDefinition and produces a project file tree:
5
- * - main.workflow.tsx (root layout + routing + state machine hooks)
6
- * - pages/*.tsx (separate page components per route/view)
7
- * - models/*.ts (TypeScript interfaces from field definitions)
8
- * - actions/*.server.ts (server action stubs from transition actions)
9
- * - mm.config.ts (blueprint configuration: defineBlueprint)
10
- *
11
- * This is the multi-file counterpart to the single-file decompile() function.
12
- * It splits complex definitions into cohesive, focused files matching the
13
- * envelope fs-tree file role conventions (view-entry, page, model, server-action, config).
14
- */
15
-
16
- import type {
17
- IRWorkflowDefinition,
18
- IRExperienceNode,
19
- IRFieldDefinition,
20
- IRStateDefinition,
21
- IRTransitionDefinition,
22
- IRActionDefinition,
23
- } from '@mindmatrix/player-core';
24
- import { decompile } from './index';
25
- import type { DecompilerInput, DecompileOptions } from './index';
26
- import type { FileRole } from '../envelope/fs-tree';
27
-
28
- // =============================================================================
29
- // Public Types
30
- // =============================================================================
31
-
32
- /** A single file in the decompiled project. */
33
- export interface ProjectFile {
34
- /** Relative path from project root (e.g. "main.workflow.tsx"). */
35
- path: string;
36
- /** File role matching the envelope fs-tree convention. */
37
- role: FileRole;
38
- /** Generated source code. */
39
- content: string;
40
- }
41
-
42
- /** Result of decompiling a definition into a multi-file project. */
43
- export interface DecompileProjectResult {
44
- /** All generated files. */
45
- files: ProjectFile[];
46
- /** The main entry file path. */
47
- entryFile: string;
48
- /** The slug used for naming. */
49
- slug: string;
50
- }
51
-
52
- /** Options for project decompilation. */
53
- export interface DecompileProjectOptions extends DecompileOptions {
54
- /** Skip generating mm.config.ts. Default: false. */
55
- skipConfig?: boolean;
56
- /** Skip generating model files. Default: false. */
57
- skipModels?: boolean;
58
- /** Skip generating server action stubs. Default: false. */
59
- skipActions?: boolean;
60
- /** Skip splitting pages into separate files. Default: false. */
61
- skipPages?: boolean;
62
- /** Skip decompiling child definitions into separate files. Default: false. */
63
- skipChildren?: boolean;
64
- }
65
-
66
- // =============================================================================
67
- // Name Helpers
68
- // =============================================================================
69
-
70
- function pascalCase(slug: string): string {
71
- return slug
72
- .split(/[-_]/)
73
- .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
74
- .join('');
75
- }
76
-
77
- function camelCase(str: string): string {
78
- return str.replace(/[-_]([a-z])/g, (_, c: string) => c.toUpperCase());
79
- }
80
-
81
- /** Map IR field type to TypeScript type string, with union type support. */
82
- function fieldTypeToTS(fieldType: string, field?: IRFieldDefinition): string {
83
- // Check for options → emit union type
84
- const options = getFieldOptions(field);
85
- if (options && options.length > 0 && (fieldType === 'select' || fieldType === 'text')) {
86
- return options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
87
- }
88
-
89
- switch (fieldType) {
90
- case 'text':
91
- case 'rich_text':
92
- case 'email':
93
- case 'url':
94
- case 'phone':
95
- case 'color':
96
- case 'select':
97
- return 'string';
98
- case 'number':
99
- case 'currency':
100
- case 'percentage':
101
- case 'rating':
102
- case 'duration':
103
- case 'auto_number':
104
- return 'number';
105
- case 'boolean':
106
- return 'boolean';
107
- case 'date':
108
- case 'datetime':
109
- case 'created_at':
110
- case 'updated_at':
111
- return 'Date';
112
- case 'multi_select':
113
- return 'string[]';
114
- case 'json':
115
- return 'Record<string, unknown>';
116
- case 'file':
117
- case 'image':
118
- return 'string'; // URL
119
- case 'relation':
120
- case 'lookup':
121
- return 'string'; // ID reference
122
- default:
123
- return 'unknown';
124
- }
125
- }
126
-
127
- /** Extracts options from field validation, metadata, or legacy field.options. */
128
- function getFieldOptions(field?: IRFieldDefinition): string[] | null {
129
- if (!field) return null;
130
- const f = field as unknown as Record<string, unknown>;
131
- const validation = f.validation as Record<string, unknown> | undefined;
132
- if (validation?.options && Array.isArray(validation.options)) return validation.options as string[];
133
- const meta = f.metadata as Record<string, unknown> | undefined;
134
- if (meta?.options && Array.isArray(meta.options)) return meta.options as string[];
135
- if (f.options && Array.isArray(f.options)) return f.options as string[];
136
- return null;
137
- }
138
-
139
- // =============================================================================
140
- // Model Generation
141
- // =============================================================================
142
-
143
- /**
144
- * Generates a TypeScript interface file from field definitions.
145
- *
146
- * Output example:
147
- * export interface Invoice {
148
- * amount: number;
149
- * vendor: string;
150
- * date: Date;
151
- * status: string;
152
- * }
153
- */
154
- function generateModelFile(
155
- slug: string,
156
- fields: IRFieldDefinition[],
157
- ): string {
158
- const typeName = pascalCase(slug);
159
- const lines: string[] = [
160
- `/**`,
161
- ` * ${typeName} — data model interface.`,
162
- ` *`,
163
- ` * Auto-generated from workflow definition "${slug}".`,
164
- ` * Fields: ${fields.length}`,
165
- ` */`,
166
- ``,
167
- `export interface ${typeName} {`,
168
- ];
169
-
170
- for (const field of fields) {
171
- const tsType = fieldTypeToTS(field.type, field);
172
- const optional = field.required ? '' : '?';
173
- const comment = field.label ? ` /** ${field.label} */` : '';
174
- if (comment) lines.push(` ${comment}`);
175
- lines.push(` ${camelCase(field.name)}${optional}: ${tsType};`);
176
- }
177
-
178
- lines.push(`}`);
179
- lines.push(``);
180
-
181
- // Export state enum if states exist (added by caller)
182
- return lines.join('\n');
183
- }
184
-
185
- /**
186
- * Generates a state enum from state definitions.
187
- *
188
- * Output:
189
- * export enum InvoiceState {
190
- * Draft = 'draft',
191
- * Submitted = 'submitted',
192
- * }
193
- */
194
- function generateStateEnum(
195
- slug: string,
196
- states: IRStateDefinition[],
197
- ): string {
198
- if (states.length === 0) return '';
199
-
200
- const enumName = `${pascalCase(slug)}State`;
201
- const lines: string[] = [
202
- ``,
203
- `export enum ${enumName} {`,
204
- ];
205
-
206
- for (const state of states) {
207
- const enumKey = pascalCase(state.name);
208
- lines.push(` ${enumKey} = '${state.name}',`);
209
- }
210
-
211
- lines.push(`}`);
212
- lines.push(``);
213
- return lines.join('\n');
214
- }
215
-
216
- // =============================================================================
217
- // Server Action Generation
218
- // =============================================================================
219
-
220
- /** Extracts unique server-side action types from states and transitions. */
221
- function extractServerActions(
222
- states: IRStateDefinition[],
223
- transitions: IRTransitionDefinition[],
224
- ): Array<{ name: string; type: string; config: Record<string, unknown> }> {
225
- const seen = new Set<string>();
226
- const actions: Array<{ name: string; type: string; config: Record<string, unknown> }> = [];
227
-
228
- const SERVER_ACTION_TYPES = new Set([
229
- 'http_request', 'webhook', 'call_webhook',
230
- 'notify', 'send_notification',
231
- 'call_workflow', 'spawn_instance', 'spawn_subworkflow',
232
- 'emit_event',
233
- ]);
234
-
235
- function collectFromActions(defs: IRActionDefinition[]) {
236
- for (const action of defs) {
237
- if (SERVER_ACTION_TYPES.has(action.type) && !seen.has(action.id)) {
238
- seen.add(action.id);
239
- const name = actionToFunctionName(action);
240
- actions.push({ name, type: action.type, config: action.config });
241
- }
242
- }
243
- }
244
-
245
- for (const state of states) {
246
- collectFromActions(state.on_enter);
247
- collectFromActions(state.on_exit);
248
- for (const during of state.during) {
249
- collectFromActions(during.actions);
250
- }
251
- }
252
- for (const trans of transitions) {
253
- collectFromActions(trans.actions);
254
- }
255
-
256
- return actions;
257
- }
258
-
259
- function actionToFunctionName(action: IRActionDefinition): string {
260
- // Use a descriptive name from config or fall back to type + id
261
- const slug = (action.config.name ?? action.config.event ?? action.config.slug ?? action.type) as string;
262
- return camelCase(String(slug).replace(/[^a-zA-Z0-9_-]/g, '-'));
263
- }
264
-
265
- /**
266
- * Generates a *.server.ts file with server action stubs.
267
- *
268
- * Output example:
269
- * import type { TransitionContext } from '@mindmatrix/react';
270
- *
271
- * export async function sendNotification(ctx: TransitionContext) {
272
- * // TODO: Implement notification logic
273
- * }
274
- */
275
- function generateServerActionFile(
276
- actions: Array<{ name: string; type: string; config: Record<string, unknown> }>,
277
- ): string {
278
- const lines: string[] = [
279
- `/**`,
280
- ` * Server actions — backend handlers for workflow transitions.`,
281
- ` *`,
282
- ` * These functions run server-side during state transitions.`,
283
- ` * Each receives a TransitionContext with instance data and utilities.`,
284
- ` */`,
285
- ``,
286
- `import type { TransitionContext } from '@mindmatrix/react';`,
287
- ``,
288
- ];
289
-
290
- for (const action of actions) {
291
- const comment = actionComment(action.type, action.config);
292
- lines.push(`/** ${comment} */`);
293
- lines.push(`export async function ${action.name}(ctx: TransitionContext): Promise<void> {`);
294
- lines.push(` const { instance, env } = ctx;`);
295
-
296
- // Emit a type-specific stub body
297
- switch (action.type) {
298
- case 'http_request':
299
- case 'webhook':
300
- case 'call_webhook': {
301
- const url = String(action.config.url ?? 'https://api.example.com/webhook');
302
- const method = String(action.config.method ?? 'POST');
303
- lines.push(` await fetch('${url}', {`);
304
- lines.push(` method: '${method}',`);
305
- lines.push(` headers: { 'Content-Type': 'application/json' },`);
306
- lines.push(` body: JSON.stringify({ instanceId: instance.id }),`);
307
- lines.push(` });`);
308
- break;
309
- }
310
- case 'notify':
311
- case 'send_notification': {
312
- const msg = String(action.config.message ?? action.config.body ?? 'Notification');
313
- lines.push(` // Send notification: "${msg}"`);
314
- lines.push(` await env.notify({ message: '${msg}', instanceId: instance.id });`);
315
- break;
316
- }
317
- case 'call_workflow':
318
- case 'spawn_instance':
319
- case 'spawn_subworkflow': {
320
- const slug = String(action.config.slug ?? action.config.workflow ?? 'child-workflow');
321
- lines.push(` await env.spawn('${slug}', { parentId: instance.id });`);
322
- break;
323
- }
324
- case 'emit_event': {
325
- const event = String(action.config.event ?? action.config.name ?? 'custom-event');
326
- lines.push(` await env.emit('${event}', { instanceId: instance.id });`);
327
- break;
328
- }
329
- default:
330
- lines.push(` // TODO: Implement ${action.type} logic`);
331
- }
332
-
333
- lines.push(`}`);
334
- lines.push(``);
335
- }
336
-
337
- return lines.join('\n');
338
- }
339
-
340
- function actionComment(type: string, config: Record<string, unknown>): string {
341
- switch (type) {
342
- case 'http_request':
343
- case 'webhook':
344
- case 'call_webhook':
345
- return `HTTP ${config.method ?? 'POST'} to ${config.url ?? 'webhook endpoint'}`;
346
- case 'notify':
347
- case 'send_notification':
348
- return `Send notification: ${config.message ?? config.body ?? ''}`;
349
- case 'call_workflow':
350
- case 'spawn_instance':
351
- case 'spawn_subworkflow':
352
- return `Spawn child workflow: ${config.slug ?? config.workflow ?? ''}`;
353
- case 'emit_event':
354
- return `Emit event: ${config.event ?? config.name ?? ''}`;
355
- default:
356
- return `Server action: ${type}`;
357
- }
358
- }
359
-
360
- // =============================================================================
361
- // Config Generation
362
- // =============================================================================
363
-
364
- /**
365
- * Generates mm.config.ts with defineBlueprint configuration.
366
- */
367
- function generateConfigFile(def: IRWorkflowDefinition): string {
368
- const lines: string[] = [
369
- `/**`,
370
- ` * Blueprint configuration for "${def.name || pascalCase(def.slug)}".`,
371
- ` */`,
372
- ``,
373
- `import { defineBlueprint } from '@mindmatrix/react';`,
374
- ``,
375
- `export default defineBlueprint({`,
376
- ` slug: '${def.slug}',`,
377
- ` name: '${(def.name || pascalCase(def.slug)).replace(/'/g, "\\'")}',`,
378
- ` version: '${def.version}',`,
379
- ` category: '${def.category}',`,
380
- ];
381
-
382
- if (def.description) {
383
- lines.push(` description: '${def.description.replace(/'/g, "\\'")}',`);
384
- }
385
-
386
- // Roles
387
- if (def.roles && def.roles.length > 0) {
388
- lines.push(` roles: [`);
389
- for (const role of def.roles) {
390
- const perms = role.permissions.map(p => `'${p}'`).join(', ');
391
- lines.push(` { name: '${role.name}', permissions: [${perms}] },`);
392
- }
393
- lines.push(` ],`);
394
- }
395
-
396
- lines.push(`});`);
397
- lines.push(``);
398
- return lines.join('\n');
399
- }
400
-
401
- // =============================================================================
402
- // Page Extraction
403
- // =============================================================================
404
-
405
- interface ExtractedPage {
406
- /** Route path or state name this page represents. */
407
- route: string;
408
- /** PascalCase component name for the page. */
409
- componentName: string;
410
- /** The experience subtree for this page. */
411
- tree: IRExperienceNode;
412
- /** File path relative to project root. */
413
- filePath: string;
414
- }
415
-
416
- /**
417
- * Extracts page components from an experience tree.
418
- *
419
- * Heuristics for page detection:
420
- * 1. Top-level children with visible_when conditions referencing route/state
421
- * 2. Children with id containing "page", "route", "view"
422
- * 3. Children wrapped in Show components with route conditions
423
- */
424
- function extractPages(
425
- experience: IRExperienceNode | undefined,
426
- slug: string,
427
- ): ExtractedPage[] {
428
- if (!experience || !experience.children) return [];
429
-
430
- const pages: ExtractedPage[] = [];
431
- const rootChildren = experience.children;
432
-
433
- for (const child of rootChildren) {
434
- // Check for route-conditional sections (visible_when with route/activeRoute)
435
- const isRoutePage = child.visible_when && (
436
- /activeRoute|route|isOn/i.test(child.visible_when)
437
- );
438
-
439
- // Check for page-like IDs
440
- const isPageId = child.id && (
441
- /page|route|view/i.test(child.id)
442
- );
443
-
444
- // Check for Show wrappers with route conditions
445
- const isShowRoute = child.component === 'Show' && child.config?.when &&
446
- /activeRoute|route/i.test(String(child.config.when));
447
-
448
- if (isRoutePage || isPageId || isShowRoute) {
449
- // Extract route name from visible_when or id
450
- const routeName = extractRouteName(child) || child.id || `page-${pages.length}`;
451
- const componentName = pascalCase(routeName.replace(/^\//, '').replace(/\//g, '-') || `${slug}-page`);
452
-
453
- pages.push({
454
- route: routeName,
455
- componentName,
456
- tree: child,
457
- filePath: `pages/${componentName}.tsx`,
458
- });
459
- }
460
- }
461
-
462
- return pages;
463
- }
464
-
465
- function extractRouteName(node: IRExperienceNode): string {
466
- // Try to extract route from visible_when: "activeRoute === '/dashboard'"
467
- if (node.visible_when) {
468
- const match = node.visible_when.match(/['"]\/([^'"]+)['"]/);
469
- if (match) return match[1];
470
- // Try: "isOnDashboard" → "dashboard"
471
- const boolMatch = node.visible_when.match(/isOn([A-Z]\w+)/);
472
- if (boolMatch) return boolMatch[1].toLowerCase();
473
- }
474
- // Try from Show when config
475
- if (node.config?.when) {
476
- const match = String(node.config.when).match(/['"]\/([^'"]+)['"]/);
477
- if (match) return match[1];
478
- }
479
- return '';
480
- }
481
-
482
- // =============================================================================
483
- // Page File Generation
484
- // =============================================================================
485
-
486
- /**
487
- * Generates a page component TSX file from an extracted page.
488
- * Uses the single-file decompiler for the experience subtree.
489
- */
490
- function generatePageFile(page: ExtractedPage, _slug: string): string {
491
- // Create a minimal definition with just the experience subtree
492
- const pageInput: DecompilerInput = {
493
- slug: page.route || 'page',
494
- name: page.componentName,
495
- version: '1.0.0',
496
- category: 'page',
497
- states: [],
498
- transitions: [],
499
- fields: [],
500
- roles: [],
501
- experience: page.tree,
502
- };
503
-
504
- const result = decompile(pageInput, {
505
- componentName: page.componentName,
506
- includeAnnotation: false,
507
- });
508
-
509
- return result.code;
510
- }
511
-
512
- // =============================================================================
513
- // Main Entry File Generation
514
- // =============================================================================
515
-
516
- /**
517
- * Generates the main.workflow.tsx file.
518
- *
519
- * When pages are extracted, this file contains:
520
- * - Field declarations (useState)
521
- * - State effects (useOnEnter/useOnExit)
522
- * - Transition declarations (useTransition)
523
- * - Page imports
524
- * - Root layout with page rendering
525
- */
526
- function generateMainFile(
527
- def: DecompilerInput,
528
- pages: ExtractedPage[],
529
- hasModel: boolean,
530
- serverActionNames: string[],
531
- options?: DecompileOptions,
532
- ): string {
533
- // If no pages to split, use the single-file decompiler directly
534
- if (pages.length === 0) {
535
- const result = decompile(def, options);
536
- return result.code;
537
- }
538
-
539
- // Build main file with page imports and layout
540
- const slug = def.slug;
541
- const componentName = options?.componentName ?? pascalCase(slug);
542
-
543
- const importLines: string[] = [];
544
-
545
- // Collect @mindmatrix/react imports based on what's used
546
- const reactImports = new Set<string>();
547
-
548
- // Fields need useState
549
- const nonComputed = def.fields.filter(f => !f.computed);
550
- if (nonComputed.length > 0) reactImports.add('useState');
551
-
552
- // State effects
553
- for (const state of def.states) {
554
- if (state.on_enter.length > 0) reactImports.add('useOnEnter');
555
- if (state.on_exit.length > 0) reactImports.add('useOnExit');
556
- if (state.during.length > 0) reactImports.add('useWhileIn');
557
- }
558
-
559
- // Transitions
560
- if (def.transitions.length > 0) reactImports.add('useTransition');
561
-
562
- // Layout atoms for the root wrapper
563
- reactImports.add('Stack');
564
-
565
- if (reactImports.size > 0) {
566
- const sorted = [...reactImports].sort();
567
- importLines.push(`import { ${sorted.join(', ')} } from '@mindmatrix/react';`);
568
- }
569
-
570
- // Model import
571
- if (hasModel) {
572
- const typeName = pascalCase(slug);
573
- importLines.push(`import type { ${typeName} } from './models/${slug}';`);
574
- }
575
-
576
- // Server action imports
577
- if (serverActionNames.length > 0) {
578
- const names = serverActionNames.join(', ');
579
- importLines.push(`import { ${names} } from './actions/${slug}.server';`);
580
- }
581
-
582
- // Page imports
583
- for (const page of pages) {
584
- importLines.push(`import { ${page.componentName} } from './${page.filePath.replace(/\.tsx$/, '')}';`);
585
- }
586
-
587
- // Build component body lines
588
- const bodyLines: string[] = [];
589
-
590
- // Field declarations
591
- for (const field of nonComputed) {
592
- const name = camelCase(field.name);
593
- const setter = `set${pascalCase(field.name)}`;
594
- const defaultVal = fieldDefaultLiteral(field);
595
- bodyLines.push(` const [${name}, ${setter}] = useState(${defaultVal});`);
596
- }
597
-
598
- if (nonComputed.length > 0) bodyLines.push(``);
599
-
600
- // State effects
601
- for (const state of def.states) {
602
- if (state.on_enter.length > 0) {
603
- const actions = state.on_enter.map(a => actionToInlineCode(a)).filter(Boolean);
604
- bodyLines.push(` useOnEnter('${state.name}', () => {`);
605
- for (const a of actions) bodyLines.push(` ${a}`);
606
- bodyLines.push(` });`);
607
- }
608
- if (state.on_exit.length > 0) {
609
- const actions = state.on_exit.map(a => actionToInlineCode(a)).filter(Boolean);
610
- bodyLines.push(` useOnExit('${state.name}', () => {`);
611
- for (const a of actions) bodyLines.push(` ${a}`);
612
- bodyLines.push(` });`);
613
- }
614
- }
615
-
616
- // Transitions
617
- for (const trans of def.transitions) {
618
- const varName = camelCase(trans.name);
619
- const from = trans.from.length === 1
620
- ? `'${trans.from[0]}'`
621
- : `[${trans.from.map(f => `'${f}'`).join(', ')}]`;
622
- bodyLines.push(` const ${varName} = useTransition('${trans.name}', { from: ${from}, to: '${trans.to}' });`);
623
- }
624
-
625
- if (def.transitions.length > 0) bodyLines.push(``);
626
-
627
- // Build root layout JSX with page rendering
628
- bodyLines.push(` return (`);
629
- bodyLines.push(` <Stack sx={{ minHeight: '100vh' }}>`);
630
- for (const page of pages) {
631
- bodyLines.push(` <${page.componentName} />`);
632
- }
633
- bodyLines.push(` </Stack>`);
634
- bodyLines.push(` );`);
635
-
636
- // Assemble file
637
- const lines: string[] = [
638
- ...importLines,
639
- ``,
640
- `/**`,
641
- ` * @workflow slug="${slug}" version="${def.version}" category="${def.category}"`,
642
- ...(def.description ? [` * @description ${def.description}`] : []),
643
- ` */`,
644
- `export default function ${componentName}() {`,
645
- ...bodyLines,
646
- `}`,
647
- ``,
648
- ];
649
-
650
- return lines.join('\n');
651
- }
652
-
653
- function fieldDefaultLiteral(field: IRFieldDefinition): string {
654
- if (field.default_value !== undefined) {
655
- if (typeof field.default_value === 'string') return `'${field.default_value}'`;
656
- return JSON.stringify(field.default_value);
657
- }
658
- switch (field.type) {
659
- case 'text': case 'rich_text': case 'email': case 'url': case 'phone':
660
- case 'color': case 'select':
661
- return "''";
662
- case 'number': case 'currency': case 'percentage': case 'rating':
663
- case 'duration': case 'auto_number':
664
- return '0';
665
- case 'boolean':
666
- return 'false';
667
- case 'date': case 'datetime':
668
- return 'null';
669
- case 'multi_select': case 'json':
670
- return '[]';
671
- default:
672
- return 'null';
673
- }
674
- }
675
-
676
- function actionToInlineCode(action: IRActionDefinition): string {
677
- switch (action.type) {
678
- case 'set_field': {
679
- const field = String(action.config.field ?? '');
680
- const setter = `set${pascalCase(field)}`;
681
- const val = action.config.expression
682
- ? String(action.config.expression)
683
- : JSON.stringify(action.config.value ?? null);
684
- return `${setter}(${val});`;
685
- }
686
- case 'log_event':
687
- case 'log':
688
- return `console.log('${action.config.message ?? action.config.event ?? action.type}');`;
689
- case 'noop':
690
- return '';
691
- default:
692
- return `// ${action.type}: ${JSON.stringify(action.config)}`;
693
- }
694
- }
695
-
696
- // =============================================================================
697
- // Main: decompileProject
698
- // =============================================================================
699
-
700
- /**
701
- * Decompiles an IR definition into a multi-file project structure.
702
- *
703
- * Produces files matching the envelope fs-tree file role conventions:
704
- * - main.workflow.tsx → view-entry (root layout + state machine)
705
- * - pages/*.tsx → page (per-route components)
706
- * - models/<slug>.ts → model (TypeScript interfaces)
707
- * - actions/<slug>.server.ts → server-action (backend handlers)
708
- * - mm.config.ts → config (defineBlueprint)
709
- *
710
- * For simple definitions (no states/transitions/models), falls back
711
- * to single-file output in main.workflow.tsx.
712
- */
713
- export function decompileProject(
714
- definition: DecompilerInput,
715
- options?: DecompileProjectOptions,
716
- ): DecompileProjectResult {
717
- const files: ProjectFile[] = [];
718
- const slug = definition.slug;
719
-
720
- // 1. Extract pages from experience tree
721
- const pages = options?.skipPages
722
- ? []
723
- : extractPages(definition.experience, slug);
724
-
725
- // 2. Extract server actions from states + transitions
726
- const serverActions = options?.skipActions
727
- ? []
728
- : extractServerActions(definition.states, definition.transitions);
729
-
730
- // 3. Generate model file
731
- const hasFields = definition.fields.length > 0;
732
- if (hasFields && !options?.skipModels) {
733
- let modelContent = generateModelFile(slug, definition.fields);
734
- if (definition.states.length > 0) {
735
- modelContent += generateStateEnum(slug, definition.states);
736
- }
737
- files.push({
738
- path: `models/${slug}.ts`,
739
- role: 'model',
740
- content: modelContent,
741
- });
742
- }
743
-
744
- // 4. Generate server action file
745
- if (serverActions.length > 0) {
746
- files.push({
747
- path: `actions/${slug}.server.ts`,
748
- role: 'server-action',
749
- content: generateServerActionFile(serverActions),
750
- });
751
- }
752
-
753
- // 5. Generate page files
754
- for (const page of pages) {
755
- files.push({
756
- path: page.filePath,
757
- role: 'page',
758
- content: generatePageFile(page, slug),
759
- });
760
- }
761
-
762
- // 6. Generate config file
763
- if (!options?.skipConfig) {
764
- files.push({
765
- path: 'mm.config.ts',
766
- role: 'config',
767
- content: generateConfigFile(definition),
768
- });
769
- }
770
-
771
- // 7. Decompile child definitions into separate .workflow.tsx files
772
- // Each child becomes its own file in the project (UBER-SCALE support)
773
- if (definition.childDefinitions && definition.childDefinitions.length > 0) {
774
- for (const child of definition.childDefinitions) {
775
- const childSlug = child.slug;
776
- const childFileName = `${childSlug}.workflow.tsx`;
777
-
778
- // Build a DecompilerInput from the child definition
779
- const childInput: DecompilerInput = {
780
- ...child,
781
- experience: (child as any).views?.default as IRExperienceNode | undefined,
782
- };
783
-
784
- // Generate model files for child data definitions
785
- if (child.category === 'data' && child.fields.length > 0 && !options?.skipModels) {
786
- let childModelContent = generateModelFile(childSlug, child.fields);
787
- if (child.states.length > 0) {
788
- childModelContent += generateStateEnum(childSlug, child.states);
789
- }
790
- files.push({
791
- path: `models/${childSlug}.ts`,
792
- role: 'model',
793
- content: childModelContent,
794
- });
795
- }
796
-
797
- // Generate server action files for child workflows with server actions
798
- if (!options?.skipActions) {
799
- const childServerActions = extractServerActions(child.states, child.transitions);
800
- if (childServerActions.length > 0) {
801
- files.push({
802
- path: `actions/${childSlug}.server.ts`,
803
- role: 'server-action',
804
- content: generateServerActionFile(childServerActions),
805
- });
806
- }
807
- }
808
-
809
- // Generate the child workflow file itself
810
- const childResult = decompile(childInput, {
811
- componentName: pascalCase(childSlug),
812
- includeAnnotation: true,
813
- });
814
-
815
- files.push({
816
- path: childFileName,
817
- role: 'view-entry',
818
- content: childResult.code,
819
- });
820
- }
821
- }
822
-
823
- // 8. Generate main entry file
824
- const mainContent = generateMainFile(
825
- definition,
826
- pages,
827
- hasFields && !options?.skipModels,
828
- serverActions.map(a => a.name),
829
- options,
830
- );
831
-
832
- files.push({
833
- path: 'main.workflow.tsx',
834
- role: 'view-entry',
835
- content: mainContent,
836
- });
837
-
838
- return {
839
- files,
840
- entryFile: 'main.workflow.tsx',
841
- slug,
842
- };
843
- }
844
-
845
- /**
846
- * Returns true if a definition is complex enough to warrant multi-file output.
847
- *
848
- * Criteria: has states + transitions + either fields or experience tree.
849
- */
850
- export function shouldDecompileAsProject(def: IRWorkflowDefinition): boolean {
851
- // Check for child definitions in metadata (UBER-SCALE projects)
852
- const meta = def.metadata as Record<string, unknown> | undefined;
853
- const childSlugs = meta?.childSlugs;
854
- const hasChildren = Array.isArray(childSlugs) && childSlugs.length > 0;
855
-
856
- return (
857
- hasChildren ||
858
- (def.states.length > 1 &&
859
- def.transitions.length > 0 &&
860
- (def.fields.length > 0 || def.category === 'blueprint'))
861
- );
862
- }