@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,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
- }