@mmapp/react 0.1.0-alpha.1 → 0.1.0-alpha.4
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.
- package/README.md +112 -0
- package/dist/index.d.mts +1378 -94
- package/dist/index.d.ts +1378 -94
- package/dist/index.js +1094 -1309
- package/dist/index.mjs +1038 -1296
- package/package.json +4 -3
- package/package.json.backup +0 -41
- package/src/Blueprint.ts +0 -216
- package/src/__tests__/Blueprint.test.ts +0 -106
- package/src/__tests__/action-context.test.ts +0 -166
- package/src/__tests__/actionCreators.test.ts +0 -179
- package/src/__tests__/builders.test.ts +0 -336
- package/src/__tests__/defineBlueprint-composition.test.ts +0 -106
- package/src/__tests__/factories.test.ts +0 -229
- package/src/__tests__/loader.test.ts +0 -159
- package/src/__tests__/logger.test.ts +0 -70
- package/src/__tests__/type-inference.test.ts +0 -160
- package/src/__tests__/typed-transitions.test.ts +0 -126
- package/src/__tests__/useModuleConfig.test.ts +0 -61
- package/src/actionCreators.ts +0 -132
- package/src/actions.ts +0 -547
- package/src/atoms/index.ts +0 -600
- package/src/authoring.ts +0 -92
- package/src/browser-player.ts +0 -783
- package/src/builders.ts +0 -1342
- package/src/components/ExperienceWorkflowBridge.tsx +0 -123
- package/src/components/PlayerProvider.tsx +0 -43
- package/src/components/atoms/index.tsx +0 -269
- package/src/components/index.ts +0 -36
- package/src/conditions.ts +0 -692
- package/src/config/defineBlueprint.ts +0 -329
- package/src/config/defineModel.ts +0 -753
- package/src/config/defineWorkspace.ts +0 -24
- package/src/core/WorkflowRuntime.ts +0 -153
- package/src/factories.ts +0 -425
- package/src/grammar/index.ts +0 -173
- package/src/hooks/index.ts +0 -106
- package/src/hooks/useAuth.ts +0 -288
- package/src/hooks/useChannel.ts +0 -304
- package/src/hooks/useComputed.ts +0 -154
- package/src/hooks/useDomainSubscription.ts +0 -110
- package/src/hooks/useDuringAction.ts +0 -99
- package/src/hooks/useExperienceState.ts +0 -59
- package/src/hooks/useExpressionLibrary.ts +0 -129
- package/src/hooks/useForm.ts +0 -352
- package/src/hooks/useGeolocation.ts +0 -207
- package/src/hooks/useMapView.ts +0 -259
- package/src/hooks/useMiddleware.ts +0 -291
- package/src/hooks/useModel.ts +0 -363
- package/src/hooks/useModule.ts +0 -59
- package/src/hooks/useModuleConfig.ts +0 -61
- package/src/hooks/useMutation.ts +0 -237
- package/src/hooks/useNotification.ts +0 -151
- package/src/hooks/useOnChange.ts +0 -30
- package/src/hooks/useOnEnter.ts +0 -59
- package/src/hooks/useOnEvent.ts +0 -37
- package/src/hooks/useOnExit.ts +0 -27
- package/src/hooks/useOnTransition.ts +0 -30
- package/src/hooks/usePackage.ts +0 -128
- package/src/hooks/useParams.ts +0 -33
- package/src/hooks/usePlayer.ts +0 -308
- package/src/hooks/useQuery.ts +0 -184
- package/src/hooks/useRealtimeQuery.ts +0 -222
- package/src/hooks/useRole.ts +0 -191
- package/src/hooks/useRouteParams.ts +0 -100
- package/src/hooks/useRouter.ts +0 -347
- package/src/hooks/useServerAction.ts +0 -178
- package/src/hooks/useServerState.ts +0 -284
- package/src/hooks/useToast.ts +0 -164
- package/src/hooks/useTransition.ts +0 -39
- package/src/hooks/useView.ts +0 -102
- package/src/hooks/useWhileIn.ts +0 -48
- package/src/hooks/useWorkflow.ts +0 -63
- package/src/index.ts +0 -465
- package/src/loader/experience-workflow-loader.ts +0 -192
- package/src/loader/index.ts +0 -6
- package/src/local/LocalEngine.ts +0 -388
- package/src/local/LocalEngineAdapter.ts +0 -175
- package/src/local/LocalEngineContext.ts +0 -30
- package/src/logger.ts +0 -37
- package/src/mixins.ts +0 -1160
- package/src/providers/RuntimeContext.ts +0 -20
- package/src/providers/WorkflowProvider.tsx +0 -28
- package/src/routing/instance-key.ts +0 -107
- package/src/server/transition-context.ts +0 -172
- package/src/testing/index.ts +0 -9
- package/src/testing/useBlueprintTestRunner.ts +0 -91
- package/src/testing/useGraphAnalysis.ts +0 -18
- package/src/testing/useTestRunner.ts +0 -77
- package/src/testing.ts +0 -995
- package/src/types/workflow-inference.ts +0 -158
- package/src/types.ts +0 -114
- package/tsconfig.json +0 -27
- 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
|
-
}
|