@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
@@ -1,284 +0,0 @@
1
- /**
2
- * useServerState — subscribe to server-side workflow state via SSE.
3
- *
4
- * Connects to the SSE endpoint at /api/v1/workflow/events and filters
5
- * events for a specific instance ID. Returns the current server state
6
- * with real-time updates.
7
- *
8
- * Usage in .workflow.tsx:
9
- * const { state, stateData, loading, error } = useServerState(instanceId);
10
- *
11
- * <Show when={state === 'submitted'}>
12
- * <Text>Order has been submitted</Text>
13
- * </Show>
14
- *
15
- * The compiler extracts this as a server state subscription in the IR.
16
- * At runtime, the hook opens an SSE connection and filters by instance.
17
- */
18
-
19
- import { useState, useEffect, useRef, useCallback } from 'react';
20
-
21
- // =============================================================================
22
- // Types
23
- // =============================================================================
24
-
25
- export interface ServerStateOptions {
26
- /** Disable the SSE connection (manual mode). */
27
- enabled?: boolean;
28
- /** Called when server state changes. */
29
- onStateChange?: (state: ServerStateSnapshot) => void;
30
- /** Called on connection error. */
31
- onError?: (error: Error) => void;
32
- }
33
-
34
- export interface ServerStateSnapshot {
35
- /** Current workflow state name. */
36
- currentState: string;
37
- /** Current state data (fields). */
38
- stateData: Record<string, unknown>;
39
- /** Instance status (ACTIVE, COMPLETED, etc.). */
40
- status: string;
41
- /** Last transition that occurred. */
42
- lastTransition?: {
43
- name: string;
44
- from: string;
45
- to: string;
46
- timestamp: string;
47
- };
48
- }
49
-
50
- export interface ServerStateResult {
51
- /** Current workflow state name (empty string until first event). */
52
- state: string;
53
- /** Current state data. */
54
- stateData: Record<string, unknown>;
55
- /** Instance status. */
56
- status: string;
57
- /** Whether the initial state is still loading. */
58
- loading: boolean;
59
- /** Connection or parse error. */
60
- error: Error | null;
61
- /** Whether the SSE connection is active. */
62
- connected: boolean;
63
- /** Last transition info. */
64
- lastTransition: ServerStateSnapshot['lastTransition'] | undefined;
65
- /** Force-reconnect the SSE stream. */
66
- reconnect: () => void;
67
- }
68
-
69
- /**
70
- * Resolver interface for fetching initial instance state.
71
- * Falls back to a direct fetch if not configured.
72
- */
73
- export interface ServerStateResolver {
74
- getInstanceState: (instanceId: string) => Promise<ServerStateSnapshot>;
75
- }
76
-
77
- // Global resolver (set by provider)
78
- let _globalServerStateResolver: ServerStateResolver | null = null;
79
-
80
- export function setServerStateResolver(resolver: ServerStateResolver | null): void {
81
- _globalServerStateResolver = resolver;
82
- }
83
-
84
- // =============================================================================
85
- // Internal helpers
86
- // =============================================================================
87
-
88
- /** Get JWT token from localStorage. */
89
- function getToken(): string | null {
90
- return typeof localStorage !== 'undefined'
91
- ? localStorage.getItem('auth_token')
92
- : null;
93
- }
94
-
95
- /** Fetch initial instance state from the API. */
96
- async function fetchInitialState(instanceId: string): Promise<ServerStateSnapshot> {
97
- if (_globalServerStateResolver) {
98
- return _globalServerStateResolver.getInstanceState(instanceId);
99
- }
100
-
101
- const token = getToken();
102
- const res = await fetch(
103
- `/api/v1/workflow/instances/${encodeURIComponent(instanceId)}`,
104
- {
105
- headers: token ? { Authorization: `Bearer ${token}` } : {},
106
- },
107
- );
108
-
109
- if (!res.ok) {
110
- throw new Error(`Failed to fetch instance state: ${res.status}`);
111
- }
112
-
113
- const data = await res.json();
114
- return {
115
- currentState: data.current_state || '',
116
- stateData: data.state_data || {},
117
- status: data.status || 'UNKNOWN',
118
- };
119
- }
120
-
121
- // =============================================================================
122
- // Hook
123
- // =============================================================================
124
-
125
- /**
126
- * Subscribes to real-time server state for a workflow instance via SSE.
127
- *
128
- * @param instanceId - The workflow instance ID to subscribe to.
129
- * @param options - Optional configuration (enabled, callbacks).
130
- * @returns Server state with loading/error/connected status.
131
- */
132
- export function useServerState(
133
- instanceId: string,
134
- options: ServerStateOptions = {},
135
- ): ServerStateResult {
136
- const [state, setState] = useState('');
137
- const [stateData, setStateData] = useState<Record<string, unknown>>({});
138
- const [status, setStatus] = useState('UNKNOWN');
139
- const [loading, setLoading] = useState(true);
140
- const [error, setError] = useState<Error | null>(null);
141
- const [connected, setConnected] = useState(false);
142
- const [lastTransition, setLastTransition] = useState<ServerStateSnapshot['lastTransition']>();
143
- const [reconnectKey, setReconnectKey] = useState(0);
144
-
145
- const optionsRef = useRef(options);
146
- optionsRef.current = options;
147
-
148
- const eventSourceRef = useRef<EventSource | null>(null);
149
-
150
- const reconnect = useCallback(() => {
151
- setReconnectKey((k) => k + 1);
152
- }, []);
153
-
154
- // Fetch initial state
155
- useEffect(() => {
156
- if (options.enabled === false || !instanceId) return;
157
-
158
- let cancelled = false;
159
- setLoading(true);
160
-
161
- fetchInitialState(instanceId)
162
- .then((snapshot) => {
163
- if (cancelled) return;
164
- setState(snapshot.currentState);
165
- setStateData(snapshot.stateData);
166
- setStatus(snapshot.status);
167
- setLoading(false);
168
- })
169
- .catch((err) => {
170
- if (cancelled) return;
171
- setError(err instanceof Error ? err : new Error(String(err)));
172
- setLoading(false);
173
- });
174
-
175
- return () => {
176
- cancelled = true;
177
- };
178
- }, [instanceId, options.enabled, reconnectKey]);
179
-
180
- // SSE subscription
181
- useEffect(() => {
182
- if (options.enabled === false || !instanceId) return;
183
-
184
- const token = getToken();
185
- if (!token) {
186
- setError(new Error('useServerState: No auth token found in localStorage'));
187
- return;
188
- }
189
-
190
- const url = `/api/v1/workflow/events?token=${encodeURIComponent(token)}`;
191
- const es = new EventSource(url);
192
- eventSourceRef.current = es;
193
-
194
- es.addEventListener('connected', () => {
195
- setConnected(true);
196
- setError(null);
197
- });
198
-
199
- // Listen to relevant SSE event types and filter by instance ID
200
- const handleEvent = (eventName: string, e: MessageEvent) => {
201
- try {
202
- const data = JSON.parse(e.data);
203
- // Filter: only process events for our instance
204
- if (data.instance_id !== instanceId) return;
205
-
206
- switch (eventName) {
207
- case 'workflow:transitioned': {
208
- const snapshot: ServerStateSnapshot = {
209
- currentState: data.to_state,
210
- stateData: stateData, // Keep current, will be updated by data_updated
211
- status: 'ACTIVE',
212
- lastTransition: {
213
- name: data.transition_name,
214
- from: data.from_state,
215
- to: data.to_state,
216
- timestamp: data.timestamp,
217
- },
218
- };
219
- setState(data.to_state);
220
- setLastTransition(snapshot.lastTransition);
221
- optionsRef.current.onStateChange?.(snapshot);
222
- break;
223
- }
224
- case 'workflow:data_updated': {
225
- if (data.state_data) {
226
- setStateData((prev) => ({ ...prev, ...data.state_data }));
227
- }
228
- break;
229
- }
230
- case 'workflow:state_changed': {
231
- if (data.current_state) setState(data.current_state);
232
- if (data.status) setStatus(data.status);
233
- break;
234
- }
235
- case 'workflow:instance_deleted': {
236
- setStatus('DELETED');
237
- break;
238
- }
239
- }
240
- } catch {
241
- // Ignore parse errors on individual events
242
- }
243
- };
244
-
245
- const eventTypes = [
246
- 'workflow:transitioned',
247
- 'workflow:data_updated',
248
- 'workflow:state_changed',
249
- 'workflow:instance_created',
250
- 'workflow:instance_deleted',
251
- 'workflow:blocked',
252
- 'workflow:unblocked',
253
- ];
254
-
255
- for (const eventType of eventTypes) {
256
- es.addEventListener(eventType, (e) => handleEvent(eventType, e as MessageEvent));
257
- }
258
-
259
- es.onerror = () => {
260
- setConnected(false);
261
- const err = new Error('SSE connection lost');
262
- setError(err);
263
- optionsRef.current.onError?.(err);
264
- };
265
-
266
- return () => {
267
- es.close();
268
- eventSourceRef.current = null;
269
- setConnected(false);
270
- };
271
- }, [instanceId, options.enabled, reconnectKey]); // eslint-disable-line react-hooks/exhaustive-deps
272
-
273
- const handle: ServerStateResult = {
274
- state,
275
- stateData,
276
- status,
277
- loading,
278
- error,
279
- connected,
280
- lastTransition,
281
- reconnect,
282
- };
283
- return handle;
284
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * useToast — In-app toast notification hook.
3
- *
4
- * Manages a queue of toast messages with auto-dismiss, stacking,
5
- * and manual dismiss. Framework-agnostic — provides state only,
6
- * the actual rendering is done by the host application's toast component.
7
- *
8
- * Usage in .workflow.tsx:
9
- * const { toast, toasts, dismiss } = useToast();
10
- *
11
- * // Show a success toast
12
- * toast({ title: 'Ride booked!', variant: 'success' });
13
- *
14
- * // Show an error toast
15
- * toast({ title: 'Payment failed', description: 'Please try again', variant: 'error' });
16
- *
17
- * // Render toasts (in your layout)
18
- * {toasts.map(t => <ToastComponent key={t.id} {...t} onDismiss={() => dismiss(t.id)} />)}
19
- */
20
-
21
- import { useState, useCallback, useRef, useMemo } from 'react';
22
-
23
- // =============================================================================
24
- // Types
25
- // =============================================================================
26
-
27
- /** Toast variant for styling. */
28
- export type ToastVariant = 'default' | 'success' | 'error' | 'warning' | 'info';
29
-
30
- /** Toast configuration for creating a new toast. */
31
- export interface ToastConfig {
32
- /** Toast title (required). */
33
- title: string;
34
- /** Optional description/body. */
35
- description?: string;
36
- /** Visual variant (default: 'default'). */
37
- variant?: ToastVariant;
38
- /** Auto-dismiss after ms (default: 5000, 0 = no auto-dismiss). */
39
- duration?: number;
40
- /** Action button configuration. */
41
- action?: {
42
- label: string;
43
- onClick: () => void;
44
- };
45
- }
46
-
47
- /** A toast instance in the queue. */
48
- export interface ToastInstance extends ToastConfig {
49
- /** Unique toast ID. */
50
- id: string;
51
- /** When the toast was created. */
52
- createdAt: number;
53
- }
54
-
55
- /** Toast handle returned by useToast. */
56
- export interface ToastHandle {
57
- /** Show a new toast. Returns the toast ID. */
58
- toast: (config: ToastConfig) => string;
59
- /** All active toasts. */
60
- toasts: ToastInstance[];
61
- /** Dismiss a specific toast by ID. */
62
- dismiss: (id: string) => void;
63
- /** Dismiss all toasts. */
64
- dismissAll: () => void;
65
- }
66
-
67
- // =============================================================================
68
- // Global toast store (shared across all useToast instances)
69
- // =============================================================================
70
-
71
- type ToastListener = (toasts: ToastInstance[]) => void;
72
-
73
- let _nextId = 0;
74
- let _toasts: ToastInstance[] = [];
75
- const _listeners = new Set<ToastListener>();
76
- const _timers = new Map<string, ReturnType<typeof setTimeout>>();
77
-
78
- function notifyListeners(): void {
79
- for (const listener of _listeners) {
80
- listener([..._toasts]);
81
- }
82
- }
83
-
84
- function addToast(config: ToastConfig): string {
85
- const id = `toast-${++_nextId}`;
86
- const instance: ToastInstance = {
87
- ...config,
88
- id,
89
- createdAt: Date.now(),
90
- };
91
- _toasts = [..._toasts, instance];
92
-
93
- const duration = config.duration ?? 5000;
94
- if (duration > 0) {
95
- const timer = setTimeout(() => {
96
- removeToast(id);
97
- }, duration);
98
- _timers.set(id, timer);
99
- }
100
-
101
- notifyListeners();
102
- return id;
103
- }
104
-
105
- function removeToast(id: string): void {
106
- const timer = _timers.get(id);
107
- if (timer) {
108
- clearTimeout(timer);
109
- _timers.delete(id);
110
- }
111
- _toasts = _toasts.filter((t) => t.id !== id);
112
- notifyListeners();
113
- }
114
-
115
- function clearAllToasts(): void {
116
- for (const timer of _timers.values()) {
117
- clearTimeout(timer);
118
- }
119
- _timers.clear();
120
- _toasts = [];
121
- notifyListeners();
122
- }
123
-
124
- // =============================================================================
125
- // Hook
126
- // =============================================================================
127
-
128
- /**
129
- * In-app toast notification manager.
130
- *
131
- * @returns Toast handle with toast(), toasts, dismiss, and dismissAll.
132
- */
133
- export function useToast(): ToastHandle {
134
- const [toasts, setToasts] = useState<ToastInstance[]>(() => [..._toasts]);
135
- const listenerRef = useRef<ToastListener | null>(null);
136
-
137
- // Subscribe to global toast store
138
- if (!listenerRef.current) {
139
- const listener: ToastListener = (newToasts) => setToasts(newToasts);
140
- listenerRef.current = listener;
141
- _listeners.add(listener);
142
- }
143
-
144
- // Note: we intentionally don't clean up the listener in useEffect
145
- // because the component will re-subscribe anyway. Using a ref-based
146
- // approach avoids the stale closure problem with useEffect + useState.
147
-
148
- const toast = useCallback((config: ToastConfig): string => {
149
- return addToast(config);
150
- }, []);
151
-
152
- const dismiss = useCallback((id: string): void => {
153
- removeToast(id);
154
- }, []);
155
-
156
- const dismissAll = useCallback((): void => {
157
- clearAllToasts();
158
- }, []);
159
-
160
- return useMemo(
161
- (): ToastHandle => ({ toast, toasts, dismiss, dismissAll }),
162
- [toast, toasts, dismiss, dismissAll],
163
- );
164
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * useTransition — Helper hook for firing transitions
3
- *
4
- * Returns a handle to fire a specific transition with pending state.
5
- * Must be used within a RuntimeContext (e.g., WorkflowProvider).
6
- */
7
-
8
- import { useCallback, useState } from 'react';
9
- import { useRuntimeContext } from '../providers/RuntimeContext';
10
- import type { TransitionResult } from '@mindmatrix/player-core';
11
-
12
- export interface TransitionHandle {
13
- fire: (data?: Record<string, unknown>) => Promise<TransitionResult>;
14
- available: boolean;
15
- pending: boolean;
16
- }
17
-
18
- /**
19
- * @param transitionName - Name of the transition.
20
- * @param _config - Optional authoring config (from/to/requiredFields).
21
- * Used by the Babel plugin at compile time; ignored at runtime.
22
- */
23
- export function useTransition(transitionName: string, _config?: { from?: string; to?: string; requiredFields?: string[] }): TransitionHandle {
24
- const runtime = useRuntimeContext();
25
- const [pending, setPending] = useState(false);
26
- const available = runtime.sm.getAvailableTransitions().some(t => t.name === transitionName);
27
-
28
- const fire = useCallback(async (data?: Record<string, unknown>) => {
29
- setPending(true);
30
- try {
31
- return await runtime.transition(transitionName, data);
32
- } finally {
33
- setPending(false);
34
- }
35
- }, [runtime, transitionName]);
36
-
37
- const handle: TransitionHandle = { fire, available, pending };
38
- return handle;
39
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * useView — Load a view definition by ID or slug
3
- *
4
- * Fetches a view definition (category='view') from the workflow API.
5
- * Supports B1 views-as-definitions — views are standalone workflow
6
- * definitions with category='view'.
7
- *
8
- * Usage in .workflow.tsx:
9
- * const { view, loading, error } = useView('my-custom-view');
10
- */
11
-
12
- import { useState, useEffect, useCallback, useRef } from 'react';
13
-
14
- // =============================================================================
15
- // Types
16
- // =============================================================================
17
-
18
- export interface ViewDefinitionResult {
19
- /** The loaded view definition (component tree). */
20
- view: ViewRecord | null;
21
- /** Whether the view is currently loading. */
22
- loading: boolean;
23
- /** Error from the last fetch attempt. */
24
- error: Error | null;
25
- /** Manually trigger a refetch. */
26
- refetch: () => Promise<void>;
27
- }
28
-
29
- export interface ViewRecord {
30
- id: string;
31
- slug: string;
32
- name: string;
33
- category: string;
34
- view?: Record<string, unknown>;
35
- metadata?: Record<string, unknown>;
36
- }
37
-
38
- /**
39
- * View resolver interface — the runtime must provide an implementation.
40
- */
41
- export interface ViewResolver {
42
- getView: (idOrSlug: string) => Promise<ViewRecord | null>;
43
- }
44
-
45
- // Global resolver (set by provider)
46
- let _globalViewResolver: ViewResolver | null = null;
47
-
48
- export function setViewResolver(resolver: ViewResolver | null): void {
49
- _globalViewResolver = resolver;
50
- }
51
-
52
- // =============================================================================
53
- // Hook
54
- // =============================================================================
55
-
56
- /**
57
- * Fetches a view definition by ID or slug.
58
- *
59
- * @param idOrSlug - The view definition ID or slug to load.
60
- * @returns View result with definition, loading state, error, and refetch.
61
- */
62
- export function useView(idOrSlug: string | null): ViewDefinitionResult {
63
- const [view, setView] = useState<ViewRecord | null>(null);
64
- const [loading, setLoading] = useState(!!idOrSlug);
65
- const [error, setError] = useState<Error | null>(null);
66
- const idRef = useRef(idOrSlug);
67
- idRef.current = idOrSlug;
68
-
69
- const fetchView = useCallback(async () => {
70
- const id = idRef.current;
71
- if (!id) {
72
- setView(null);
73
- setLoading(false);
74
- return;
75
- }
76
-
77
- const resolver = _globalViewResolver;
78
- if (!resolver) {
79
- setError(new Error('useView: No view resolver configured. Wrap your app in a WorkflowProvider.'));
80
- setLoading(false);
81
- return;
82
- }
83
-
84
- try {
85
- setLoading(true);
86
- setError(null);
87
- const result = await resolver.getView(id);
88
- setView(result);
89
- } catch (err) {
90
- setError(err instanceof Error ? err : new Error(String(err)));
91
- } finally {
92
- setLoading(false);
93
- }
94
- }, []);
95
-
96
- useEffect(() => {
97
- fetchView();
98
- }, [idOrSlug, fetchView]);
99
-
100
- const result: ViewDefinitionResult = { view, loading, error, refetch: fetchView };
101
- return result;
102
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * useWhileIn — Run effect on interval while in state
3
- *
4
- * Starts an interval when entering the specified state.
5
- * Stops the interval when exiting the state.
6
- */
7
-
8
- import { useEffect, useRef } from 'react';
9
- import { useRuntimeContext } from '../providers/RuntimeContext';
10
-
11
- export function useWhileIn(
12
- stateName: string,
13
- intervalMs: number,
14
- effect: () => void | Promise<void>
15
- ): void {
16
- const runtime = useRuntimeContext();
17
- const effectRef = useRef(effect);
18
- effectRef.current = effect;
19
-
20
- useEffect(() => {
21
- let timer: ReturnType<typeof setInterval> | null = null;
22
-
23
- function startInterval() {
24
- if (timer) clearInterval(timer);
25
- timer = setInterval(() => effectRef.current(), intervalMs);
26
- }
27
-
28
- function stopInterval() {
29
- if (timer) {
30
- clearInterval(timer);
31
- timer = null;
32
- }
33
- }
34
-
35
- // If already in target state, start immediately
36
- if (runtime.sm.currentState === stateName) startInterval();
37
-
38
- const unsub = runtime.sm.on((event) => {
39
- if (event.type === 'state_enter' && event.to_state === stateName) startInterval();
40
- if (event.type === 'state_exit' && event.from_state === stateName) stopInterval();
41
- });
42
-
43
- return () => {
44
- unsub();
45
- stopInterval();
46
- };
47
- }, [runtime, stateName, intervalMs]);
48
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * useWorkflow — Create and manage a WorkflowRuntime instance
3
- *
4
- * Creates a standalone WorkflowRuntime from a definition and returns
5
- * a handle with snapshot state and methods to interact with the runtime.
6
- */
7
-
8
- import { useState, useEffect, useMemo, useCallback } from 'react';
9
- import { WorkflowRuntime } from '../core/WorkflowRuntime';
10
- import type { RuntimeSnapshot } from '../core/WorkflowRuntime';
11
- import type { PlayerWorkflowDefinition, TransitionResult, ExpressionContext } from '@mindmatrix/player-core';
12
-
13
- export interface UseWorkflowOptions {
14
- initialData?: Record<string, unknown>;
15
- actionHandlers?: Record<string, (config: Record<string, unknown>, ctx: ExpressionContext) => void | Promise<void>>;
16
- onTransition?: (result: TransitionResult) => void;
17
- }
18
-
19
- export interface WorkflowHandle {
20
- slug: string;
21
- currentState: string;
22
- stateData: Record<string, unknown>;
23
- memory: Record<string, unknown>;
24
- status: 'ACTIVE' | 'COMPLETED' | 'CANCELLED';
25
- availableTransitions: string[];
26
- transition: (name: string, data?: Record<string, unknown>) => Promise<TransitionResult>;
27
- setField: (field: string, value: unknown) => void;
28
- setMemory: (key: string, value: unknown) => void;
29
- publishEvent: (topic: string, payload?: Record<string, unknown>) => void;
30
- runtime: WorkflowRuntime;
31
- }
32
-
33
- export function useWorkflow(definition: PlayerWorkflowDefinition, options?: UseWorkflowOptions): WorkflowHandle {
34
- const runtime = useMemo(() => new WorkflowRuntime({
35
- definition,
36
- initialData: options?.initialData,
37
- actionHandlers: options?.actionHandlers,
38
- }), [definition.id]);
39
-
40
- const [snapshot, setSnapshot] = useState<RuntimeSnapshot>(() => runtime.getSnapshot());
41
-
42
- useEffect(() => {
43
- const unsub = runtime.subscribe(setSnapshot);
44
- return unsub;
45
- }, [runtime]);
46
-
47
- const transition = useCallback(async (name: string, data?: Record<string, unknown>) => {
48
- const result = await runtime.transition(name, data);
49
- options?.onTransition?.(result);
50
- return result;
51
- }, [runtime, options?.onTransition]);
52
-
53
- const handle: WorkflowHandle = {
54
- slug: definition.slug,
55
- ...snapshot,
56
- transition,
57
- setField: runtime.setField.bind(runtime),
58
- setMemory: runtime.setMemory.bind(runtime),
59
- publishEvent: runtime.publishEvent.bind(runtime),
60
- runtime,
61
- };
62
- return handle;
63
- }