@mmapp/react 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 (94) hide show
  1. package/README.md +112 -0
  2. package/dist/index.d.mts +27 -2
  3. package/dist/index.d.ts +27 -2
  4. package/dist/index.js +70 -3
  5. package/dist/index.mjs +74 -12
  6. package/package.json +4 -3
  7. package/package.json.backup +0 -41
  8. package/src/Blueprint.ts +0 -216
  9. package/src/__tests__/Blueprint.test.ts +0 -106
  10. package/src/__tests__/action-context.test.ts +0 -166
  11. package/src/__tests__/actionCreators.test.ts +0 -179
  12. package/src/__tests__/builders.test.ts +0 -336
  13. package/src/__tests__/defineBlueprint-composition.test.ts +0 -106
  14. package/src/__tests__/factories.test.ts +0 -229
  15. package/src/__tests__/loader.test.ts +0 -159
  16. package/src/__tests__/logger.test.ts +0 -70
  17. package/src/__tests__/type-inference.test.ts +0 -160
  18. package/src/__tests__/typed-transitions.test.ts +0 -126
  19. package/src/__tests__/useModuleConfig.test.ts +0 -61
  20. package/src/actionCreators.ts +0 -132
  21. package/src/actions.ts +0 -547
  22. package/src/atoms/index.ts +0 -600
  23. package/src/authoring.ts +0 -92
  24. package/src/browser-player.ts +0 -783
  25. package/src/builders.ts +0 -1342
  26. package/src/components/ExperienceWorkflowBridge.tsx +0 -123
  27. package/src/components/PlayerProvider.tsx +0 -43
  28. package/src/components/atoms/index.tsx +0 -269
  29. package/src/components/index.ts +0 -36
  30. package/src/conditions.ts +0 -692
  31. package/src/config/defineBlueprint.ts +0 -329
  32. package/src/config/defineModel.ts +0 -753
  33. package/src/config/defineWorkspace.ts +0 -24
  34. package/src/core/WorkflowRuntime.ts +0 -153
  35. package/src/factories.ts +0 -425
  36. package/src/grammar/index.ts +0 -173
  37. package/src/hooks/index.ts +0 -106
  38. package/src/hooks/useAuth.ts +0 -288
  39. package/src/hooks/useChannel.ts +0 -304
  40. package/src/hooks/useComputed.ts +0 -154
  41. package/src/hooks/useDomainSubscription.ts +0 -110
  42. package/src/hooks/useDuringAction.ts +0 -99
  43. package/src/hooks/useExperienceState.ts +0 -59
  44. package/src/hooks/useExpressionLibrary.ts +0 -129
  45. package/src/hooks/useForm.ts +0 -352
  46. package/src/hooks/useGeolocation.ts +0 -207
  47. package/src/hooks/useMapView.ts +0 -259
  48. package/src/hooks/useMiddleware.ts +0 -291
  49. package/src/hooks/useModel.ts +0 -363
  50. package/src/hooks/useModule.ts +0 -59
  51. package/src/hooks/useModuleConfig.ts +0 -61
  52. package/src/hooks/useMutation.ts +0 -237
  53. package/src/hooks/useNotification.ts +0 -151
  54. package/src/hooks/useOnChange.ts +0 -30
  55. package/src/hooks/useOnEnter.ts +0 -59
  56. package/src/hooks/useOnEvent.ts +0 -37
  57. package/src/hooks/useOnExit.ts +0 -27
  58. package/src/hooks/useOnTransition.ts +0 -30
  59. package/src/hooks/usePackage.ts +0 -128
  60. package/src/hooks/useParams.ts +0 -33
  61. package/src/hooks/usePlayer.ts +0 -308
  62. package/src/hooks/useQuery.ts +0 -184
  63. package/src/hooks/useRealtimeQuery.ts +0 -222
  64. package/src/hooks/useRole.ts +0 -191
  65. package/src/hooks/useRouteParams.ts +0 -100
  66. package/src/hooks/useRouter.ts +0 -347
  67. package/src/hooks/useServerAction.ts +0 -178
  68. package/src/hooks/useServerState.ts +0 -284
  69. package/src/hooks/useToast.ts +0 -164
  70. package/src/hooks/useTransition.ts +0 -39
  71. package/src/hooks/useView.ts +0 -102
  72. package/src/hooks/useWhileIn.ts +0 -48
  73. package/src/hooks/useWorkflow.ts +0 -63
  74. package/src/index.ts +0 -465
  75. package/src/loader/experience-workflow-loader.ts +0 -192
  76. package/src/loader/index.ts +0 -6
  77. package/src/local/LocalEngine.ts +0 -388
  78. package/src/local/LocalEngineAdapter.ts +0 -175
  79. package/src/local/LocalEngineContext.ts +0 -30
  80. package/src/logger.ts +0 -37
  81. package/src/mixins.ts +0 -1160
  82. package/src/providers/RuntimeContext.ts +0 -20
  83. package/src/providers/WorkflowProvider.tsx +0 -28
  84. package/src/routing/instance-key.ts +0 -107
  85. package/src/server/transition-context.ts +0 -172
  86. package/src/testing/index.ts +0 -9
  87. package/src/testing/useBlueprintTestRunner.ts +0 -91
  88. package/src/testing/useGraphAnalysis.ts +0 -18
  89. package/src/testing/useTestRunner.ts +0 -77
  90. package/src/testing.ts +0 -995
  91. package/src/types/workflow-inference.ts +0 -158
  92. package/src/types.ts +0 -114
  93. package/tsconfig.json +0 -27
  94. package/vitest.config.ts +0 -8
package/src/conditions.ts DELETED
@@ -1,692 +0,0 @@
1
- /**
2
- * @mindmatrix/react/conditions — Typed condition helpers for transition guards.
3
- *
4
- * Factory functions that produce `TransitionCondition` objects for use in
5
- * model transition definitions. Instead of writing raw condition objects:
6
- *
7
- * ```typescript
8
- * conditions: [
9
- * { type: 'expression', expression: 'context.actor_id == state_data.senderId' },
10
- * { type: 'role', role: 'admin' },
11
- * { type: 'field', field: 'status', operator: 'eq', value: 'draft' },
12
- * ]
13
- * ```
14
- *
15
- * You can use ergonomic helpers:
16
- *
17
- * ```typescript
18
- * import { isSender, hasRole, fieldEquals, or } from '@mindmatrix/react';
19
- *
20
- * conditions: [
21
- * or(isSender(), hasRole('admin')),
22
- * fieldEquals('status', 'draft'),
23
- * ]
24
- * ```
25
- */
26
-
27
- import type { TransitionCondition } from './config/defineModel';
28
-
29
- // ============================================================================
30
- // INTERNAL HELPERS
31
- // ============================================================================
32
-
33
- /** Normalize a condition argument — strings become expression conditions. */
34
- function normalize(c: TransitionCondition | string): TransitionCondition {
35
- return typeof c === 'string' ? { type: 'expression', expression: c } : c;
36
- }
37
-
38
- // ============================================================================
39
- // EXPRESSION CONDITIONS
40
- // ============================================================================
41
-
42
- /**
43
- * Create an expression-based transition condition.
44
- *
45
- * @param expression - The expression string to evaluate. Must be truthy for the transition to fire.
46
- * @returns A `TransitionCondition` of type `'expression'`.
47
- *
48
- * @example
49
- * ```typescript
50
- * import { expr } from '@mindmatrix/react';
51
- *
52
- * transitions: {
53
- * approve: {
54
- * from: 'review', to: 'approved',
55
- * conditions: [
56
- * expr('state_data.amount <= context.approvalLimit'),
57
- * ],
58
- * },
59
- * }
60
- * ```
61
- */
62
- export function expr(expression: string): TransitionCondition {
63
- return { type: 'expression', expression };
64
- }
65
-
66
- /**
67
- * Alias for {@link expr}. Create an expression-based transition condition.
68
- *
69
- * @param expression - The expression string to evaluate.
70
- * @returns A `TransitionCondition` of type `'expression'`.
71
- *
72
- * @example
73
- * ```typescript
74
- * import { when } from '@mindmatrix/react';
75
- *
76
- * conditions: [when('input.email != null && LEN(input.email) > 0')]
77
- * ```
78
- */
79
- export function when(expression: string): TransitionCondition {
80
- return expr(expression);
81
- }
82
-
83
- // ============================================================================
84
- // ROLE CHECKS
85
- // ============================================================================
86
-
87
- /**
88
- * Require the actor to have a specific role.
89
- *
90
- * @param role - The role name to check (e.g. `'admin'`, `'owner'`).
91
- * @returns A `TransitionCondition` of type `'role'`.
92
- *
93
- * @example
94
- * ```typescript
95
- * import { hasRole } from '@mindmatrix/react';
96
- *
97
- * transitions: {
98
- * delete: {
99
- * from: 'active', to: 'deleted',
100
- * conditions: [hasRole('admin')],
101
- * },
102
- * }
103
- * ```
104
- */
105
- export function hasRole(role: string): TransitionCondition {
106
- return { type: 'role', role };
107
- }
108
-
109
- /**
110
- * Require the actor to have at least one of the specified roles.
111
- *
112
- * Produces an `OR` combinator wrapping individual role conditions.
113
- *
114
- * @param roles - One or more role names.
115
- * @returns A `TransitionCondition` with an `OR` array of role conditions.
116
- *
117
- * @example
118
- * ```typescript
119
- * import { hasAnyRole } from '@mindmatrix/react';
120
- *
121
- * transitions: {
122
- * archive: {
123
- * from: 'active', to: 'archived',
124
- * conditions: [hasAnyRole('admin', 'owner')],
125
- * },
126
- * }
127
- * ```
128
- */
129
- export function hasAnyRole(...roles: string[]): TransitionCondition {
130
- if (roles.length === 1) {
131
- return hasRole(roles[0]);
132
- }
133
- return { OR: roles.map((r) => ({ type: 'role' as const, role: r })) };
134
- }
135
-
136
- // ============================================================================
137
- // ACTOR / OWNERSHIP CHECKS
138
- // ============================================================================
139
-
140
- /**
141
- * Require the actor to be the owner of the workflow instance.
142
- *
143
- * Generates: `context.actor_id == state_data.<ownerField>`
144
- *
145
- * @param ownerField - The field name that stores the owner ID. Defaults to `'ownerId'`.
146
- * @returns A `TransitionCondition` of type `'expression'`.
147
- *
148
- * @example
149
- * ```typescript
150
- * import { isOwner } from '@mindmatrix/react';
151
- *
152
- * // Default: checks state_data.ownerId
153
- * conditions: [isOwner()]
154
- *
155
- * // Custom field: checks state_data.createdBy
156
- * conditions: [isOwner('createdBy')]
157
- * ```
158
- */
159
- export function isOwner(ownerField: string = 'ownerId'): TransitionCondition {
160
- return { type: 'expression', expression: `context.actor_id == state_data.${ownerField}` };
161
- }
162
-
163
- /**
164
- * Require the actor's ID to match a specific field on the instance.
165
- *
166
- * Generates: `context.actor_id == state_data.<actorField>`
167
- *
168
- * @param actorField - The field name to compare against `context.actor_id`.
169
- * @returns A `TransitionCondition` of type `'expression'`.
170
- *
171
- * @example
172
- * ```typescript
173
- * import { isActor } from '@mindmatrix/react';
174
- *
175
- * // Only the sender can edit their message
176
- * conditions: [isActor('senderId')]
177
- * // Equivalent to: { type: 'expression', expression: 'context.actor_id == state_data.senderId' }
178
- * ```
179
- */
180
- export function isActor(actorField: string): TransitionCondition {
181
- return { type: 'expression', expression: `context.actor_id == state_data.${actorField}` };
182
- }
183
-
184
- /**
185
- * Shorthand for `isActor('senderId')`. Require the actor to be the message sender.
186
- *
187
- * Generates: `context.actor_id == state_data.senderId`
188
- *
189
- * @returns A `TransitionCondition` of type `'expression'`.
190
- *
191
- * @example
192
- * ```typescript
193
- * import { isSender, hasRole, or } from '@mindmatrix/react';
194
- *
195
- * // Only the sender or an admin can delete a message
196
- * conditions: [or(isSender(), hasRole('admin'))]
197
- * ```
198
- */
199
- export function isSender(): TransitionCondition {
200
- return isActor('senderId');
201
- }
202
-
203
- /**
204
- * Shorthand for `isActor('createdBy')`. Require the actor to be the record creator.
205
- *
206
- * Generates: `context.actor_id == state_data.createdBy`
207
- *
208
- * @returns A `TransitionCondition` of type `'expression'`.
209
- *
210
- * @example
211
- * ```typescript
212
- * import { isCreator } from '@mindmatrix/react';
213
- *
214
- * conditions: [isCreator()]
215
- * ```
216
- */
217
- export function isCreator(): TransitionCondition {
218
- return isActor('createdBy');
219
- }
220
-
221
- // ============================================================================
222
- // FIELD COMPARISONS
223
- // ============================================================================
224
-
225
- /**
226
- * Require a field to equal a specific value.
227
- *
228
- * @param field - The field name on `state_data`.
229
- * @param value - The value to compare against.
230
- * @returns A `TransitionCondition` of type `'field'` with operator `'eq'`.
231
- *
232
- * @example
233
- * ```typescript
234
- * import { fieldEquals } from '@mindmatrix/react';
235
- *
236
- * conditions: [fieldEquals('status', 'draft')]
237
- * // Equivalent to: { type: 'field', field: 'status', operator: 'eq', value: 'draft' }
238
- * ```
239
- */
240
- export function fieldEquals(field: string, value: unknown): TransitionCondition {
241
- return { type: 'field', field, operator: 'eq', value };
242
- }
243
-
244
- /**
245
- * Require a field to NOT equal a specific value.
246
- *
247
- * @param field - The field name on `state_data`.
248
- * @param value - The value to compare against.
249
- * @returns A `TransitionCondition` of type `'field'` with operator `'ne'`.
250
- *
251
- * @example
252
- * ```typescript
253
- * import { fieldNotEquals } from '@mindmatrix/react';
254
- *
255
- * conditions: [fieldNotEquals('type', 'system')]
256
- * ```
257
- */
258
- export function fieldNotEquals(field: string, value: unknown): TransitionCondition {
259
- return { type: 'field', field, operator: 'ne', value };
260
- }
261
-
262
- /**
263
- * Require a numeric field to be greater than a value.
264
- *
265
- * @param field - The field name on `state_data`.
266
- * @param value - The numeric threshold.
267
- * @returns A `TransitionCondition` of type `'field'` with operator `'gt'`.
268
- *
269
- * @example
270
- * ```typescript
271
- * import { fieldGreaterThan } from '@mindmatrix/react';
272
- *
273
- * conditions: [fieldGreaterThan('amount', 1000)]
274
- * ```
275
- */
276
- export function fieldGreaterThan(field: string, value: number): TransitionCondition {
277
- return { type: 'field', field, operator: 'gt', value };
278
- }
279
-
280
- /**
281
- * Require a numeric field to be less than a value.
282
- *
283
- * @param field - The field name on `state_data`.
284
- * @param value - The numeric threshold.
285
- * @returns A `TransitionCondition` of type `'field'` with operator `'lt'`.
286
- *
287
- * @example
288
- * ```typescript
289
- * import { fieldLessThan } from '@mindmatrix/react';
290
- *
291
- * conditions: [fieldLessThan('retryCount', 3)]
292
- * ```
293
- */
294
- export function fieldLessThan(field: string, value: number): TransitionCondition {
295
- return { type: 'field', field, operator: 'lt', value };
296
- }
297
-
298
- /**
299
- * Require a field's value to be one of the specified values.
300
- *
301
- * @param field - The field name on `state_data`.
302
- * @param values - The allowed values.
303
- * @returns A `TransitionCondition` of type `'field'` with operator `'in'`.
304
- *
305
- * @example
306
- * ```typescript
307
- * import { fieldIn } from '@mindmatrix/react';
308
- *
309
- * conditions: [fieldIn('priority', ['high', 'critical'])]
310
- * ```
311
- */
312
- export function fieldIn(field: string, values: unknown[]): TransitionCondition {
313
- return { type: 'field', field, operator: 'in', value: values };
314
- }
315
-
316
- /**
317
- * Require a field's value to NOT be one of the specified values.
318
- *
319
- * @param field - The field name on `state_data`.
320
- * @param values - The disallowed values.
321
- * @returns A `TransitionCondition` of type `'field'` with operator `'not_in'`.
322
- *
323
- * @example
324
- * ```typescript
325
- * import { fieldNotIn } from '@mindmatrix/react';
326
- *
327
- * conditions: [fieldNotIn('status', ['deleted', 'archived'])]
328
- * ```
329
- */
330
- export function fieldNotIn(field: string, values: unknown[]): TransitionCondition {
331
- return { type: 'field', field, operator: 'not_in', value: values };
332
- }
333
-
334
- /**
335
- * Require a field (typically an array or string) to contain a value.
336
- *
337
- * @param field - The field name on `state_data`.
338
- * @param value - The value to check for containment.
339
- * @returns A `TransitionCondition` of type `'field'` with operator `'contains'`.
340
- *
341
- * @example
342
- * ```typescript
343
- * import { fieldContains } from '@mindmatrix/react';
344
- *
345
- * // Check if the members array contains the actor
346
- * conditions: [fieldContains('members', 'context.actor_id')]
347
- * ```
348
- */
349
- export function fieldContains(field: string, value: unknown): TransitionCondition {
350
- return { type: 'field', field, operator: 'contains', value };
351
- }
352
-
353
- /**
354
- * Require a field to be set (non-null and non-undefined).
355
- *
356
- * @param field - The field name on `state_data`.
357
- * @returns A `TransitionCondition` of type `'field'` with operator `'is_set'`.
358
- *
359
- * @example
360
- * ```typescript
361
- * import { fieldIsSet } from '@mindmatrix/react';
362
- *
363
- * conditions: [fieldIsSet('approvedBy')]
364
- * ```
365
- */
366
- export function fieldIsSet(field: string): TransitionCondition {
367
- return { type: 'field', field, operator: 'is_set' };
368
- }
369
-
370
- /**
371
- * Require a field to be empty (null, undefined, empty string, or empty array).
372
- *
373
- * @param field - The field name on `state_data`.
374
- * @returns A `TransitionCondition` of type `'field'` with operator `'is_empty'`.
375
- *
376
- * @example
377
- * ```typescript
378
- * import { fieldIsEmpty } from '@mindmatrix/react';
379
- *
380
- * conditions: [fieldIsEmpty('errorMessage')]
381
- * ```
382
- */
383
- export function fieldIsEmpty(field: string): TransitionCondition {
384
- return { type: 'field', field, operator: 'is_empty' };
385
- }
386
-
387
- /**
388
- * Require a field to match a regex pattern.
389
- *
390
- * @param field - The field name on `state_data`.
391
- * @param pattern - The regex pattern string.
392
- * @returns A `TransitionCondition` of type `'field'` with operator `'matches'`.
393
- *
394
- * @example
395
- * ```typescript
396
- * import { fieldMatches } from '@mindmatrix/react';
397
- *
398
- * conditions: [fieldMatches('email', '^[^@]+@[^@]+\\.[^@]+$')]
399
- * ```
400
- */
401
- export function fieldMatches(field: string, pattern: string): TransitionCondition {
402
- return { type: 'field', field, operator: 'matches', value: pattern };
403
- }
404
-
405
- // ============================================================================
406
- // INPUT VALIDATION
407
- // ============================================================================
408
-
409
- /**
410
- * Require one or more input fields to be non-null and non-empty.
411
- *
412
- * Returns an AND combinator when multiple fields are specified, or a single
413
- * expression condition for a single field. This mirrors the pattern used in
414
- * real models like the authentication model's login transition.
415
- *
416
- * @param fields - One or more input field names that must be provided.
417
- * @returns A `TransitionCondition` — single expression or AND combinator.
418
- *
419
- * @example
420
- * ```typescript
421
- * import { inputRequired } from '@mindmatrix/react';
422
- *
423
- * transitions: {
424
- * login: {
425
- * from: 'unauthenticated', to: 'authenticating',
426
- * conditions: [inputRequired('email', 'password')],
427
- * // Equivalent to:
428
- * // conditions: [
429
- * // { type: 'expression', expression: 'input.email != null && LEN(input.email) > 0' },
430
- * // { type: 'expression', expression: 'input.password != null && LEN(input.password) > 0' },
431
- * // ]
432
- * },
433
- * }
434
- * ```
435
- */
436
- export function inputRequired(...fields: string[]): TransitionCondition {
437
- const conditions = fields.map((f) => ({
438
- type: 'expression' as const,
439
- expression: `input.${f} != null && LEN(input.${f}) > 0`,
440
- }));
441
- if (conditions.length === 1) {
442
- return conditions[0];
443
- }
444
- return { AND: conditions };
445
- }
446
-
447
- /**
448
- * Require an input field to equal a specific value.
449
- *
450
- * Generates: `input.<field> == <value>` (with proper quoting for strings).
451
- *
452
- * @param field - The input field name.
453
- * @param value - The expected value.
454
- * @returns A `TransitionCondition` of type `'expression'`.
455
- *
456
- * @example
457
- * ```typescript
458
- * import { inputEquals } from '@mindmatrix/react';
459
- *
460
- * conditions: [inputEquals('confirmDelete', true)]
461
- * // Generates: { type: 'expression', expression: 'input.confirmDelete == true' }
462
- *
463
- * conditions: [inputEquals('action', 'approve')]
464
- * // Generates: { type: 'expression', expression: 'input.action == "approve"' }
465
- * ```
466
- */
467
- export function inputEquals(field: string, value: unknown): TransitionCondition {
468
- const formatted = typeof value === 'string' ? `"${value}"` : String(value);
469
- return { type: 'expression', expression: `input.${field} == ${formatted}` };
470
- }
471
-
472
- // ============================================================================
473
- // STATE CHECKS
474
- // ============================================================================
475
-
476
- /**
477
- * Require the workflow instance to be in a specific state.
478
- *
479
- * Generates: `current_state == "<state>"`
480
- *
481
- * @param state - The state name.
482
- * @returns A `TransitionCondition` of type `'expression'`.
483
- *
484
- * @example
485
- * ```typescript
486
- * import { inState } from '@mindmatrix/react';
487
- *
488
- * conditions: [inState('active')]
489
- * ```
490
- */
491
- export function inState(state: string): TransitionCondition {
492
- return { type: 'expression', expression: `current_state == "${state}"` };
493
- }
494
-
495
- /**
496
- * Require the workflow instance to NOT be in a specific state.
497
- *
498
- * Generates: `current_state != "<state>"`
499
- *
500
- * @param state - The state name to exclude.
501
- * @returns A `TransitionCondition` of type `'expression'`.
502
- *
503
- * @example
504
- * ```typescript
505
- * import { notInState } from '@mindmatrix/react';
506
- *
507
- * conditions: [notInState('deleted')]
508
- * ```
509
- */
510
- export function notInState(state: string): TransitionCondition {
511
- return { type: 'expression', expression: `current_state != "${state}"` };
512
- }
513
-
514
- // ============================================================================
515
- // BOOLEAN COMBINATORS
516
- // ============================================================================
517
-
518
- /**
519
- * Logical AND — all sub-conditions must pass.
520
- *
521
- * Accepts `TransitionCondition` objects or expression strings.
522
- *
523
- * @param conditions - Two or more conditions to combine.
524
- * @returns A `TransitionCondition` with an `AND` array.
525
- *
526
- * @example
527
- * ```typescript
528
- * import { and, isOwner, fieldEquals } from '@mindmatrix/react';
529
- *
530
- * conditions: [and(isOwner(), fieldEquals('status', 'draft'))]
531
- * ```
532
- */
533
- export function and(...conditions: (TransitionCondition | string)[]): TransitionCondition {
534
- return { AND: conditions.map(normalize) };
535
- }
536
-
537
- /**
538
- * Logical OR — at least one sub-condition must pass.
539
- *
540
- * Accepts `TransitionCondition` objects or expression strings.
541
- * This is especially useful for "owner or admin" patterns common in chat models.
542
- *
543
- * @param conditions - Two or more conditions to combine.
544
- * @returns A `TransitionCondition` with an `OR` array.
545
- *
546
- * @example
547
- * ```typescript
548
- * import { or, isSender, hasRole } from '@mindmatrix/react';
549
- *
550
- * // Only the sender or an admin can delete — mirrors the chat-message delete pattern
551
- * transitions: {
552
- * delete: {
553
- * from: ['sent', 'delivered', 'read', 'edited'],
554
- * to: 'deleted',
555
- * conditions: [or(isSender(), hasRole('admin'))],
556
- * },
557
- * }
558
- * ```
559
- */
560
- export function or(...conditions: (TransitionCondition | string)[]): TransitionCondition {
561
- return { OR: conditions.map(normalize) };
562
- }
563
-
564
- /**
565
- * Logical NOT — negate a condition.
566
- *
567
- * Wraps the condition in an expression using `NOT(...)` if it is expression-based,
568
- * or creates a negated expression from the condition's semantic meaning.
569
- *
570
- * @param condition - The condition to negate (object or expression string).
571
- * @returns A `TransitionCondition` of type `'expression'` with the negated expression.
572
- *
573
- * @example
574
- * ```typescript
575
- * import { not, hasRole, fieldEquals } from '@mindmatrix/react';
576
- *
577
- * // Deny admins
578
- * conditions: [not(hasRole('admin'))]
579
- *
580
- * // Negate an expression
581
- * conditions: [not('state_data.locked == true')]
582
- * ```
583
- */
584
- export function not(condition: TransitionCondition | string): TransitionCondition {
585
- const c = normalize(condition);
586
- if (c.type === 'expression' && c.expression) {
587
- return { type: 'expression', expression: `NOT(${c.expression})` };
588
- }
589
- if (c.type === 'role' && c.role) {
590
- return { type: 'expression', expression: `NOT(context.roles CONTAINS "${c.role}")` };
591
- }
592
- if (c.type === 'field' && c.field) {
593
- // Invert field operators
594
- const invertedOps: Record<string, string> = {
595
- eq: 'ne',
596
- ne: 'eq',
597
- gt: 'lte',
598
- gte: 'lt',
599
- lt: 'gte',
600
- lte: 'gt',
601
- in: 'not_in',
602
- not_in: 'in',
603
- is_set: 'is_empty',
604
- is_empty: 'is_set',
605
- };
606
- const op = c.operator ?? 'eq';
607
- const inverted = invertedOps[op];
608
- if (inverted) {
609
- return { type: 'field', field: c.field, operator: inverted, value: c.value };
610
- }
611
- // Fallback for operators without a clean inverse (contains, matches)
612
- return { type: 'expression', expression: `NOT(state_data.${c.field} ${op.toUpperCase()} ${JSON.stringify(c.value)})` };
613
- }
614
- // Fallback: wrap the whole thing as a negated expression
615
- return { type: 'expression', expression: `NOT(true)` };
616
- }
617
-
618
- // ============================================================================
619
- // CROSS-MODEL REFERENCE CHECKS
620
- // ============================================================================
621
-
622
- /**
623
- * Check a role on a referenced workflow instance (cross-model lookup).
624
- *
625
- * Uses the `$ref()` expression function to look up a related workflow instance
626
- * and check a role on it. This mirrors patterns from the chat-channel model
627
- * where membership roles are checked via `$ref("chat-membership", ...)`.
628
- *
629
- * Generates: `$ref("<slug>", "<lookupField>", <lookupValue>).role == "<role>"`
630
- *
631
- * @param slug - The workflow definition slug to look up.
632
- * @param lookupField - The field to match on in the referenced model.
633
- * @param lookupValue - The expression for the lookup value (e.g. `'state_data.id'`).
634
- * @param role - The role to check for.
635
- * @returns A `TransitionCondition` of type `'expression'`.
636
- *
637
- * @example
638
- * ```typescript
639
- * import { refHasRole } from '@mindmatrix/react';
640
- *
641
- * // Only channel owners can delete — mirrors chat-channel delete pattern
642
- * transitions: {
643
- * delete: {
644
- * from: 'archived', to: 'deleted',
645
- * conditions: [refHasRole('chat-membership', 'channel', 'state_data.id', 'owner')],
646
- * // Generates: $ref("chat-membership", "channel", state_data.id).role == "owner"
647
- * },
648
- * }
649
- * ```
650
- */
651
- export function refHasRole(slug: string, lookupField: string, lookupValue: string, role: string): TransitionCondition {
652
- return {
653
- type: 'expression',
654
- expression: `$ref("${slug}", "${lookupField}", ${lookupValue}).role == "${role}"`,
655
- };
656
- }
657
-
658
- /**
659
- * Check if a referenced workflow instance has any of the specified roles.
660
- *
661
- * Uses the `$ref()` expression function with an `IN` operator. This mirrors
662
- * the chat-channel archive/restore pattern.
663
- *
664
- * Generates: `$ref("<slug>", "<lookupField>", <lookupValue>).role IN [<roles>]`
665
- *
666
- * @param slug - The workflow definition slug to look up.
667
- * @param lookupField - The field to match on in the referenced model.
668
- * @param lookupValue - The expression for the lookup value.
669
- * @param roles - The roles to check for (at least one must match).
670
- * @returns A `TransitionCondition` of type `'expression'`.
671
- *
672
- * @example
673
- * ```typescript
674
- * import { refHasAnyRole } from '@mindmatrix/react';
675
- *
676
- * // Admins or owners can archive — mirrors chat-channel archive pattern
677
- * transitions: {
678
- * archive: {
679
- * from: ['active', 'muted'], to: 'archived',
680
- * conditions: [refHasAnyRole('chat-membership', 'channel', 'state_data.id', ['admin', 'owner'])],
681
- * // Generates: $ref("chat-membership", "channel", state_data.id).role IN ["admin", "owner"]
682
- * },
683
- * }
684
- * ```
685
- */
686
- export function refHasAnyRole(slug: string, lookupField: string, lookupValue: string, roles: string[]): TransitionCondition {
687
- const roleList = roles.map((r) => `"${r}"`).join(', ');
688
- return {
689
- type: 'expression',
690
- expression: `$ref("${slug}", "${lookupField}", ${lookupValue}).role IN [${roleList}]`,
691
- };
692
- }