@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,59 +0,0 @@
1
- /**
2
- * useModule — Load and configure a module dependency.
3
- *
4
- * Modules are self-contained blueprints that provide their own routes, models,
5
- * and UI. useModule registers the module's routes and slot contributions at
6
- * runtime, and passes configuration to the module's config model.
7
- *
8
- * The module's views are rendered via the Router when their routes match.
9
- * Cross-module communication happens via:
10
- * - Slots: module contributes views to named slots in the parent app
11
- * - Data queries: parent can useQuery() on module's models
12
- * - Guards: route guards reference shared context (e.g. context.actor_id)
13
- */
14
-
15
- import { useMemo } from 'react';
16
-
17
- export interface UseModuleOptions {
18
- /** Whether the module is enabled (default: true) */
19
- enabled?: boolean;
20
- }
21
-
22
- export interface ModuleHandle {
23
- /** The module slug */
24
- slug: string;
25
- /** Configuration passed to the module */
26
- config: Record<string, unknown>;
27
- /** Whether the module is loaded */
28
- isLoaded: boolean;
29
- }
30
-
31
- /**
32
- * Load a module dependency and pass configuration to it.
33
- *
34
- * @param slug - The module's slug (e.g. 'mod-authentication')
35
- * @param config - Configuration values passed to the module (merged with defaults from configSchema)
36
- * @param options - Additional options
37
- *
38
- * @example
39
- * ```tsx
40
- * const auth = useModule('mod-authentication', {
41
- * appName: 'My App',
42
- * layout: 'card',
43
- * redirectPath: '/',
44
- * });
45
- * ```
46
- */
47
- export function useModule(
48
- slug: string,
49
- config: Record<string, unknown> = {},
50
- options: UseModuleOptions = {},
51
- ): ModuleHandle {
52
- const { enabled = true } = options;
53
-
54
- return useMemo(() => ({
55
- slug,
56
- config,
57
- isLoaded: enabled,
58
- }), [slug, enabled]);
59
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * useModuleConfig — Read merged module configuration.
3
- *
4
- * Merges configSchema.defaults with install-time config overrides.
5
- * Works both inside and outside a module boundary:
6
- * - Inside: reads from ModuleContext (no slug needed)
7
- * - Outside: requires explicit slug, reads from parent's installed_modules metadata
8
- */
9
-
10
- import { useMemo } from 'react';
11
-
12
- /**
13
- * Installed module reference stored in parent definition metadata.
14
- * This matches the shape written by handleInstallModule in GlassConsoleShell.
15
- */
16
- export interface InstalledModuleRef {
17
- slug: string;
18
- moduleId: string;
19
- version?: string;
20
- installedAt?: string;
21
- config?: Record<string, unknown>;
22
- childSlugs?: string[];
23
- routeConfig?: { prefix?: string; routes?: Record<string, string | false> };
24
- slotMapping?: Record<string, string>;
25
- }
26
-
27
- /** Store for installed modules — populated at runtime by the editor/player */
28
- let installedModulesStore: InstalledModuleRef[] = [];
29
- let configDefaultsStore: Map<string, Record<string, unknown>> = new Map();
30
-
31
- /** Set the installed modules list (called by editor/player on load) */
32
- export function setInstalledModules(modules: InstalledModuleRef[]): void {
33
- installedModulesStore = modules;
34
- }
35
-
36
- /** Set config defaults for a module (from configSchema.defaults in manifest) */
37
- export function setModuleConfigDefaults(moduleSlug: string, defaults: Record<string, unknown>): void {
38
- configDefaultsStore.set(moduleSlug, defaults);
39
- }
40
-
41
- /** Get the installed module ref by slug */
42
- export function getInstalledModule(slug: string): InstalledModuleRef | undefined {
43
- return installedModulesStore.find(m => m.slug === slug);
44
- }
45
-
46
- /** Get all installed modules */
47
- export function getInstalledModules(): InstalledModuleRef[] {
48
- return installedModulesStore;
49
- }
50
-
51
- /**
52
- * Read the merged configuration for a module.
53
- * Merges: configSchema.defaults ← install-time config
54
- */
55
- export function useModuleConfig(moduleSlug: string): Record<string, unknown> {
56
- return useMemo(() => {
57
- const installed = getInstalledModule(moduleSlug);
58
- const defaults = configDefaultsStore.get(moduleSlug) ?? {};
59
- return { ...defaults, ...installed?.config };
60
- }, [moduleSlug]);
61
- }
@@ -1,237 +0,0 @@
1
- /**
2
- * useMutation — imperative mutation hook for workflow instances.
3
- *
4
- * Provides create, update, and transition operations against the workflow engine.
5
- *
6
- * Usage in .workflow.tsx:
7
- * const mutation = useMutation('channel');
8
- *
9
- * // Create a new instance
10
- * const id = await mutation.create({ name: 'General', isPublic: true });
11
- *
12
- * // Update fields
13
- * await mutation.update(id, { name: 'Updated Name' });
14
- *
15
- * // Fire a transition
16
- * await mutation.transition(id, 'activate');
17
- *
18
- * The compiler extracts useMutation calls as action stubs in the IR.
19
- * The runtime resolves them via the configured mutation adapter.
20
- */
21
-
22
- import { useState, useCallback, useRef } from 'react';
23
- import type { ModelDefinition } from '../config/defineModel';
24
- import type { InferFields, InferTransitions } from '../types/workflow-inference';
25
-
26
- // =============================================================================
27
- // Types
28
- // =============================================================================
29
-
30
- /** Typed mutation handle — field names and transition names are constrained. */
31
- export interface TypedMutationHandle<Fields, Transitions extends string> {
32
- /** Create a new workflow instance. Returns the instance ID. */
33
- create: (input?: Partial<Fields>) => Promise<string>;
34
- /** Update fields on an existing instance. */
35
- update: (instanceId: string, fields: Partial<Fields>) => Promise<void>;
36
- /** Fire a transition on an instance. */
37
- transition: (instanceId: string, transitionName: Transitions, input?: Record<string, unknown>) => Promise<void>;
38
- /** Fire a transition on an instance (alias for transition). */
39
- trigger: (instanceId: string, transitionName: Transitions, input?: Record<string, unknown>) => Promise<void>;
40
- /**
41
- * Advance to the next state automatically.
42
- * The engine evaluates all outgoing transitions from the current state
43
- * and fires the first one whose conditions are satisfied (evaluated in definition order).
44
- * If only one outgoing transition exists, it fires unconditionally.
45
- */
46
- next: (instanceId: string, input?: Record<string, unknown>) => Promise<void>;
47
- /** Delete an instance. */
48
- remove: (instanceId: string) => Promise<void>;
49
- /** Whether any mutation is currently in progress. */
50
- isPending: boolean;
51
- /** Error from the last mutation attempt. */
52
- error: Error | null;
53
- /** Reset the error state. */
54
- reset: () => void;
55
- }
56
-
57
- export interface MutationHandle {
58
- /** Create a new workflow instance. Returns the instance ID. */
59
- create: (input?: Record<string, unknown>) => Promise<string>;
60
- /** Update fields on an existing instance. */
61
- update: (instanceId: string, fields: Record<string, unknown>) => Promise<void>;
62
- /** Fire a transition on an instance. */
63
- transition: (instanceId: string, transitionName: string, input?: Record<string, unknown>) => Promise<void>;
64
- /** Fire a transition on an instance (alias for transition). */
65
- trigger: (instanceId: string, transitionName: string, input?: Record<string, unknown>) => Promise<void>;
66
- /**
67
- * Advance to the next state automatically.
68
- * The engine evaluates all outgoing transitions from the current state
69
- * and fires the first one whose conditions are satisfied (evaluated in definition order).
70
- * If only one outgoing transition exists, it fires unconditionally.
71
- */
72
- next: (instanceId: string, input?: Record<string, unknown>) => Promise<void>;
73
- /** Delete an instance. */
74
- remove: (instanceId: string) => Promise<void>;
75
- /** Whether any mutation is currently in progress. */
76
- isPending: boolean;
77
- /** Error from the last mutation attempt. */
78
- error: Error | null;
79
- /** Reset the error state. */
80
- reset: () => void;
81
- }
82
-
83
- /**
84
- * Mutation resolver interface — the runtime must provide an implementation.
85
- */
86
- export interface MutationResolver {
87
- create: (slug: string, input?: Record<string, unknown>) => Promise<string>;
88
- update: (slug: string, instanceId: string, fields: Record<string, unknown>) => Promise<void>;
89
- transition: (slug: string, instanceId: string, transitionName: string, input?: Record<string, unknown>) => Promise<void>;
90
- /** Advance to next state. The engine picks the first valid outgoing transition. */
91
- next?: (slug: string, instanceId: string, input?: Record<string, unknown>) => Promise<void>;
92
- remove: (slug: string, instanceId: string) => Promise<void>;
93
- }
94
-
95
- // Global resolver (set by provider)
96
- let _globalMutationResolver: MutationResolver | null = null;
97
-
98
- export function setMutationResolver(resolver: MutationResolver | null): void {
99
- _globalMutationResolver = resolver;
100
- }
101
-
102
- // =============================================================================
103
- // Hook
104
- // =============================================================================
105
-
106
- /**
107
- * Provides mutation operations for a workflow definition.
108
- *
109
- * Overload 1: Type-safe — pass a model definition for typed transitions.
110
- * Overload 2: String slug — backward-compatible, untyped.
111
- *
112
- * @example
113
- * ```tsx
114
- * // Typed (recommended)
115
- * import channelModel from '../models/channel';
116
- * const mutation = useMutation(channelModel);
117
- * mutation.trigger(id, 'activate'); // ✓ valid
118
- * mutation.trigger(id, 'nonexistent'); // ✗ TS error
119
- *
120
- * // Untyped (backward compatible)
121
- * const mutation = useMutation('chat-channel');
122
- * ```
123
- */
124
- export function useMutation<D extends ModelDefinition>(
125
- definition: D,
126
- ): TypedMutationHandle<InferFields<D>, InferTransitions<D> & string>;
127
- export function useMutation(slug: string): MutationHandle;
128
- export function useMutation(slugOrDef: string | ModelDefinition): MutationHandle {
129
- const slug = typeof slugOrDef === 'string' ? slugOrDef : slugOrDef.slug;
130
- const [isPending, setIsPending] = useState(false);
131
- const [error, setError] = useState<Error | null>(null);
132
- const slugRef = useRef(slug);
133
- slugRef.current = slug;
134
-
135
- const getResolver = useCallback((): MutationResolver => {
136
- const resolver = _globalMutationResolver;
137
- if (!resolver) {
138
- throw new Error(`useMutation: No mutation resolver configured. Wrap your app in a WorkflowProvider.`);
139
- }
140
- return resolver;
141
- }, []);
142
-
143
- const create = useCallback(async (input?: Record<string, unknown>): Promise<string> => {
144
- try {
145
- setIsPending(true);
146
- setError(null);
147
- const resolver = getResolver();
148
- return await resolver.create(slugRef.current, input);
149
- } catch (err) {
150
- const e = err instanceof Error ? err : new Error(String(err));
151
- setError(e);
152
- throw e;
153
- } finally {
154
- setIsPending(false);
155
- }
156
- }, [getResolver]);
157
-
158
- const update = useCallback(async (instanceId: string, fields: Record<string, unknown>): Promise<void> => {
159
- try {
160
- setIsPending(true);
161
- setError(null);
162
- const resolver = getResolver();
163
- await resolver.update(slugRef.current, instanceId, fields);
164
- } catch (err) {
165
- const e = err instanceof Error ? err : new Error(String(err));
166
- setError(e);
167
- throw e;
168
- } finally {
169
- setIsPending(false);
170
- }
171
- }, [getResolver]);
172
-
173
- const transitionFn = useCallback(async (instanceId: string, transitionName: string, input?: Record<string, unknown>): Promise<void> => {
174
- try {
175
- setIsPending(true);
176
- setError(null);
177
- const resolver = getResolver();
178
- await resolver.transition(slugRef.current, instanceId, transitionName, input);
179
- } catch (err) {
180
- const e = err instanceof Error ? err : new Error(String(err));
181
- setError(e);
182
- throw e;
183
- } finally {
184
- setIsPending(false);
185
- }
186
- }, [getResolver]);
187
-
188
- const next = useCallback(async (instanceId: string, input?: Record<string, unknown>): Promise<void> => {
189
- try {
190
- setIsPending(true);
191
- setError(null);
192
- const resolver = getResolver();
193
- if (resolver.next) {
194
- await resolver.next(slugRef.current, instanceId, input);
195
- } else {
196
- // Fallback: transition with special '__next__' name — engine resolves it
197
- await resolver.transition(slugRef.current, instanceId, '__next__', input);
198
- }
199
- } catch (err) {
200
- const e = err instanceof Error ? err : new Error(String(err));
201
- setError(e);
202
- throw e;
203
- } finally {
204
- setIsPending(false);
205
- }
206
- }, [getResolver]);
207
-
208
- const remove = useCallback(async (instanceId: string): Promise<void> => {
209
- try {
210
- setIsPending(true);
211
- setError(null);
212
- const resolver = getResolver();
213
- await resolver.remove(slugRef.current, instanceId);
214
- } catch (err) {
215
- const e = err instanceof Error ? err : new Error(String(err));
216
- setError(e);
217
- throw e;
218
- } finally {
219
- setIsPending(false);
220
- }
221
- }, [getResolver]);
222
-
223
- const reset = useCallback(() => setError(null), []);
224
-
225
- const handle: MutationHandle = {
226
- create,
227
- update,
228
- transition: transitionFn,
229
- trigger: transitionFn,
230
- next,
231
- remove,
232
- isPending,
233
- error,
234
- reset,
235
- };
236
- return handle;
237
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * useNotification — Push notification hook for workflow applications.
3
- *
4
- * Wraps the browser Notification API with permission management
5
- * and a React-friendly interface.
6
- *
7
- * Usage in .workflow.tsx:
8
- * const { notify, permission, requestPermission, supported } = useNotification();
9
- *
10
- * // Request permission on first use
11
- * if (permission === 'default') {
12
- * await requestPermission();
13
- * }
14
- *
15
- * // Send notification when ride arrives
16
- * useOnChange('rideStatus', (status) => {
17
- * if (status === 'arrived') {
18
- * notify('Your ride has arrived!', {
19
- * body: 'Your driver is waiting outside.',
20
- * icon: '/icons/car.png',
21
- * });
22
- * }
23
- * });
24
- */
25
-
26
- import { useState, useCallback, useEffect, useMemo } from 'react';
27
-
28
- // =============================================================================
29
- // Types
30
- // =============================================================================
31
-
32
- /** Notification options (mirrors the browser Notification API). */
33
- export interface NotifyOptions {
34
- /** Notification body text. */
35
- body?: string;
36
- /** Notification icon URL. */
37
- icon?: string;
38
- /** Badge icon URL (for mobile). */
39
- badge?: string;
40
- /** Notification image URL. */
41
- image?: string;
42
- /** Notification tag (for grouping/replacing). */
43
- tag?: string;
44
- /** Whether to require user interaction to dismiss. */
45
- requireInteraction?: boolean;
46
- /** Vibration pattern (ms array). */
47
- vibrate?: number[];
48
- /** Auto-close after ms (default: 5000, 0 = no auto-close). */
49
- autoClose?: number;
50
- /** Data payload attached to the notification. */
51
- data?: unknown;
52
- /** Click handler. */
53
- onClick?: (notification: Notification) => void;
54
- /** Close handler. */
55
- onClose?: () => void;
56
- /** Error handler. */
57
- onError?: (error: Error) => void;
58
- }
59
-
60
- /** Notification result returned by useNotification. */
61
- export interface NotificationResult {
62
- /** Current permission state: 'default' | 'granted' | 'denied'. */
63
- permission: NotificationPermission;
64
- /** Whether the browser supports notifications. */
65
- supported: boolean;
66
- /** Request notification permission from the user. */
67
- requestPermission: () => Promise<NotificationPermission>;
68
- /** Show a notification. Returns the Notification instance (or null if not permitted). */
69
- notify: (title: string, options?: NotifyOptions) => Notification | null;
70
- }
71
-
72
- // =============================================================================
73
- // Hook
74
- // =============================================================================
75
-
76
- /**
77
- * Push notification management via the browser Notification API.
78
- *
79
- * @returns Notification handle with permission state and notify method.
80
- */
81
- export function useNotification(): NotificationResult {
82
- const supported = typeof window !== 'undefined' && 'Notification' in window;
83
-
84
- const [permission, setPermission] = useState<NotificationPermission>(
85
- supported ? Notification.permission : 'denied',
86
- );
87
-
88
- // Sync permission state (can change externally)
89
- useEffect(() => {
90
- if (!supported) return;
91
-
92
- const interval = setInterval(() => {
93
- if (Notification.permission !== permission) {
94
- setPermission(Notification.permission);
95
- }
96
- }, 1000);
97
-
98
- return () => clearInterval(interval);
99
- }, [supported, permission]);
100
-
101
- const requestPermission = useCallback(async (): Promise<NotificationPermission> => {
102
- if (!supported) return 'denied';
103
-
104
- const result = await Notification.requestPermission();
105
- setPermission(result);
106
- return result;
107
- }, [supported]);
108
-
109
- const notify = useCallback(
110
- (title: string, options: NotifyOptions = {}): Notification | null => {
111
- if (!supported || permission !== 'granted') return null;
112
-
113
- const {
114
- onClick,
115
- onClose,
116
- onError,
117
- autoClose = 5000,
118
- ...notificationOptions
119
- } = options;
120
-
121
- try {
122
- const notification = new Notification(title, notificationOptions);
123
-
124
- if (onClick) {
125
- notification.onclick = () => onClick(notification);
126
- }
127
- if (onClose) {
128
- notification.onclose = onClose;
129
- }
130
- if (onError) {
131
- notification.onerror = () => onError(new Error('Notification error'));
132
- }
133
-
134
- if (autoClose > 0) {
135
- setTimeout(() => notification.close(), autoClose);
136
- }
137
-
138
- return notification;
139
- } catch (err) {
140
- options.onError?.(err instanceof Error ? err : new Error(String(err)));
141
- return null;
142
- }
143
- },
144
- [supported, permission],
145
- );
146
-
147
- return useMemo(
148
- (): NotificationResult => ({ permission, supported, requestPermission, notify }),
149
- [permission, supported, requestPermission, notify],
150
- );
151
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * useOnChange — React to state data field changes
3
- *
4
- * Runs handler when a specific field in stateData changes.
5
- * Provides both newValue and oldValue to the handler.
6
- */
7
-
8
- import { useEffect, useRef } from 'react';
9
- import { useRuntimeContext } from '../providers/RuntimeContext';
10
-
11
- export function useOnChange(
12
- field: string,
13
- handler: (newValue: unknown, oldValue: unknown) => void | Promise<void>
14
- ): void {
15
- const runtime = useRuntimeContext();
16
- const handlerRef = useRef(handler);
17
- handlerRef.current = handler;
18
- const prevRef = useRef<unknown>(runtime.sm.stateData[field]);
19
-
20
- useEffect(() => {
21
- const unsub = runtime.subscribe((snap) => {
22
- const newVal = snap.stateData[field];
23
- if (newVal !== prevRef.current) {
24
- handlerRef.current(newVal, prevRef.current);
25
- prevRef.current = newVal;
26
- }
27
- });
28
- return unsub;
29
- }, [runtime, field]);
30
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * useOnEnter — Fire effect when entering a state
3
- *
4
- * Runs effect when the workflow enters the specified state(s).
5
- * If already in the target state, fires immediately.
6
- * Cleanup fires when exiting the state.
7
- */
8
-
9
- import { useEffect, useRef } from 'react';
10
- import { useRuntimeContext } from '../providers/RuntimeContext';
11
-
12
- type EffectCleanup = void | (() => void);
13
- type EffectCallback = () => EffectCleanup | Promise<EffectCleanup>;
14
-
15
- export function useOnEnter(stateName: string | string[], effect: EffectCallback): void {
16
- const runtime = useRuntimeContext();
17
- const effectRef = useRef(effect);
18
- effectRef.current = effect;
19
- const cleanupRef = useRef<(() => void) | null>(null);
20
- const states = Array.isArray(stateName) ? stateName : [stateName];
21
-
22
- useEffect(() => {
23
- // If already in target state, fire immediately
24
- if (states.includes(runtime.sm.currentState)) {
25
- const result = effectRef.current();
26
- if (result instanceof Promise) {
27
- result.then(cleanup => {
28
- if (cleanup && typeof cleanup === 'function') cleanupRef.current = cleanup;
29
- });
30
- } else if (result && typeof result === 'function') {
31
- cleanupRef.current = result;
32
- }
33
- }
34
-
35
- const unsub = runtime.sm.on((event) => {
36
- if (event.type === 'state_enter' && event.to_state && states.includes(event.to_state)) {
37
- // Cleanup previous if re-entering
38
- cleanupRef.current?.();
39
- const result = effectRef.current();
40
- if (result instanceof Promise) {
41
- result.then(cleanup => {
42
- if (cleanup && typeof cleanup === 'function') cleanupRef.current = cleanup;
43
- });
44
- } else if (result && typeof result === 'function') {
45
- cleanupRef.current = result;
46
- }
47
- }
48
- if (event.type === 'state_exit' && event.from_state && states.includes(event.from_state)) {
49
- cleanupRef.current?.();
50
- cleanupRef.current = null;
51
- }
52
- });
53
-
54
- return () => {
55
- unsub();
56
- cleanupRef.current?.();
57
- };
58
- }, [runtime, ...states]);
59
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * useOnEvent — Subscribe to EventBus events with optional state filtering
3
- *
4
- * Runs handler when events matching the pattern are emitted.
5
- * Optionally filters events to only fire while in a specific state.
6
- */
7
-
8
- import { useEffect, useRef } from 'react';
9
- import { useRuntimeContext } from '../providers/RuntimeContext';
10
-
11
- export interface EventPayload {
12
- topic: string;
13
- payload?: Record<string, unknown>;
14
- }
15
-
16
- export interface UseOnEventOptions {
17
- while?: string;
18
- }
19
-
20
- export function useOnEvent(
21
- pattern: string,
22
- handler: (event: EventPayload) => void | Promise<void>,
23
- options?: UseOnEventOptions
24
- ): void {
25
- const runtime = useRuntimeContext();
26
- const handlerRef = useRef(handler);
27
- handlerRef.current = handler;
28
-
29
- useEffect(() => {
30
- const unsub = runtime.eventBus.subscribe(pattern, (event) => {
31
- // If { while: state } option, only fire when in that state
32
- if (options?.while && runtime.sm.currentState !== options.while) return;
33
- handlerRef.current(event);
34
- });
35
- return unsub;
36
- }, [runtime, pattern, options?.while]);
37
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * useOnExit — Fire effect when exiting a state
3
- *
4
- * Runs effect when the workflow exits the specified state(s).
5
- */
6
-
7
- import { useEffect, useRef } from 'react';
8
- import { useRuntimeContext } from '../providers/RuntimeContext';
9
-
10
- type EffectCallback = () => void | Promise<void>;
11
-
12
- export function useOnExit(stateName: string | string[], effect: EffectCallback): void {
13
- const runtime = useRuntimeContext();
14
- const effectRef = useRef(effect);
15
- effectRef.current = effect;
16
- const states = Array.isArray(stateName) ? stateName : [stateName];
17
-
18
- useEffect(() => {
19
- const unsub = runtime.sm.on((event) => {
20
- if (event.type === 'state_exit' && event.from_state && states.includes(event.from_state)) {
21
- effectRef.current();
22
- }
23
- });
24
-
25
- return unsub;
26
- }, [runtime, ...states]);
27
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * useOnTransition — Fire effect on any state transition
3
- *
4
- * Runs effect whenever a transition occurs.
5
- * Receives { from, to } as parameters.
6
- */
7
-
8
- import { useEffect, useRef } from 'react';
9
- import { useRuntimeContext } from '../providers/RuntimeContext';
10
-
11
- type TransitionCallback = (transition: { from?: string; to?: string }) => void | Promise<void>;
12
-
13
- export function useOnTransition(effect: TransitionCallback): void {
14
- const runtime = useRuntimeContext();
15
- const effectRef = useRef(effect);
16
- effectRef.current = effect;
17
-
18
- useEffect(() => {
19
- const unsub = runtime.sm.on((event) => {
20
- if (event.type === 'transition') {
21
- effectRef.current({
22
- from: event.from_state,
23
- to: event.to_state,
24
- });
25
- }
26
- });
27
-
28
- return unsub;
29
- }, [runtime]);
30
- }