@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.
Files changed (94) hide show
  1. package/README.md +112 -0
  2. package/dist/index.d.mts +1378 -94
  3. package/dist/index.d.ts +1378 -94
  4. package/dist/index.js +1094 -1309
  5. package/dist/index.mjs +1038 -1296
  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
@@ -1,24 +0,0 @@
1
- /**
2
- * defineWorkspace — configuration function for mm.config.ts files.
3
- *
4
- * Declares workspace metadata used by the mmrc CLI (build, deploy, pull).
5
- * This is a compile-time configuration — the function simply returns its
6
- * input for the project compiler to consume.
7
- */
8
-
9
- export interface WorkspaceConfig {
10
- slug: string;
11
- name?: string;
12
- version?: string;
13
- category?: string;
14
- description?: string;
15
- models?: string[];
16
- entries?: string[];
17
- actions?: string[];
18
- pages?: string[];
19
- components?: string[];
20
- }
21
-
22
- export function defineWorkspace(config: WorkspaceConfig): WorkspaceConfig {
23
- return config;
24
- }
@@ -1,153 +0,0 @@
1
- /**
2
- * WorkflowRuntime — Class-based runtime for MindMatrix workflows
3
- *
4
- * Wraps StateMachine + EventBus + ActionDispatcher in a subscribable class
5
- * that can be used outside of React hooks (e.g., in vanilla JS contexts).
6
- */
7
-
8
- import {
9
- StateMachine,
10
- EventBus,
11
- ActionDispatcher,
12
- createEvaluator,
13
- WEB_FAILURE_POLICIES,
14
- } from '@mindmatrix/player-core';
15
- import type {
16
- PlayerWorkflowDefinition,
17
- TransitionResult,
18
- ActionHandler,
19
- ExpressionContext,
20
- PlayerAction,
21
- Evaluator,
22
- } from '@mindmatrix/player-core';
23
-
24
- export interface RuntimeConfig {
25
- definition: PlayerWorkflowDefinition;
26
- initialData?: Record<string, unknown>;
27
- actionHandlers?: Record<string, (config: Record<string, unknown>, ctx: ExpressionContext) => void | Promise<void>>;
28
- debug?: boolean;
29
- }
30
-
31
- export interface RuntimeSnapshot {
32
- currentState: string;
33
- stateData: Record<string, unknown>;
34
- memory: Record<string, unknown>;
35
- status: 'ACTIVE' | 'COMPLETED' | 'CANCELLED';
36
- availableTransitions: string[];
37
- }
38
-
39
- export class WorkflowRuntime {
40
- readonly sm: StateMachine;
41
- readonly eventBus: EventBus;
42
- readonly dispatcher: ActionDispatcher;
43
- readonly evaluator: Evaluator;
44
- private listeners = new Set<(snap: RuntimeSnapshot) => void>();
45
-
46
- constructor(config: RuntimeConfig) {
47
- // 1. Create evaluator with WEB_FAILURE_POLICIES.EVENT_REACTION
48
- this.evaluator = createEvaluator({
49
- functions: [],
50
- failurePolicy: WEB_FAILURE_POLICIES.EVENT_REACTION,
51
- });
52
-
53
- // 2. Create action handlers map (built-in set_field, set_memory + custom)
54
- const actionHandlers = new Map<string, ActionHandler>();
55
-
56
- // Wrap custom ActionHandlerFn (config, ctx) into ActionHandler (action, ctx)
57
- if (config.actionHandlers) {
58
- for (const [type, handler] of Object.entries(config.actionHandlers)) {
59
- actionHandlers.set(type, (action: PlayerAction, ctx: ExpressionContext) =>
60
- handler(action.config, ctx)
61
- );
62
- }
63
- }
64
-
65
- // Built-in set_field handler
66
- let smRef: StateMachine | null = null;
67
- actionHandlers.set('set_field', (action: PlayerAction) => {
68
- if (smRef && typeof action.config.field === 'string') {
69
- smRef.setField(action.config.field, action.config.value);
70
- }
71
- });
72
-
73
- // Built-in set_memory handler
74
- actionHandlers.set('set_memory', (action: PlayerAction) => {
75
- if (smRef && typeof action.config.key === 'string') {
76
- smRef.setMemory(action.config.key, action.config.value);
77
- }
78
- });
79
-
80
- // 3. Create StateMachine(definition, initialData, { evaluator, actionHandlers })
81
- this.sm = new StateMachine(
82
- config.definition,
83
- config.initialData ?? {},
84
- { evaluator: this.evaluator, actionHandlers },
85
- );
86
- smRef = this.sm;
87
-
88
- // 4. Create EventBus + ActionDispatcher
89
- this.eventBus = new EventBus();
90
- this.dispatcher = new ActionDispatcher();
91
-
92
- // 5. Mirror custom handlers to dispatcher
93
- // Register built-in handlers on dispatcher (for on_event action dispatch)
94
- this.dispatcher.register('set_field', (cfg: Record<string, unknown>) => {
95
- if (smRef && typeof cfg.field === 'string') {
96
- smRef.setField(cfg.field, cfg.value);
97
- }
98
- });
99
- this.dispatcher.register('set_memory', (cfg: Record<string, unknown>) => {
100
- if (smRef && typeof cfg.key === 'string') {
101
- smRef.setMemory(cfg.key, cfg.value);
102
- }
103
- });
104
-
105
- // Mirror custom action handlers to dispatcher (dispatcher uses ActionHandlerFn signature)
106
- if (config.actionHandlers) {
107
- for (const [type, handler] of Object.entries(config.actionHandlers)) {
108
- this.dispatcher.register(type, handler);
109
- }
110
- }
111
- }
112
-
113
- getSnapshot(): RuntimeSnapshot {
114
- return {
115
- currentState: this.sm.currentState,
116
- stateData: this.sm.stateData,
117
- memory: this.sm.getSnapshot().memory,
118
- status: this.sm.status as RuntimeSnapshot['status'],
119
- availableTransitions: this.sm.getAvailableTransitions().map(t => t.name),
120
- };
121
- }
122
-
123
- subscribe(listener: (snap: RuntimeSnapshot) => void): () => void {
124
- this.listeners.add(listener);
125
- return () => this.listeners.delete(listener);
126
- }
127
-
128
- async transition(name: string, data?: Record<string, unknown>): Promise<TransitionResult> {
129
- const result = await this.sm.transition(name, data);
130
- this.notify();
131
- return result;
132
- }
133
-
134
- setField(field: string, value: unknown): void {
135
- this.sm.setField(field, value);
136
- this.notify();
137
- }
138
-
139
- setMemory(key: string, value: unknown): void {
140
- this.sm.setMemory(key, value);
141
- }
142
-
143
- publishEvent(topic: string, payload?: Record<string, unknown>): void {
144
- this.eventBus.emit(topic, payload);
145
- }
146
-
147
- private notify(): void {
148
- const snap = this.getSnapshot();
149
- for (const listener of this.listeners) {
150
- listener(snap);
151
- }
152
- }
153
- }
package/src/factories.ts DELETED
@@ -1,425 +0,0 @@
1
- /**
2
- * Transition factories — reusable workflow patterns as ModelDefinition mixins.
3
- *
4
- * Each factory returns a mixin function compatible with ModelBuilder.mixin().
5
- * Factories add states, transitions, fields, and roles as a cohesive unit.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { model, field, state } from '@mindmatrix/react';
10
- * import { approval, escalation, review, crud } from '@mindmatrix/react/factories';
11
- *
12
- * export default model('invoice')
13
- * .field('amount', field.currency().required())
14
- * .state('draft', state.initial())
15
- * .mixin(approval({ approverRole: 'manager', field: 'status' }))
16
- * .mixin(escalation({ timeout: '24h', notifyRole: 'admin' }))
17
- * .build();
18
- * ```
19
- */
20
-
21
- import type {
22
- ModelDefinition,
23
- TransitionDescriptor,
24
- StateDescriptor,
25
- ActionDefinition,
26
- WorkflowFieldDescriptor,
27
- RoleDefinition,
28
- } from './config/defineModel';
29
- import { setField, logEvent, notify } from './actions';
30
-
31
- // =============================================================================
32
- // Helpers
33
- // =============================================================================
34
-
35
- /** Merge fields into a definition without overwriting existing ones. */
36
- function mergeFields(
37
- def: ModelDefinition,
38
- fields: Record<string, WorkflowFieldDescriptor>,
39
- ): void {
40
- for (const [name, desc] of Object.entries(fields)) {
41
- if (!def.fields[name]) {
42
- def.fields[name] = desc;
43
- }
44
- }
45
- }
46
-
47
- /** Merge states into a definition without overwriting existing ones. */
48
- function mergeStates(
49
- def: ModelDefinition,
50
- states: Record<string, StateDescriptor>,
51
- ): void {
52
- for (const [name, desc] of Object.entries(states)) {
53
- if (!def.states[name]) {
54
- def.states[name] = desc;
55
- }
56
- }
57
- }
58
-
59
- /** Merge transitions into a definition without overwriting existing ones. */
60
- function mergeTransitions(
61
- def: ModelDefinition,
62
- transitions: Record<string, TransitionDescriptor>,
63
- ): void {
64
- for (const [name, desc] of Object.entries(transitions)) {
65
- if (!def.transitions[name]) {
66
- def.transitions[name] = desc;
67
- }
68
- }
69
- }
70
-
71
- /** Merge roles into a definition without overwriting existing ones. */
72
- function mergeRoles(
73
- def: ModelDefinition,
74
- roles: Record<string, RoleDefinition>,
75
- ): void {
76
- if (!def.roles) def.roles = {};
77
- for (const [name, desc] of Object.entries(roles)) {
78
- if (!def.roles[name]) {
79
- def.roles[name] = desc;
80
- }
81
- }
82
- }
83
-
84
- // =============================================================================
85
- // Approval Pattern
86
- // =============================================================================
87
-
88
- export interface ApprovalConfig {
89
- /** Role required to approve (default: 'approver'). */
90
- approverRole?: string;
91
- /** Field to track approval status (default: 'approvalStatus'). */
92
- field?: string;
93
- /** State to transition FROM for approval (default: 'pending_approval'). */
94
- fromState?: string;
95
- /** State to transition TO on approval (default: 'approved'). */
96
- approvedState?: string;
97
- /** State to transition TO on rejection (default: 'rejected'). */
98
- rejectedState?: string;
99
- /** Require a reason for rejection. */
100
- requireRejectionReason?: boolean;
101
- }
102
-
103
- /**
104
- * Approval pattern — adds approve/reject transitions with role guards.
105
- *
106
- * Adds: pending_approval state, approved state, rejected state,
107
- * approve transition, reject transition, approvalStatus field.
108
- */
109
- export function approval(config: ApprovalConfig = {}): (def: ModelDefinition) => ModelDefinition {
110
- const {
111
- approverRole = 'approver',
112
- field: statusField = 'approvalStatus',
113
- fromState = 'pending_approval',
114
- approvedState = 'approved',
115
- rejectedState = 'rejected',
116
- requireRejectionReason = false,
117
- } = config;
118
-
119
- return (def) => {
120
- mergeFields(def, {
121
- [statusField]: { type: 'string', default: 'pending', enum: ['pending', 'approved', 'rejected'] },
122
- approvedBy: { type: 'string', default: '' },
123
- approvedAt: { type: 'datetime' },
124
- rejectionReason: { type: 'string', default: '' },
125
- });
126
-
127
- mergeStates(def, {
128
- [fromState]: {},
129
- [approvedState]: {},
130
- [rejectedState]: {},
131
- });
132
-
133
- mergeRoles(def, {
134
- [approverRole]: { description: `Can approve or reject`, permissions: ['approve', 'reject'] },
135
- });
136
-
137
- const approveActions: ActionDefinition[] = [
138
- setField(statusField, '"approved"'),
139
- setField('approvedBy', 'context.actor_id'),
140
- setField('approvedAt', 'NOW()'),
141
- logEvent('approved'),
142
- ];
143
-
144
- const rejectActions: ActionDefinition[] = [
145
- setField(statusField, '"rejected"'),
146
- setField('rejectionReason', 'COALESCE(input.reason, "")'),
147
- logEvent('rejected', { reason: '{{ input.reason }}' }),
148
- ];
149
-
150
- mergeTransitions(def, {
151
- approve: {
152
- from: fromState,
153
- to: approvedState,
154
- roles: [approverRole],
155
- actions: approveActions,
156
- description: 'Approve the request',
157
- },
158
- reject: {
159
- from: fromState,
160
- to: rejectedState,
161
- roles: [approverRole],
162
- actions: rejectActions,
163
- ...(requireRejectionReason ? { requiredFields: ['reason'] } : {}),
164
- description: 'Reject the request',
165
- },
166
- });
167
-
168
- return def;
169
- };
170
- }
171
-
172
- // =============================================================================
173
- // Escalation Pattern
174
- // =============================================================================
175
-
176
- export interface EscalationConfig {
177
- /** Duration before escalation (e.g., '24h', '2d'). */
178
- timeout: string;
179
- /** Role to notify on escalation (default: 'admin'). */
180
- notifyRole?: string;
181
- /** State to escalate FROM (default: 'pending_approval'). */
182
- fromState?: string;
183
- /** State to escalate TO (default: 'escalated'). */
184
- escalatedState?: string;
185
- }
186
-
187
- /**
188
- * Escalation pattern — adds timeout-based escalation with notification.
189
- *
190
- * Adds: escalated state, escalate transition (auto, timed),
191
- * notification to the specified role.
192
- */
193
- export function escalation(config: EscalationConfig): (def: ModelDefinition) => ModelDefinition {
194
- const {
195
- timeout,
196
- notifyRole = 'admin',
197
- fromState = 'pending_approval',
198
- escalatedState = 'escalated',
199
- } = config;
200
-
201
- return (def) => {
202
- mergeFields(def, {
203
- escalatedAt: { type: 'datetime' },
204
- escalationLevel: { type: 'number', default: 0 },
205
- });
206
-
207
- // Add timeout to the source state
208
- if (def.states[fromState]) {
209
- def.states[fromState].timeout = {
210
- duration: timeout,
211
- fallback: { transition: 'escalate' },
212
- };
213
- }
214
-
215
- mergeStates(def, {
216
- [escalatedState]: {
217
- onEnter: [
218
- setField('escalatedAt', 'NOW()'),
219
- setField('escalationLevel', 'state_data.escalationLevel + 1'),
220
- logEvent('escalated'),
221
- notify('escalation', { to: `role:${notifyRole}` }),
222
- ],
223
- },
224
- });
225
-
226
- mergeTransitions(def, {
227
- escalate: {
228
- from: fromState,
229
- to: escalatedState,
230
- auto: true,
231
- description: `Auto-escalate after ${timeout}`,
232
- actions: [logEvent('escalated', { timeout })],
233
- },
234
- });
235
-
236
- return def;
237
- };
238
- }
239
-
240
- // =============================================================================
241
- // Review Pattern
242
- // =============================================================================
243
-
244
- export interface ReviewConfig {
245
- /** Role that submits for review (default: 'author'). */
246
- authorRole?: string;
247
- /** Role that reviews (default: 'reviewer'). */
248
- reviewerRole?: string;
249
- /** State the item starts in (default: 'draft'). */
250
- draftState?: string;
251
- /** State when under review (default: 'in_review'). */
252
- reviewState?: string;
253
- /** State when revisions requested (default: 'revisions_requested'). */
254
- revisionsState?: string;
255
- /** State when approved (default: 'approved'). */
256
- approvedState?: string;
257
- }
258
-
259
- /**
260
- * Review cycle pattern — submit → review → approve/request-revisions → resubmit.
261
- *
262
- * Adds: in_review state, revisions_requested state, approved state,
263
- * submit, approve_review, request_revisions, resubmit transitions.
264
- */
265
- export function review(config: ReviewConfig = {}): (def: ModelDefinition) => ModelDefinition {
266
- const {
267
- authorRole = 'author',
268
- reviewerRole = 'reviewer',
269
- draftState = 'draft',
270
- reviewState = 'in_review',
271
- revisionsState = 'revisions_requested',
272
- approvedState = 'approved',
273
- } = config;
274
-
275
- return (def) => {
276
- mergeFields(def, {
277
- reviewNotes: { type: 'string', default: '' },
278
- reviewedBy: { type: 'string', default: '' },
279
- reviewedAt: { type: 'datetime' },
280
- revisionCount: { type: 'number', default: 0 },
281
- });
282
-
283
- mergeStates(def, {
284
- [reviewState]: {
285
- onEnter: [logEvent('submitted_for_review')],
286
- },
287
- [revisionsState]: {
288
- onEnter: [
289
- setField('revisionCount', 'state_data.revisionCount + 1'),
290
- logEvent('revisions_requested'),
291
- ],
292
- },
293
- [approvedState]: {
294
- onEnter: [
295
- setField('reviewedBy', 'context.actor_id'),
296
- setField('reviewedAt', 'NOW()'),
297
- logEvent('review_approved'),
298
- ],
299
- },
300
- });
301
-
302
- mergeRoles(def, {
303
- [authorRole]: { description: 'Can submit and revise', permissions: ['submit', 'revise'] },
304
- [reviewerRole]: { description: 'Can approve or request revisions', permissions: ['approve', 'request_revisions'] },
305
- });
306
-
307
- mergeTransitions(def, {
308
- submit_for_review: {
309
- from: draftState,
310
- to: reviewState,
311
- roles: [authorRole],
312
- description: 'Submit for review',
313
- },
314
- approve_review: {
315
- from: reviewState,
316
- to: approvedState,
317
- roles: [reviewerRole],
318
- description: 'Approve the submission',
319
- },
320
- request_revisions: {
321
- from: reviewState,
322
- to: revisionsState,
323
- roles: [reviewerRole],
324
- actions: [setField('reviewNotes', 'COALESCE(input.notes, "")')],
325
- description: 'Request revisions',
326
- },
327
- resubmit: {
328
- from: revisionsState,
329
- to: reviewState,
330
- roles: [authorRole],
331
- description: 'Resubmit after revisions',
332
- },
333
- });
334
-
335
- return def;
336
- };
337
- }
338
-
339
- // =============================================================================
340
- // CRUD Pattern
341
- // =============================================================================
342
-
343
- export interface CrudConfig {
344
- /** Use soft delete (archived state) instead of hard delete. Default: true. */
345
- softDelete?: boolean;
346
- /** Owner role name (default: 'owner'). */
347
- ownerRole?: string;
348
- /** Admin role name (default: 'admin'). */
349
- adminRole?: string;
350
- }
351
-
352
- /**
353
- * CRUD lifecycle pattern — draft → active → archived/deleted.
354
- *
355
- * Adds: draft (initial), active, archived/deleted states,
356
- * publish, update, archive/delete, restore transitions.
357
- */
358
- export function crud(config: CrudConfig = {}): (def: ModelDefinition) => ModelDefinition {
359
- const {
360
- softDelete = true,
361
- ownerRole = 'owner',
362
- adminRole = 'admin',
363
- } = config;
364
-
365
- return (def) => {
366
- mergeFields(def, {
367
- createdBy: { type: 'string', default: '' },
368
- createdAt: { type: 'datetime' },
369
- updatedAt: { type: 'datetime' },
370
- });
371
-
372
- mergeStates(def, {
373
- draft: { type: 'initial', onEnter: [setField('createdAt', 'NOW()'), setField('createdBy', 'context.actor_id')] },
374
- active: { onEnter: [setField('updatedAt', 'NOW()')] },
375
- });
376
-
377
- mergeRoles(def, {
378
- [ownerRole]: { description: 'Record owner', permissions: ['read', 'write', 'delete'] },
379
- [adminRole]: { description: 'Administrator', permissions: ['read', 'write', 'delete', 'manage'] },
380
- });
381
-
382
- const transitions: Record<string, TransitionDescriptor> = {
383
- publish: {
384
- from: 'draft',
385
- to: 'active',
386
- description: 'Publish the draft',
387
- actions: [logEvent('published')],
388
- },
389
- update: {
390
- from: 'active',
391
- to: 'active',
392
- description: 'Update the record',
393
- actions: [setField('updatedAt', 'NOW()'), logEvent('updated')],
394
- },
395
- };
396
-
397
- if (softDelete) {
398
- mergeStates(def, { archived: {} });
399
- transitions.archive = {
400
- from: 'active',
401
- to: 'archived',
402
- description: 'Archive the record',
403
- actions: [logEvent('archived')],
404
- };
405
- transitions.restore = {
406
- from: 'archived',
407
- to: 'active',
408
- description: 'Restore from archive',
409
- actions: [logEvent('restored')],
410
- };
411
- } else {
412
- mergeStates(def, { deleted: { type: 'end' } });
413
- transitions.delete_record = {
414
- from: ['draft', 'active'],
415
- to: 'deleted',
416
- roles: [ownerRole, adminRole],
417
- description: 'Permanently delete',
418
- actions: [logEvent('deleted')],
419
- };
420
- }
421
-
422
- mergeTransitions(def, transitions);
423
- return def;
424
- };
425
- }