@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.
- package/README.md +112 -0
- package/dist/index.d.mts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +70 -3
- package/dist/index.mjs +74 -12
- 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
|
@@ -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
|
-
}
|
package/src/hooks/useToast.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useView.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useWhileIn.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useWorkflow.ts
DELETED
|
@@ -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
|
-
}
|