@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,502 +0,0 @@
1
- /**
2
- * Model Round-Trip Test — validates full channel model → complete IR.
3
- *
4
- * This is the acceptance test for model file compilation.
5
- */
6
-
7
- import { describe, it, expect } from 'vitest';
8
- import { transformSync } from '@babel/core';
9
- import babelPlugin from '../babel';
10
- import type { IRWorkflowDefinition } from '@mindmatrix/player-core';
11
-
12
- function compileModel(code: string, filename = 'models/channel.ts'): IRWorkflowDefinition {
13
- const result = transformSync(code, {
14
- filename,
15
- plugins: [[babelPlugin, { mode: 'strict' }]],
16
- parserOpts: { plugins: ['typescript'], attachComment: true },
17
- });
18
- return (result as any)?.metadata?.mindmatrixIR;
19
- }
20
-
21
- function compileDefinition(code: string, filename = 'models/channel.ts'): Record<string, unknown> {
22
- const result = transformSync(code, {
23
- filename,
24
- plugins: [[babelPlugin, { mode: 'strict' }]],
25
- parserOpts: { plugins: ['typescript'], attachComment: true },
26
- });
27
- return (result as any)?.metadata?.mindmatrixDefinition;
28
- }
29
-
30
- const CHANNEL_MODEL = `
31
- /**
32
- * @workflow slug="mm-chat-channel-model" version="1.0.0" category="model"
33
- * @description Channel lifecycle management
34
- */
35
- export interface Channel {
36
- name: string;
37
- description: string;
38
- status: 'draft' | 'active' | 'archived';
39
- memberCount: number;
40
- isPublic: boolean;
41
- createdAt: Date;
42
- tags: string[];
43
- }
44
-
45
- export const transitions = {
46
- activate: {
47
- from: 'draft',
48
- to: 'active',
49
- guard: '$instance.name != ""',
50
- required_fields: ['name'],
51
- },
52
- archive: {
53
- from: 'active',
54
- to: 'archived',
55
- guard: '$user.role == "admin"',
56
- roles: ['admin', 'owner'],
57
- },
58
- reactivate: {
59
- from: 'archived',
60
- to: 'active',
61
- roles: ['admin'],
62
- },
63
- };
64
-
65
- export const endStates = ['deleted'];
66
- export const cancelledStates = ['cancelled'];
67
-
68
- export const hooks = {
69
- on_enter_active: {
70
- emit: 'channel:activated',
71
- actions: ['notifyMembers', 'indexForSearch'],
72
- },
73
- on_exit_active: {
74
- actions: ['logDeactivation'],
75
- },
76
- };
77
-
78
- export const runtime = 'backend';
79
-
80
- export const fieldOptions = {
81
- name: {
82
- scope: 'instance',
83
- persistence: 'durable',
84
- sync: 'tenant',
85
- editable_in_states: ['draft'],
86
- validation: { minLength: 1, maxLength: 100 },
87
- },
88
- memberCount: {
89
- computed: 'count(refs("member", { channel_id: $instance.id }))',
90
- computed_deps: [],
91
- visible_in_states: ['active', 'archived'],
92
- editable_by_roles: ['admin'],
93
- },
94
- isPublic: {
95
- editable_by_roles: ['admin', 'owner'],
96
- visible_when: '$instance.status != "deleted"',
97
- },
98
- };
99
- `;
100
-
101
- describe('Model Round-Trip — Channel', () => {
102
- it('should have correct metadata', () => {
103
- const ir = compileModel(CHANNEL_MODEL);
104
- expect(ir.slug).toBe('mm-chat-channel-model');
105
- expect(ir.name).toBe('Channel');
106
- expect(ir.version).toBe('1.0.0');
107
- expect(ir.category).toBe('model');
108
- expect(ir.metadata?.runtime).toBe('backend');
109
- });
110
-
111
- it('should have 7 fields', () => {
112
- const ir = compileModel(CHANNEL_MODEL);
113
- expect(ir.fields).toHaveLength(7);
114
- const fieldNames = ir.fields.map((f) => f.name);
115
- expect(fieldNames).toEqual(['name', 'description', 'status', 'memberCount', 'isPublic', 'createdAt', 'tags']);
116
- });
117
-
118
- it('should have correct field types', () => {
119
- const ir = compileModel(CHANNEL_MODEL);
120
- const typeMap = Object.fromEntries(ir.fields.map((f) => [f.name, f.type]));
121
- expect(typeMap.name).toBe('text');
122
- expect(typeMap.description).toBe('text');
123
- expect(typeMap.status).toBe('select');
124
- expect(typeMap.memberCount).toBe('number');
125
- expect(typeMap.isPublic).toBe('boolean');
126
- expect(typeMap.createdAt).toBe('datetime');
127
- expect(typeMap.tags).toBe('multi_select');
128
- });
129
-
130
- it('should have status field with select options', () => {
131
- const ir = compileModel(CHANNEL_MODEL);
132
- const status = ir.fields.find((f) => f.name === 'status');
133
- expect(status!.validation?.options).toEqual(['draft', 'active', 'archived']);
134
- });
135
-
136
- it('should have 3 transitions with guards and roles', () => {
137
- const ir = compileModel(CHANNEL_MODEL);
138
- expect(ir.transitions).toHaveLength(3);
139
-
140
- const activate = ir.transitions.find((t) => t.name === 'activate');
141
- expect(activate!.from).toEqual(['draft']);
142
- expect(activate!.to).toBe('active');
143
- expect(activate!.conditions).toBeDefined();
144
- expect(activate!.required_fields).toEqual(['name']);
145
-
146
- const archive = ir.transitions.find((t) => t.name === 'archive');
147
- expect(archive!.roles).toEqual(['admin', 'owner']);
148
- expect(archive!.conditions).toBeDefined();
149
-
150
- const reactivate = ir.transitions.find((t) => t.name === 'reactivate');
151
- expect(reactivate!.roles).toEqual(['admin']);
152
- });
153
-
154
- it('should have states from union + endStates + cancelledStates', () => {
155
- const ir = compileModel(CHANNEL_MODEL);
156
- const stateMap = Object.fromEntries(ir.states.map((s) => [s.name, s.type]));
157
-
158
- expect(stateMap.draft).toBe('START');
159
- expect(stateMap.active).toBe('REGULAR');
160
- expect(stateMap.archived).toBe('REGULAR');
161
- expect(stateMap.deleted).toBe('END');
162
- expect(stateMap.cancelled).toBe('CANCELLED');
163
- });
164
-
165
- it('should have on_enter hooks on active state', () => {
166
- const ir = compileModel(CHANNEL_MODEL);
167
- const active = ir.states.find((s) => s.name === 'active');
168
- expect(active!.on_enter).toHaveLength(3); // 1 emit + 2 server actions
169
- expect(active!.on_enter[0].type).toBe('emit_event');
170
- expect(active!.on_enter[0].config.event).toBe('channel:activated');
171
- expect(active!.on_enter[1].type).toBe('server_action');
172
- expect(active!.on_enter[1].config.handler).toBe('notifyMembers');
173
- expect(active!.on_enter[2].type).toBe('server_action');
174
- expect(active!.on_enter[2].config.handler).toBe('indexForSearch');
175
- });
176
-
177
- it('should have on_exit hooks on active state', () => {
178
- const ir = compileModel(CHANNEL_MODEL);
179
- const active = ir.states.find((s) => s.name === 'active');
180
- expect(active!.on_exit).toHaveLength(1);
181
- expect(active!.on_exit[0].type).toBe('server_action');
182
- expect(active!.on_exit[0].config.handler).toBe('logDeactivation');
183
- });
184
-
185
- it('should have field ACL on name field', () => {
186
- const ir = compileModel(CHANNEL_MODEL);
187
- const name = ir.fields.find((f) => f.name === 'name');
188
- expect(name!.editable_in_states).toEqual(['draft']);
189
- expect(name!.state_home).toEqual({
190
- scope: 'instance',
191
- persistence: 'durable',
192
- sync: 'tenant',
193
- });
194
- expect(name!.validation?.minLength).toBe(1);
195
- expect(name!.validation?.maxLength).toBe(100);
196
- });
197
-
198
- it('should have computed field on memberCount', () => {
199
- const ir = compileModel(CHANNEL_MODEL);
200
- const memberCount = ir.fields.find((f) => f.name === 'memberCount');
201
- expect(memberCount!.computed).toBe('count(refs("member", { channel_id: $instance.id }))');
202
- expect(memberCount!.computed_deps).toEqual([]);
203
- expect(memberCount!.visible_in_states).toEqual(['active', 'archived']);
204
- expect(memberCount!.editable_by_roles).toEqual(['admin']);
205
- });
206
-
207
- it('should have visible_when on isPublic', () => {
208
- const ir = compileModel(CHANNEL_MODEL);
209
- const isPublic = ir.fields.find((f) => f.name === 'isPublic');
210
- expect(isPublic!.editable_by_roles).toEqual(['admin', 'owner']);
211
- expect(isPublic!.visible_when).toBe('$instance.status != "deleted"');
212
- });
213
-
214
- it('should produce valid engine-compatible definition', () => {
215
- const def = compileDefinition(CHANNEL_MODEL);
216
- expect(def.id).toBe('def-mm-chat-channel-model');
217
- expect(def.slug).toBe('mm-chat-channel-model');
218
- expect(def.version).toBe('1.0.0');
219
- expect(def.category).toBe('model');
220
-
221
- const states = def.states as any[];
222
- expect(states.length).toBeGreaterThanOrEqual(5);
223
-
224
- const fields = def.fields as any[];
225
- expect(fields.length).toBe(7);
226
-
227
- const transitions = def.transitions as any[];
228
- expect(transitions.length).toBe(3);
229
- });
230
- });
231
-
232
- describe('Model Round-Trip — Message', () => {
233
- const MESSAGE_MODEL = `
234
- export interface Message {
235
- content: string;
236
- senderId: string;
237
- status: 'composing' | 'sent' | 'delivered' | 'read';
238
- isEdited: boolean;
239
- }
240
-
241
- export const transitions = {
242
- send: {
243
- from: 'composing',
244
- to: 'sent',
245
- guard: '$instance.content != ""',
246
- required_fields: ['content', 'senderId'],
247
- },
248
- deliver: {
249
- from: 'sent',
250
- to: 'delivered',
251
- auto: true,
252
- },
253
- markRead: {
254
- from: 'delivered',
255
- to: 'read',
256
- },
257
- edit: {
258
- from: ['sent', 'delivered', 'read'],
259
- to: 'sent',
260
- guard: '$user.id == $instance.senderId',
261
- },
262
- };
263
-
264
- export const endStates = ['deleted'];
265
-
266
- export const hooks = {
267
- on_enter_sent: { emit: 'message:sent', actions: ['notifyChannel'] },
268
- on_enter_read: { emit: 'message:read' },
269
- };
270
-
271
- export const runtime = 'collaborative';
272
-
273
- export const fieldOptions = {
274
- content: {
275
- editable_in_states: ['composing'],
276
- editable_when: '$user.id == $instance.senderId',
277
- },
278
- isEdited: {
279
- computed: '$instance.editedAt != null',
280
- computed_deps: ['editedAt'],
281
- },
282
- };
283
- `;
284
-
285
- it('should have 4 fields and 4 transitions', () => {
286
- const ir = compileModel(MESSAGE_MODEL, 'models/message.ts');
287
- expect(ir.fields).toHaveLength(4);
288
- expect(ir.transitions).toHaveLength(4);
289
- });
290
-
291
- it('should have auto transition for deliver', () => {
292
- const ir = compileModel(MESSAGE_MODEL, 'models/message.ts');
293
- const deliver = ir.transitions.find((t) => t.name === 'deliver');
294
- expect(deliver!.auto).toBe(true);
295
- });
296
-
297
- it('should have edit transition from multiple states', () => {
298
- const ir = compileModel(MESSAGE_MODEL, 'models/message.ts');
299
- const edit = ir.transitions.find((t) => t.name === 'edit');
300
- expect(edit!.from).toEqual(['sent', 'delivered', 'read']);
301
- });
302
-
303
- it('should have computed isEdited field', () => {
304
- const ir = compileModel(MESSAGE_MODEL, 'models/message.ts');
305
- const isEdited = ir.fields.find((f) => f.name === 'isEdited');
306
- expect(isEdited!.computed).toBe('$instance.editedAt != null');
307
- expect(isEdited!.computed_deps).toEqual(['editedAt']);
308
- });
309
-
310
- it('should have collaborative runtime', () => {
311
- const ir = compileModel(MESSAGE_MODEL, 'models/message.ts');
312
- expect(ir.metadata?.runtime).toBe('collaborative');
313
- });
314
-
315
- it('should have deleted as END state', () => {
316
- const ir = compileModel(MESSAGE_MODEL, 'models/message.ts');
317
- const deleted = ir.states.find((s) => s.name === 'deleted');
318
- expect(deleted!.type).toBe('END');
319
- });
320
- });
321
-
322
- // =============================================================================
323
- // defineModel() format compilation
324
- // =============================================================================
325
-
326
- const DEFINE_MODEL_EMPLOYEE = `
327
- import { defineModel } from '@mindmatrix/react';
328
-
329
- export default defineModel({
330
- slug: 'employee',
331
- version: '1.0.0',
332
- category: 'data',
333
- description: 'Employee lifecycle model',
334
- fields: {
335
- name: { type: 'string', required: true },
336
- email: { type: 'email', required: true },
337
- salary: { type: 'currency', default: 0 },
338
- isActive: { type: 'boolean', default: false },
339
- department: { type: 'string', enum: ['engineering', 'sales', 'hr'] },
340
- },
341
- states: {
342
- onboarding: { type: 'initial' },
343
- active: {},
344
- on_leave: {},
345
- terminated: { type: 'final' },
346
- },
347
- transitions: {
348
- activate: { from: 'onboarding', to: 'active' },
349
- request_leave: { from: 'active', to: 'on_leave', roles: ['hr', 'admin'] },
350
- return_from_leave: { from: 'on_leave', to: 'active' },
351
- terminate: { from: ['active', 'on_leave'], to: 'terminated', required_fields: ['reason'] },
352
- },
353
- });
354
- `;
355
-
356
- describe('defineModel() format', () => {
357
- it('should compile defineModel file into IR', () => {
358
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
359
- expect(ir).toBeDefined();
360
- expect(ir.slug).toBe('employee');
361
- });
362
-
363
- it('should extract metadata', () => {
364
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
365
- expect(ir.version).toBe('1.0.0');
366
- expect(ir.category).toBe('data');
367
- expect(ir.description).toBe('Employee lifecycle model');
368
- });
369
-
370
- it('should extract all fields', () => {
371
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
372
- expect(ir.fields).toHaveLength(5);
373
-
374
- const name = ir.fields.find(f => f.name === 'name');
375
- expect(name).toBeDefined();
376
- expect(name!.required).toBe(true);
377
-
378
- const salary = ir.fields.find(f => f.name === 'salary');
379
- expect(salary).toBeDefined();
380
- expect(salary!.type).toBe('currency');
381
-
382
- const isActive = ir.fields.find(f => f.name === 'isActive');
383
- expect(isActive).toBeDefined();
384
- expect(isActive!.type).toBe('boolean');
385
- });
386
-
387
- it('should extract enum options into validation', () => {
388
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
389
- const dept = ir.fields.find(f => f.name === 'department');
390
- expect(dept).toBeDefined();
391
- expect(dept!.validation?.options).toEqual(['engineering', 'sales', 'hr']);
392
- });
393
-
394
- it('should extract states with correct types', () => {
395
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
396
- const stateMap = new Map(ir.states.map(s => [s.name, s]));
397
-
398
- expect(stateMap.get('onboarding')!.type).toBe('START');
399
- expect(stateMap.get('active')!.type).toBe('REGULAR');
400
- expect(stateMap.get('on_leave')!.type).toBe('REGULAR');
401
- expect(stateMap.get('terminated')!.type).toBe('END');
402
- });
403
-
404
- it('should extract transitions with from/to/roles', () => {
405
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
406
-
407
- const activate = ir.transitions.find(t => t.name === 'activate');
408
- expect(activate).toBeDefined();
409
- expect(activate!.from).toEqual(['onboarding']);
410
- expect(activate!.to).toBe('active');
411
-
412
- const leave = ir.transitions.find(t => t.name === 'request_leave');
413
- expect(leave).toBeDefined();
414
- expect(leave!.roles).toEqual(['hr', 'admin']);
415
- });
416
-
417
- it('should handle multi-from transitions', () => {
418
- const ir = compileModel(DEFINE_MODEL_EMPLOYEE, 'models/employee.ts');
419
- const terminate = ir.transitions.find(t => t.name === 'terminate');
420
- expect(terminate).toBeDefined();
421
- expect(terminate!.from).toEqual(['active', 'on_leave']);
422
- expect(terminate!.required_fields).toEqual(['reason']);
423
- });
424
- });
425
-
426
- // =============================================================================
427
- // useQuery(modelRef) — identifier-based slug resolution
428
- // =============================================================================
429
-
430
- describe('useQuery(modelRef) identifier resolution', () => {
431
- function compileView(code: string, filename = 'app/page.tsx') {
432
- const result = transformSync(code, {
433
- filename,
434
- plugins: [[babelPlugin, { mode: 'lenient' }]],
435
- parserOpts: { plugins: ['typescript', 'jsx'], attachComment: true },
436
- });
437
- return (result as any)?.metadata?.mindmatrixIR as IRWorkflowDefinition;
438
- }
439
-
440
- it('should resolve useQuery with string literal', () => {
441
- const ir = compileView(`
442
- import { useQuery, Stack, Text } from '@mindmatrix/react';
443
- export default function Page() {
444
- const { data: employees } = useQuery('employee', { limit: 10 });
445
- return <Stack><Text value="hello" /></Stack>;
446
- }
447
- `);
448
- expect(ir).toBeDefined();
449
- const ds = ir.metadata?.dataSources as any[];
450
- expect(ds).toBeDefined();
451
- const empDs = ds.find((d: any) => d.slug === 'employee');
452
- expect(empDs).toBeDefined();
453
- expect(empDs.pageSize).toBe(10);
454
- });
455
-
456
- it('should resolve useQuery with imported model identifier', () => {
457
- const ir = compileView(`
458
- import employeeModel from '../models/employee';
459
- import { useQuery, Stack, Text } from '@mindmatrix/react';
460
- export default function Page() {
461
- const { data: employees } = useQuery(employeeModel, { state: 'active' });
462
- return <Stack><Text value="hello" /></Stack>;
463
- }
464
- `);
465
- expect(ir).toBeDefined();
466
- const ds = ir.metadata?.dataSources as any[];
467
- expect(ds).toBeDefined();
468
- const empDs = ds.find((d: any) => d.slug === 'employee');
469
- expect(empDs).toBeDefined();
470
- });
471
-
472
- it('should resolve useMutation with imported model identifier', () => {
473
- const ir = compileView(`
474
- import employeeModel from '../models/employee';
475
- import { useMutation, Stack, Text } from '@mindmatrix/react';
476
- export default function Page() {
477
- const mutation = useMutation(employeeModel);
478
- return <Stack><Text value="hello" /></Stack>;
479
- }
480
- `);
481
- expect(ir).toBeDefined();
482
- const targets = ir.metadata?.mutationTargets as string[];
483
- expect(targets).toBeDefined();
484
- expect(targets).toContain('employee');
485
- });
486
-
487
- it('should handle kebab-case model slugs from import path', () => {
488
- const ir = compileView(`
489
- import userProfileModel from '../models/user-profile';
490
- import { useQuery, Stack, Text } from '@mindmatrix/react';
491
- export default function Page() {
492
- const { data: profiles } = useQuery(userProfileModel);
493
- return <Stack><Text value="hello" /></Stack>;
494
- }
495
- `);
496
- expect(ir).toBeDefined();
497
- const ds = ir.metadata?.dataSources as any[];
498
- expect(ds).toBeDefined();
499
- const profileDs = ds.find((d: any) => d.slug === 'user-profile');
500
- expect(profileDs).toBeDefined();
501
- });
502
- });
@@ -1,112 +0,0 @@
1
- /**
2
- * Model Runtime Tests — validates runtime declaration, endStates, cancelledStates.
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { transformSync } from '@babel/core';
7
- import babelPlugin from '../babel';
8
- import type { IRWorkflowDefinition } from '@mindmatrix/player-core';
9
-
10
- function compileModel(code: string): IRWorkflowDefinition {
11
- const result = transformSync(code, {
12
- filename: 'models/test.ts',
13
- plugins: [[babelPlugin, { mode: 'strict' }]],
14
- parserOpts: { plugins: ['typescript'], attachComment: true },
15
- });
16
- return (result as any)?.metadata?.mindmatrixIR;
17
- }
18
-
19
- describe('Model Runtime', () => {
20
- it('should extract runtime profile', () => {
21
- const ir = compileModel(`
22
- export interface Service {
23
- status: 'running' | 'stopped';
24
- }
25
- export const transitions = {};
26
- export const runtime = 'backend';
27
- `);
28
-
29
- expect(ir.metadata?.runtime).toBe('backend');
30
- });
31
-
32
- it('should extract collaborative runtime', () => {
33
- const ir = compileModel(`
34
- export interface Doc {
35
- status: 'editing' | 'published';
36
- }
37
- export const transitions = {};
38
- export const runtime = 'collaborative';
39
- `);
40
-
41
- expect(ir.metadata?.runtime).toBe('collaborative');
42
- });
43
-
44
- it('should mark end states', () => {
45
- const ir = compileModel(`
46
- export interface Ticket {
47
- status: 'open' | 'closed';
48
- }
49
- export const transitions = {};
50
- export const endStates = ['closed'];
51
- `);
52
-
53
- const closedState = ir.states.find((s) => s.name === 'closed');
54
- expect(closedState).toBeDefined();
55
- expect(closedState!.type).toBe('END');
56
- });
57
-
58
- it('should mark cancelled states', () => {
59
- const ir = compileModel(`
60
- export interface Order {
61
- status: 'pending' | 'cancelled';
62
- }
63
- export const transitions = {};
64
- export const cancelledStates = ['cancelled'];
65
- `);
66
-
67
- const cancelledState = ir.states.find((s) => s.name === 'cancelled');
68
- expect(cancelledState).toBeDefined();
69
- expect(cancelledState!.type).toBe('CANCELLED');
70
- });
71
-
72
- it('should handle multiple end states', () => {
73
- const ir = compileModel(`
74
- export interface Process {
75
- status: 'active' | 'completed' | 'failed';
76
- }
77
- export const transitions = {};
78
- export const endStates = ['completed', 'failed'];
79
- `);
80
-
81
- const completed = ir.states.find((s) => s.name === 'completed');
82
- const failed = ir.states.find((s) => s.name === 'failed');
83
- expect(completed!.type).toBe('END');
84
- expect(failed!.type).toBe('END');
85
- });
86
-
87
- it('should mark first union option as START by default', () => {
88
- const ir = compileModel(`
89
- export interface Pipeline {
90
- status: 'draft' | 'review' | 'published';
91
- }
92
- export const transitions = {};
93
- `);
94
-
95
- const draft = ir.states.find((s) => s.name === 'draft');
96
- expect(draft!.type).toBe('START');
97
- });
98
-
99
- it('should create end states even if not in union', () => {
100
- const ir = compileModel(`
101
- export interface Thing {
102
- status: 'a' | 'b';
103
- }
104
- export const transitions = {};
105
- export const endStates = ['terminated'];
106
- `);
107
-
108
- const terminated = ir.states.find((s) => s.name === 'terminated');
109
- expect(terminated).toBeDefined();
110
- expect(terminated!.type).toBe('END');
111
- });
112
- });