@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,237 +0,0 @@
1
- /**
2
- * Model Field ACL Tests — validates field-level access control, computed fields, validation.
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 Field ACL', () => {
20
- it('should apply visible_in_states', () => {
21
- const ir = compileModel(`
22
- export interface Item {
23
- name: string;
24
- balance: number;
25
- status: 'draft' | 'active';
26
- }
27
- export const transitions = {};
28
- export const fieldOptions = {
29
- balance: { visible_in_states: ['active'] },
30
- };
31
- `);
32
-
33
- const balance = ir.fields.find((f) => f.name === 'balance');
34
- expect(balance!.visible_in_states).toEqual(['active']);
35
- });
36
-
37
- it('should apply editable_in_states', () => {
38
- const ir = compileModel(`
39
- export interface Doc {
40
- title: string;
41
- status: 'draft' | 'published';
42
- }
43
- export const transitions = {};
44
- export const fieldOptions = {
45
- title: { editable_in_states: ['draft'] },
46
- };
47
- `);
48
-
49
- const title = ir.fields.find((f) => f.name === 'title');
50
- expect(title!.editable_in_states).toEqual(['draft']);
51
- });
52
-
53
- it('should apply visible_to_roles', () => {
54
- const ir = compileModel(`
55
- export interface Account {
56
- balance: number;
57
- status: 'a' | 'b';
58
- }
59
- export const transitions = {};
60
- export const fieldOptions = {
61
- balance: { visible_to_roles: ['admin', 'accountant'] },
62
- };
63
- `);
64
-
65
- const balance = ir.fields.find((f) => f.name === 'balance');
66
- expect(balance!.visible_to_roles).toEqual(['admin', 'accountant']);
67
- });
68
-
69
- it('should apply editable_by_roles', () => {
70
- const ir = compileModel(`
71
- export interface Config {
72
- setting: string;
73
- status: 'a' | 'b';
74
- }
75
- export const transitions = {};
76
- export const fieldOptions = {
77
- setting: { editable_by_roles: ['admin'] },
78
- };
79
- `);
80
-
81
- const setting = ir.fields.find((f) => f.name === 'setting');
82
- expect(setting!.editable_by_roles).toEqual(['admin']);
83
- });
84
-
85
- it('should apply visible_when expression', () => {
86
- const ir = compileModel(`
87
- export interface Toggle {
88
- secret: string;
89
- status: 'a' | 'b';
90
- }
91
- export const transitions = {};
92
- export const fieldOptions = {
93
- secret: { visible_when: '$user.role == "admin"' },
94
- };
95
- `);
96
-
97
- const secret = ir.fields.find((f) => f.name === 'secret');
98
- expect(secret!.visible_when).toBe('$user.role == "admin"');
99
- });
100
-
101
- it('should apply editable_when expression', () => {
102
- const ir = compileModel(`
103
- export interface Record {
104
- content: string;
105
- status: 'a' | 'b';
106
- }
107
- export const transitions = {};
108
- export const fieldOptions = {
109
- content: { editable_when: '$user.id == $instance.ownerId' },
110
- };
111
- `);
112
-
113
- const content = ir.fields.find((f) => f.name === 'content');
114
- expect(content!.editable_when).toBe('$user.id == $instance.ownerId');
115
- });
116
-
117
- it('should apply computed field expression', () => {
118
- const ir = compileModel(`
119
- export interface Order {
120
- total: number;
121
- status: 'a' | 'b';
122
- }
123
- export const transitions = {};
124
- export const fieldOptions = {
125
- total: { computed: 'fields.quantity * fields.unit_price', computed_deps: ['quantity', 'unit_price'] },
126
- };
127
- `);
128
-
129
- const total = ir.fields.find((f) => f.name === 'total');
130
- expect(total!.computed).toBe('fields.quantity * fields.unit_price');
131
- expect(total!.computed_deps).toEqual(['quantity', 'unit_price']);
132
- });
133
-
134
- it('should apply state home', () => {
135
- const ir = compileModel(`
136
- export interface Chat {
137
- draft: string;
138
- status: 'a' | 'b';
139
- }
140
- export const transitions = {};
141
- export const fieldOptions = {
142
- draft: { scope: 'route', persistence: 'local', sync: 'none' },
143
- };
144
- `);
145
-
146
- const draft = ir.fields.find((f) => f.name === 'draft');
147
- expect(draft!.state_home).toEqual({
148
- scope: 'route',
149
- persistence: 'local',
150
- sync: 'none',
151
- });
152
- });
153
-
154
- it('should apply validation rules', () => {
155
- const ir = compileModel(`
156
- export interface Form {
157
- email: string;
158
- status: 'a' | 'b';
159
- }
160
- export const transitions = {};
161
- export const fieldOptions = {
162
- email: {
163
- validation: {
164
- minLength: 5,
165
- maxLength: 255,
166
- rules: [{ expression: 'contains(email, "@")', message: 'Must be valid email', severity: 'error' }],
167
- },
168
- },
169
- };
170
- `);
171
-
172
- const email = ir.fields.find((f) => f.name === 'email');
173
- expect(email!.validation?.minLength).toBe(5);
174
- expect(email!.validation?.maxLength).toBe(255);
175
- expect(email!.validation?.rules).toHaveLength(1);
176
- expect(email!.validation?.rules![0].expression).toBe('contains(email, "@")');
177
- expect(email!.validation?.rules![0].message).toBe('Must be valid email');
178
- expect(email!.validation?.rules![0].severity).toBe('error');
179
- });
180
-
181
- it('should merge validation with existing options', () => {
182
- const ir = compileModel(`
183
- export interface Selector {
184
- priority: 'low' | 'medium' | 'high';
185
- status: 'a' | 'b';
186
- }
187
- export const transitions = {};
188
- export const fieldOptions = {
189
- priority: {
190
- validation: { min: 0, max: 10 },
191
- },
192
- };
193
- `);
194
-
195
- const priority = ir.fields.find((f) => f.name === 'priority');
196
- // Should keep existing options from union type AND add min/max
197
- expect(priority!.validation?.options).toEqual(['low', 'medium', 'high']);
198
- expect(priority!.validation?.min).toBe(0);
199
- expect(priority!.validation?.max).toBe(10);
200
- });
201
-
202
- it('should combine multiple field options', () => {
203
- const ir = compileModel(`
204
- export interface Full {
205
- data: string;
206
- status: 'a' | 'b';
207
- }
208
- export const transitions = {};
209
- export const fieldOptions = {
210
- data: {
211
- scope: 'instance',
212
- persistence: 'durable',
213
- sync: 'tenant',
214
- computed: 'upper(data)',
215
- computed_deps: ['data'],
216
- visible_in_states: ['a'],
217
- editable_in_states: ['a'],
218
- visible_to_roles: ['admin'],
219
- editable_by_roles: ['admin'],
220
- visible_when: '$state == "a"',
221
- editable_when: '$user.role == "admin"',
222
- },
223
- };
224
- `);
225
-
226
- const data = ir.fields.find((f) => f.name === 'data');
227
- expect(data!.state_home).toBeDefined();
228
- expect(data!.computed).toBe('upper(data)');
229
- expect(data!.computed_deps).toEqual(['data']);
230
- expect(data!.visible_in_states).toEqual(['a']);
231
- expect(data!.editable_in_states).toEqual(['a']);
232
- expect(data!.visible_to_roles).toEqual(['admin']);
233
- expect(data!.editable_by_roles).toEqual(['admin']);
234
- expect(data!.visible_when).toBe('$state == "a"');
235
- expect(data!.editable_when).toBe('$user.role == "admin"');
236
- });
237
- });
@@ -1,130 +0,0 @@
1
- /**
2
- * Model Hooks Tests — validates hook extraction (on_enter/on_exit actions, emit events, server actions).
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 Hooks', () => {
20
- it('should extract on_enter hooks with emit event action', () => {
21
- const ir = compileModel(`
22
- export interface Item {
23
- status: 'draft' | 'active';
24
- }
25
- export const transitions = {
26
- activate: { from: 'draft', to: 'active' },
27
- };
28
- export const hooks = {
29
- on_enter_active: { emit: 'item:activated' },
30
- };
31
- `);
32
-
33
- const activeState = ir.states.find((s) => s.name === 'active');
34
- expect(activeState).toBeDefined();
35
- expect(activeState!.on_enter).toHaveLength(1);
36
- expect(activeState!.on_enter[0].type).toBe('emit_event');
37
- expect(activeState!.on_enter[0].config.event).toBe('item:activated');
38
- });
39
-
40
- it('should extract on_enter hooks with server action references', () => {
41
- const ir = compileModel(`
42
- export interface Channel {
43
- status: 'draft' | 'active';
44
- }
45
- export const transitions = {};
46
- export const hooks = {
47
- on_enter_active: { actions: ['notifyMembers', 'indexForSearch'] },
48
- };
49
- `);
50
-
51
- const activeState = ir.states.find((s) => s.name === 'active');
52
- expect(activeState!.on_enter).toHaveLength(2);
53
- expect(activeState!.on_enter[0].type).toBe('server_action');
54
- expect(activeState!.on_enter[0].config.handler).toBe('notifyMembers');
55
- expect(activeState!.on_enter[1].type).toBe('server_action');
56
- expect(activeState!.on_enter[1].config.handler).toBe('indexForSearch');
57
- });
58
-
59
- it('should extract on_exit hooks', () => {
60
- const ir = compileModel(`
61
- export interface Session {
62
- status: 'active' | 'ended';
63
- }
64
- export const transitions = {};
65
- export const hooks = {
66
- on_exit_active: { actions: ['cleanup'] },
67
- };
68
- `);
69
-
70
- const activeState = ir.states.find((s) => s.name === 'active');
71
- expect(activeState!.on_exit).toHaveLength(1);
72
- expect(activeState!.on_exit[0].type).toBe('server_action');
73
- expect(activeState!.on_exit[0].config.handler).toBe('cleanup');
74
- });
75
-
76
- it('should combine emit and actions in same hook', () => {
77
- const ir = compileModel(`
78
- export interface Flow {
79
- status: 'pending' | 'approved';
80
- }
81
- export const transitions = {};
82
- export const hooks = {
83
- on_enter_approved: { emit: 'flow:approved', actions: ['notifyOwner', 'logApproval'] },
84
- };
85
- `);
86
-
87
- const approvedState = ir.states.find((s) => s.name === 'approved');
88
- expect(approvedState!.on_enter).toHaveLength(3); // 1 emit + 2 server actions
89
- expect(approvedState!.on_enter[0].type).toBe('emit_event');
90
- expect(approvedState!.on_enter[1].type).toBe('server_action');
91
- expect(approvedState!.on_enter[2].type).toBe('server_action');
92
- });
93
-
94
- it('should handle hooks for states not yet declared', () => {
95
- const ir = compileModel(`
96
- export interface Widget {
97
- status: 'a' | 'b';
98
- }
99
- export const transitions = {};
100
- export const hooks = {
101
- on_enter_c: { emit: 'widget:c_entered' },
102
- };
103
- `);
104
-
105
- const cState = ir.states.find((s) => s.name === 'c');
106
- expect(cState).toBeDefined();
107
- expect(cState!.on_enter).toHaveLength(1);
108
- });
109
-
110
- it('should have unique action IDs across hooks', () => {
111
- const ir = compileModel(`
112
- export interface Multi {
113
- status: 'a' | 'b' | 'c';
114
- }
115
- export const transitions = {};
116
- export const hooks = {
117
- on_enter_a: { actions: ['h1'] },
118
- on_enter_b: { actions: ['h2'] },
119
- on_exit_a: { actions: ['h3'] },
120
- };
121
- `);
122
-
123
- const allActionIds: string[] = [];
124
- for (const state of ir.states) {
125
- for (const action of state.on_enter) allActionIds.push(action.id);
126
- for (const action of state.on_exit) allActionIds.push(action.id);
127
- }
128
- expect(new Set(allActionIds).size).toBe(allActionIds.length);
129
- });
130
- });
@@ -1,268 +0,0 @@
1
- /**
2
- * Model Ref Resolution Tests — validates useQuery(modelRef) and useMutation(modelRef)
3
- * where modelRef is an imported defineModel result.
4
- *
5
- * The compiler should resolve the import path to derive the slug, allowing:
6
- * import ChannelModel from '../models/channel';
7
- * const { data } = useQuery(ChannelModel);
8
- * instead of requiring:
9
- * const { data } = useQuery('channel');
10
- */
11
-
12
- import { describe, it, expect } from 'vitest';
13
- import { transformSync } from '@babel/core';
14
- import babelPlugin from '../babel';
15
- import type { IRWorkflowDefinition } from '@mindmatrix/player-core';
16
-
17
- function compile(code: string, filename = 'test.workflow.tsx'): IRWorkflowDefinition {
18
- const result = transformSync(code, {
19
- filename,
20
- plugins: [[babelPlugin]],
21
- parserOpts: { plugins: ['typescript', 'jsx'], attachComment: true },
22
- });
23
- return (result as any)?.metadata?.mindmatrixIR;
24
- }
25
-
26
- describe('useQuery — model ref resolution', () => {
27
- it('should resolve slug from default model import', () => {
28
- const ir = compile(`
29
- import ChannelModel from '../models/channel';
30
- export function Dashboard() {
31
- const { data: channels } = useQuery(ChannelModel);
32
- return <div />;
33
- }
34
- `);
35
-
36
- const sources = (ir.metadata as any).dataSources;
37
- expect(sources).toHaveLength(1);
38
- expect(sources[0].slug).toBe('channel');
39
- expect(sources[0].type).toBe('workflow');
40
- });
41
-
42
- it('should resolve slug from PascalCase model filename', () => {
43
- const ir = compile(`
44
- import UserProfile from '../models/UserProfile';
45
- export function Dashboard() {
46
- const { data } = useQuery(UserProfile);
47
- return <div />;
48
- }
49
- `);
50
-
51
- const sources = (ir.metadata as any).dataSources;
52
- expect(sources).toHaveLength(1);
53
- expect(sources[0].slug).toBe('user-profile');
54
- });
55
-
56
- it('should resolve slug from kebab-case model filename', () => {
57
- const ir = compile(`
58
- import OrderItem from '../models/order-item';
59
- export function Dashboard() {
60
- const { data } = useQuery(OrderItem);
61
- return <div />;
62
- }
63
- `);
64
-
65
- const sources = (ir.metadata as any).dataSources;
66
- expect(sources).toHaveLength(1);
67
- expect(sources[0].slug).toBe('order-item');
68
- });
69
-
70
- it('should resolve slug stripping file extension', () => {
71
- const ir = compile(`
72
- import ProductModel from '../models/product.ts';
73
- export function Dashboard() {
74
- const { data } = useQuery(ProductModel);
75
- return <div />;
76
- }
77
- `);
78
-
79
- const sources = (ir.metadata as any).dataSources;
80
- expect(sources).toHaveLength(1);
81
- expect(sources[0].slug).toBe('product');
82
- });
83
-
84
- it('should still work with string literal slugs', () => {
85
- const ir = compile(`
86
- import { useQuery } from '@mindmatrix/react';
87
- export function Dashboard() {
88
- const { data } = useQuery('channel');
89
- return <div />;
90
- }
91
- `);
92
-
93
- const sources = (ir.metadata as any).dataSources;
94
- expect(sources).toHaveLength(1);
95
- expect(sources[0].slug).toBe('channel');
96
- });
97
-
98
- it('should resolve model ref with query options', () => {
99
- const ir = compile(`
100
- import ChannelModel from '../models/channel';
101
- export function Dashboard() {
102
- const { data } = useQuery(ChannelModel, { limit: 25, orderBy: 'name' });
103
- return <div />;
104
- }
105
- `);
106
-
107
- const sources = (ir.metadata as any).dataSources;
108
- expect(sources).toHaveLength(1);
109
- expect(sources[0].slug).toBe('channel');
110
- expect(sources[0].pageSize).toBe(25);
111
- expect(sources[0].sort).toBe('name');
112
- });
113
-
114
- it('should resolve named import from model file', () => {
115
- const ir = compile(`
116
- import { Channel } from '../models/channel';
117
- export function Dashboard() {
118
- const { data } = useQuery(Channel);
119
- return <div />;
120
- }
121
- `);
122
-
123
- const sources = (ir.metadata as any).dataSources;
124
- expect(sources).toHaveLength(1);
125
- expect(sources[0].slug).toBe('channel');
126
- });
127
-
128
- it('should handle multiple model imports', () => {
129
- const ir = compile(`
130
- import ChannelModel from '../models/channel';
131
- import MessageModel from '../models/message';
132
- export function Dashboard() {
133
- const { data: channels } = useQuery(ChannelModel);
134
- const { data: messages } = useQuery(MessageModel);
135
- return <div />;
136
- }
137
- `);
138
-
139
- const sources = (ir.metadata as any).dataSources;
140
- expect(sources).toHaveLength(2);
141
- expect(sources[0].slug).toBe('channel');
142
- expect(sources[1].slug).toBe('message');
143
- });
144
-
145
- it('should ignore non-model imports as useQuery arguments', () => {
146
- const ir = compile(`
147
- import { someUtil } from '../utils/helpers';
148
- export function Dashboard() {
149
- const { data } = useQuery('channel');
150
- return <div />;
151
- }
152
- `);
153
-
154
- const sources = (ir.metadata as any).dataSources;
155
- expect(sources).toHaveLength(1);
156
- expect(sources[0].slug).toBe('channel');
157
- });
158
- });
159
-
160
- describe('useMutation — model ref resolution', () => {
161
- it('should resolve slug from default model import', () => {
162
- const ir = compile(`
163
- import ChannelModel from '../models/channel';
164
- export function ChannelForm() {
165
- const mutation = useMutation(ChannelModel);
166
- return <div />;
167
- }
168
- `);
169
-
170
- const targets = (ir.metadata as any).mutationTargets;
171
- expect(targets).toHaveLength(1);
172
- expect(targets[0]).toBe('channel');
173
- });
174
-
175
- it('should resolve slug from PascalCase model filename for mutation', () => {
176
- const ir = compile(`
177
- import UserProfile from '../models/UserProfile';
178
- export function ProfileEditor() {
179
- const mutation = useMutation(UserProfile);
180
- return <div />;
181
- }
182
- `);
183
-
184
- const targets = (ir.metadata as any).mutationTargets;
185
- expect(targets).toHaveLength(1);
186
- expect(targets[0]).toBe('user-profile');
187
- });
188
-
189
- it('should still work with string literal slugs for mutation', () => {
190
- const ir = compile(`
191
- import { useMutation } from '@mindmatrix/react';
192
- export function ChannelForm() {
193
- const mutation = useMutation('channel');
194
- return <div />;
195
- }
196
- `);
197
-
198
- const targets = (ir.metadata as any).mutationTargets;
199
- expect(targets).toHaveLength(1);
200
- expect(targets[0]).toBe('channel');
201
- });
202
-
203
- it('should handle mixed model ref and string literal', () => {
204
- const ir = compile(`
205
- import ChannelModel from '../models/channel';
206
- export function Dashboard() {
207
- const { data } = useQuery(ChannelModel);
208
- const mutation = useMutation('message');
209
- return <div />;
210
- }
211
- `);
212
-
213
- const sources = (ir.metadata as any).dataSources;
214
- const targets = (ir.metadata as any).mutationTargets;
215
- expect(sources).toHaveLength(1);
216
- expect(sources[0].slug).toBe('channel');
217
- expect(targets).toHaveLength(1);
218
- expect(targets[0]).toBe('message');
219
- });
220
- });
221
-
222
- describe('Model import tracking', () => {
223
- it('should only track imports from model paths', () => {
224
- const ir = compile(`
225
- import React from 'react';
226
- import { useQuery } from '@mindmatrix/react';
227
- import ChannelModel from '../models/channel';
228
- import { formatDate } from '../utils/format';
229
- export function Dashboard() {
230
- const { data } = useQuery(ChannelModel);
231
- return <div />;
232
- }
233
- `);
234
-
235
- // Should resolve the model import
236
- const sources = (ir.metadata as any).dataSources;
237
- expect(sources).toHaveLength(1);
238
- expect(sources[0].slug).toBe('channel');
239
- });
240
-
241
- it('should not leak __modelImports into emitted metadata', () => {
242
- const ir = compile(`
243
- import ChannelModel from '../models/channel';
244
- export function Dashboard() {
245
- const { data } = useQuery(ChannelModel);
246
- return <div />;
247
- }
248
- `);
249
-
250
- // Internal tracking keys should be stripped
251
- expect((ir.metadata as any).__modelImports).toBeUndefined();
252
- expect((ir.metadata as any).__modelImportSlugs).toBeUndefined();
253
- });
254
-
255
- it('should track .model suffix imports', () => {
256
- const ir = compile(`
257
- import TaskModel from '../task.model';
258
- export function Dashboard() {
259
- const { data } = useQuery(TaskModel);
260
- return <div />;
261
- }
262
- `);
263
-
264
- const sources = (ir.metadata as any).dataSources;
265
- expect(sources).toHaveLength(1);
266
- expect(sources[0].slug).toBe('task');
267
- });
268
- });