@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
package/src/hooks/usePackage.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* usePackage — Load a package manifest and resolve its dependencies
|
|
3
|
-
*
|
|
4
|
-
* Reads a blueprint definition (category='blueprint') from the runtime context,
|
|
5
|
-
* extracts its PackageManifest from metadata, and resolves child definitions
|
|
6
|
-
* and dependency packages.
|
|
7
|
-
*
|
|
8
|
-
* Usage in .workflow.tsx:
|
|
9
|
-
* const { manifest, children, isResolved } = usePackage('my-package');
|
|
10
|
-
* // Access child definitions via scoped slugs
|
|
11
|
-
* const mainWorkflow = children.find(c => c.slug === manifest.entrypoint);
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { useMemo } from 'react';
|
|
15
|
-
import { useRuntimeContext } from '../providers/RuntimeContext';
|
|
16
|
-
|
|
17
|
-
export interface PackageManifest {
|
|
18
|
-
slug: string;
|
|
19
|
-
name: string;
|
|
20
|
-
version: string;
|
|
21
|
-
description?: string;
|
|
22
|
-
dependencies: Record<string, string>;
|
|
23
|
-
exports: string[];
|
|
24
|
-
private: string[];
|
|
25
|
-
entrypoint?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface PackageChild {
|
|
29
|
-
id: string;
|
|
30
|
-
slug: string;
|
|
31
|
-
name: string;
|
|
32
|
-
category: string;
|
|
33
|
-
version?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface PackageResult {
|
|
37
|
-
/** The package manifest (null if not found or not a package) */
|
|
38
|
-
manifest: PackageManifest | null;
|
|
39
|
-
/** Child definitions within this package */
|
|
40
|
-
children: PackageChild[];
|
|
41
|
-
/** Whether all dependencies are resolved */
|
|
42
|
-
isResolved: boolean;
|
|
43
|
-
/** Dependency package slugs */
|
|
44
|
-
dependencySlugs: string[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function usePackage(packageSlug: string): PackageResult {
|
|
48
|
-
const runtime = useRuntimeContext();
|
|
49
|
-
|
|
50
|
-
return useMemo((): PackageResult => {
|
|
51
|
-
const snapshot = runtime.getSnapshot();
|
|
52
|
-
const definition = (runtime as any).config?.definition;
|
|
53
|
-
|
|
54
|
-
// Look for a blueprint definition matching the slug
|
|
55
|
-
const childDefs: any[] = definition?.child_definitions ?? [];
|
|
56
|
-
const metadata: Record<string, any> = definition?.metadata ?? {};
|
|
57
|
-
|
|
58
|
-
// If this definition IS the package
|
|
59
|
-
if (definition?.slug === packageSlug && definition?.category === 'blueprint') {
|
|
60
|
-
const manifest: PackageManifest = {
|
|
61
|
-
slug: definition.slug,
|
|
62
|
-
name: definition.name ?? packageSlug,
|
|
63
|
-
version: definition.version ?? '0.0.1',
|
|
64
|
-
description: metadata.description ?? '',
|
|
65
|
-
dependencies: metadata.dependencies ?? {},
|
|
66
|
-
exports: metadata.exports ?? [],
|
|
67
|
-
private: metadata.private ?? [],
|
|
68
|
-
entrypoint: metadata.entrypoint,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const children: PackageChild[] = childDefs.map((child: any) => ({
|
|
72
|
-
id: String(child.id ?? ''),
|
|
73
|
-
slug: String(child.slug ?? ''),
|
|
74
|
-
name: String(child.name ?? child.slug ?? ''),
|
|
75
|
-
category: String(child.category ?? 'workflow'),
|
|
76
|
-
version: child.version ? String(child.version) : undefined,
|
|
77
|
-
}));
|
|
78
|
-
|
|
79
|
-
const dependencySlugs = Object.keys(manifest.dependencies);
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
manifest,
|
|
83
|
-
children,
|
|
84
|
-
isResolved: dependencySlugs.length === 0, // Simple: resolved if no deps
|
|
85
|
-
dependencySlugs,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Search child_definitions for a nested package reference
|
|
90
|
-
const stateData = snapshot.stateData as Record<string, any> | undefined;
|
|
91
|
-
const packages: any[] = stateData?.packages ?? [];
|
|
92
|
-
const found = packages.find((p: any) => p.slug === packageSlug);
|
|
93
|
-
|
|
94
|
-
if (found) {
|
|
95
|
-
const manifest: PackageManifest = {
|
|
96
|
-
slug: found.slug,
|
|
97
|
-
name: found.name ?? packageSlug,
|
|
98
|
-
version: found.version ?? '0.0.1',
|
|
99
|
-
description: found.description ?? '',
|
|
100
|
-
dependencies: found.dependencies ?? {},
|
|
101
|
-
exports: found.exports ?? [],
|
|
102
|
-
private: found.private ?? [],
|
|
103
|
-
entrypoint: found.entrypoint,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
manifest,
|
|
108
|
-
children: (found.children ?? []).map((c: any) => ({
|
|
109
|
-
id: String(c.id ?? ''),
|
|
110
|
-
slug: String(c.slug ?? ''),
|
|
111
|
-
name: String(c.name ?? c.slug ?? ''),
|
|
112
|
-
category: String(c.category ?? 'workflow'),
|
|
113
|
-
version: c.version ? String(c.version) : undefined,
|
|
114
|
-
})),
|
|
115
|
-
isResolved: Object.keys(manifest.dependencies).length === 0,
|
|
116
|
-
dependencySlugs: Object.keys(manifest.dependencies),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Not found
|
|
121
|
-
return {
|
|
122
|
-
manifest: null,
|
|
123
|
-
children: [],
|
|
124
|
-
isResolved: false,
|
|
125
|
-
dependencySlugs: [],
|
|
126
|
-
};
|
|
127
|
-
}, [runtime, packageSlug]);
|
|
128
|
-
}
|
package/src/hooks/useParams.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useParams — Access workflow params passed via call invocation
|
|
3
|
-
*
|
|
4
|
-
* When a workflow is invoked via `call "slug"(params)`, the params
|
|
5
|
-
* are stored in the instance's context. This hook reads them.
|
|
6
|
-
* Supports D1 workflows-as-functions.
|
|
7
|
-
*
|
|
8
|
-
* Usage in .workflow.tsx:
|
|
9
|
-
* const { orderId, quantity } = useParams<{ orderId: string; quantity: number }>();
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { useMemo } from 'react';
|
|
13
|
-
import { useRuntimeContext } from '../providers/RuntimeContext';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Read the invocation parameters for the current workflow instance.
|
|
17
|
-
*
|
|
18
|
-
* @returns The params object passed when this workflow was called, or an empty object.
|
|
19
|
-
*/
|
|
20
|
-
export function useParams<T extends Record<string, unknown> = Record<string, unknown>>(): T {
|
|
21
|
-
const runtime = useRuntimeContext();
|
|
22
|
-
|
|
23
|
-
return useMemo(() => {
|
|
24
|
-
const snapshot = runtime.getSnapshot();
|
|
25
|
-
// Params are passed via call invocation and stored in state_data._params
|
|
26
|
-
// or directly spread into state_data as the initial data
|
|
27
|
-
const params =
|
|
28
|
-
(snapshot.stateData as any)?._params ??
|
|
29
|
-
(snapshot.memory as any)?._callParams ??
|
|
30
|
-
{};
|
|
31
|
-
return params as T;
|
|
32
|
-
}, [runtime]);
|
|
33
|
-
}
|
package/src/hooks/usePlayer.ts
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* usePlayer — Main hook for browser-side workflow engine.
|
|
3
|
-
*
|
|
4
|
-
* Creates and manages a StateMachine + EventBus + ActionDispatcher
|
|
5
|
-
* from a PlayerWorkflowDefinition. Returns a PlayerHandle for
|
|
6
|
-
* React components to interact with the engine.
|
|
7
|
-
*
|
|
8
|
-
* The hook:
|
|
9
|
-
* - Initializes the state machine from the definition's START state
|
|
10
|
-
* - Wires on_event subscriptions from state definitions to the event bus
|
|
11
|
-
* - Registers action handlers (built-in + custom)
|
|
12
|
-
* - Triggers React re-renders on state transitions
|
|
13
|
-
* - Provides structured logging at event match and action dispatch points
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
17
|
-
import {
|
|
18
|
-
StateMachine,
|
|
19
|
-
EventBus,
|
|
20
|
-
ActionDispatcher,
|
|
21
|
-
createEvaluator,
|
|
22
|
-
WEB_FAILURE_POLICIES,
|
|
23
|
-
} from '@mindmatrix/player-core';
|
|
24
|
-
import type {
|
|
25
|
-
TransitionResult,
|
|
26
|
-
Evaluator,
|
|
27
|
-
ExpressionContext,
|
|
28
|
-
StateMachineEvent,
|
|
29
|
-
ActionHandler,
|
|
30
|
-
PlayerAction,
|
|
31
|
-
} from '@mindmatrix/player-core';
|
|
32
|
-
import type { PlayerConfig, PlayerHandle } from '../types';
|
|
33
|
-
import { playerLog, setPlayerDebug } from '../logger';
|
|
34
|
-
|
|
35
|
-
interface PlayerState {
|
|
36
|
-
currentState: string;
|
|
37
|
-
stateData: Record<string, unknown>;
|
|
38
|
-
status: 'ACTIVE' | 'COMPLETED' | 'CANCELLED';
|
|
39
|
-
availableTransitions: string[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function computePlayerState(sm: StateMachine): PlayerState {
|
|
43
|
-
return {
|
|
44
|
-
currentState: sm.currentState,
|
|
45
|
-
stateData: sm.stateData,
|
|
46
|
-
status: sm.status as PlayerState['status'],
|
|
47
|
-
availableTransitions: sm.getAvailableTransitions().map(t => t.name),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function usePlayer(config: PlayerConfig): PlayerHandle {
|
|
52
|
-
const configRef = useRef(config);
|
|
53
|
-
configRef.current = config;
|
|
54
|
-
|
|
55
|
-
// Enable debug logging
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (config.debug) setPlayerDebug(true);
|
|
58
|
-
}, [config.debug]);
|
|
59
|
-
|
|
60
|
-
// Create evaluator (stable — only changes if functions change)
|
|
61
|
-
const evaluator = useMemo<Evaluator>(() => {
|
|
62
|
-
return createEvaluator({
|
|
63
|
-
functions: config.functions ?? [],
|
|
64
|
-
failurePolicy: WEB_FAILURE_POLICIES.EVENT_REACTION,
|
|
65
|
-
});
|
|
66
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
|
-
}, [config.definition.id]);
|
|
68
|
-
|
|
69
|
-
// Create engine components (stable per definition)
|
|
70
|
-
const engine = useMemo(() => {
|
|
71
|
-
const actionHandlers = new Map<string, ActionHandler>();
|
|
72
|
-
|
|
73
|
-
// Wrap custom ActionHandlerFn (config, ctx) into ActionHandler (action, ctx)
|
|
74
|
-
if (config.actionHandlers) {
|
|
75
|
-
for (const [type, handler] of Object.entries(config.actionHandlers)) {
|
|
76
|
-
actionHandlers.set(type, (action: PlayerAction, ctx: ExpressionContext) => handler(action.config, ctx));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Built-in set_field handler
|
|
81
|
-
let smRef: StateMachine | null = null;
|
|
82
|
-
actionHandlers.set('set_field', (action: PlayerAction) => {
|
|
83
|
-
if (smRef && typeof action.config.field === 'string') {
|
|
84
|
-
smRef.setField(action.config.field, action.config.value);
|
|
85
|
-
playerLog({
|
|
86
|
-
level: 'debug',
|
|
87
|
-
category: 'action_dispatch',
|
|
88
|
-
message: `set_field: ${action.config.field} = ${JSON.stringify(action.config.value)}`,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Built-in set_memory handler
|
|
94
|
-
actionHandlers.set('set_memory', (action: PlayerAction) => {
|
|
95
|
-
if (smRef && typeof action.config.key === 'string') {
|
|
96
|
-
smRef.setMemory(action.config.key, action.config.value);
|
|
97
|
-
playerLog({
|
|
98
|
-
level: 'debug',
|
|
99
|
-
category: 'action_dispatch',
|
|
100
|
-
message: `set_memory: ${action.config.key} = ${JSON.stringify(action.config.value)}`,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const sm = new StateMachine(
|
|
106
|
-
config.definition,
|
|
107
|
-
config.initialData ?? {},
|
|
108
|
-
{ evaluator, actionHandlers },
|
|
109
|
-
);
|
|
110
|
-
smRef = sm;
|
|
111
|
-
|
|
112
|
-
const eventBus = new EventBus();
|
|
113
|
-
const dispatcher = new ActionDispatcher();
|
|
114
|
-
|
|
115
|
-
// Register built-in handlers on dispatcher (for on_event action dispatch)
|
|
116
|
-
dispatcher.register('set_field', (cfg: Record<string, unknown>) => {
|
|
117
|
-
if (smRef && typeof cfg.field === 'string') {
|
|
118
|
-
smRef.setField(cfg.field, cfg.value);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
dispatcher.register('set_memory', (cfg: Record<string, unknown>) => {
|
|
122
|
-
if (smRef && typeof cfg.key === 'string') {
|
|
123
|
-
smRef.setMemory(cfg.key, cfg.value);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Mirror custom action handlers to dispatcher (dispatcher uses ActionHandlerFn signature)
|
|
128
|
-
if (config.actionHandlers) {
|
|
129
|
-
for (const [type, handler] of Object.entries(config.actionHandlers)) {
|
|
130
|
-
dispatcher.register(type, handler);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return { sm, eventBus, dispatcher };
|
|
135
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
136
|
-
}, [config.definition.id, evaluator]);
|
|
137
|
-
|
|
138
|
-
const { sm, eventBus } = engine;
|
|
139
|
-
|
|
140
|
-
// React state derived from state machine
|
|
141
|
-
const [playerState, setPlayerState] = useState<PlayerState>(() => computePlayerState(sm));
|
|
142
|
-
|
|
143
|
-
// Wire on_event subscriptions from current state definition
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
const stateDef = sm.getCurrentStateDefinition();
|
|
146
|
-
if (!stateDef?.on_event?.length) return;
|
|
147
|
-
|
|
148
|
-
const unsubs: Array<() => void> = [];
|
|
149
|
-
|
|
150
|
-
for (const sub of stateDef.on_event) {
|
|
151
|
-
playerLog({
|
|
152
|
-
level: 'debug',
|
|
153
|
-
category: 'event_match',
|
|
154
|
-
message: `Subscribing to pattern: "${sub.match}"`,
|
|
155
|
-
data: { state: sm.currentState, conditions: sub.conditions, actionCount: sub.actions.length },
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const unsub = eventBus.subscribe(sub.match, async (event) => {
|
|
159
|
-
playerLog({
|
|
160
|
-
level: 'info',
|
|
161
|
-
category: 'event_match',
|
|
162
|
-
message: `Event matched: "${event.topic}" → pattern "${sub.match}"`,
|
|
163
|
-
data: { state: sm.currentState, payload: event.payload },
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// Evaluate conditions
|
|
167
|
-
if (sub.conditions?.length) {
|
|
168
|
-
const ctx: ExpressionContext = {
|
|
169
|
-
...sm.stateData,
|
|
170
|
-
state_data: sm.stateData,
|
|
171
|
-
memory: sm.getSnapshot().memory,
|
|
172
|
-
current_state: sm.currentState,
|
|
173
|
-
status: sm.status,
|
|
174
|
-
event: event.payload,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
for (const condition of sub.conditions) {
|
|
178
|
-
const result = evaluator.evaluate<boolean>(condition, ctx);
|
|
179
|
-
if (!result.value) {
|
|
180
|
-
playerLog({
|
|
181
|
-
level: 'debug',
|
|
182
|
-
category: 'event_match',
|
|
183
|
-
message: `Condition not met: "${condition}" — skipping actions`,
|
|
184
|
-
});
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Execute actions
|
|
191
|
-
for (const action of sub.actions) {
|
|
192
|
-
playerLog({
|
|
193
|
-
level: 'info',
|
|
194
|
-
category: 'action_dispatch',
|
|
195
|
-
message: `Dispatching action: "${action.type}"`,
|
|
196
|
-
data: { config: action.config, trigger: 'on_event', pattern: sub.match },
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const ctx: ExpressionContext = {
|
|
200
|
-
...sm.stateData,
|
|
201
|
-
state_data: sm.stateData,
|
|
202
|
-
memory: sm.getSnapshot().memory,
|
|
203
|
-
current_state: sm.currentState,
|
|
204
|
-
status: sm.status,
|
|
205
|
-
event: event.payload,
|
|
206
|
-
};
|
|
207
|
-
await engine.dispatcher.execute([action], ctx, evaluator);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Update React state after actions
|
|
211
|
-
setPlayerState(computePlayerState(sm));
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
unsubs.push(unsub);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return () => {
|
|
218
|
-
for (const unsub of unsubs) unsub();
|
|
219
|
-
};
|
|
220
|
-
}, [sm, eventBus, evaluator, engine.dispatcher, playerState.currentState]);
|
|
221
|
-
|
|
222
|
-
// Listen to state machine events for React state sync
|
|
223
|
-
useEffect(() => {
|
|
224
|
-
const unsub = sm.on((event: StateMachineEvent) => {
|
|
225
|
-
if (event.type === 'transition' || event.type === 'state_enter') {
|
|
226
|
-
setPlayerState(computePlayerState(sm));
|
|
227
|
-
}
|
|
228
|
-
if (event.type === 'error') {
|
|
229
|
-
playerLog({
|
|
230
|
-
level: 'error',
|
|
231
|
-
category: 'transition',
|
|
232
|
-
message: `State machine error: ${event.error}`,
|
|
233
|
-
data: { action: event.action },
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
return unsub;
|
|
238
|
-
}, [sm]);
|
|
239
|
-
|
|
240
|
-
// Stable callbacks
|
|
241
|
-
const transition = useCallback(
|
|
242
|
-
async (name: string, data?: Record<string, unknown>): Promise<TransitionResult> => {
|
|
243
|
-
playerLog({
|
|
244
|
-
level: 'info',
|
|
245
|
-
category: 'transition',
|
|
246
|
-
message: `Transition requested: "${name}"`,
|
|
247
|
-
data: { from: sm.currentState, data },
|
|
248
|
-
});
|
|
249
|
-
const result = await sm.transition(name, data);
|
|
250
|
-
if (result.success) {
|
|
251
|
-
playerLog({
|
|
252
|
-
level: 'info',
|
|
253
|
-
category: 'transition',
|
|
254
|
-
message: `Transition complete: ${result.from_state} → ${result.to_state}`,
|
|
255
|
-
data: { actions: result.actions_executed.length },
|
|
256
|
-
});
|
|
257
|
-
} else {
|
|
258
|
-
playerLog({
|
|
259
|
-
level: 'warn',
|
|
260
|
-
category: 'transition',
|
|
261
|
-
message: `Transition failed: "${name}" — ${result.error}`,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return result;
|
|
265
|
-
},
|
|
266
|
-
[sm],
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const setField = useCallback(
|
|
270
|
-
(field: string, value: unknown) => {
|
|
271
|
-
sm.setField(field, value);
|
|
272
|
-
setPlayerState(computePlayerState(sm));
|
|
273
|
-
},
|
|
274
|
-
[sm],
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const setMemory = useCallback(
|
|
278
|
-
(key: string, value: unknown) => {
|
|
279
|
-
sm.setMemory(key, value);
|
|
280
|
-
},
|
|
281
|
-
[sm],
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
const publishEvent = useCallback(
|
|
285
|
-
(topic: string, payload?: Record<string, unknown>) => {
|
|
286
|
-
playerLog({
|
|
287
|
-
level: 'debug',
|
|
288
|
-
category: 'event_match',
|
|
289
|
-
message: `Publishing local event: "${topic}"`,
|
|
290
|
-
data: payload,
|
|
291
|
-
});
|
|
292
|
-
eventBus.emit(topic, payload);
|
|
293
|
-
},
|
|
294
|
-
[eventBus],
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
return {
|
|
298
|
-
currentState: playerState.currentState,
|
|
299
|
-
stateData: playerState.stateData,
|
|
300
|
-
status: playerState.status,
|
|
301
|
-
availableTransitions: playerState.availableTransitions,
|
|
302
|
-
transition,
|
|
303
|
-
setField,
|
|
304
|
-
setMemory,
|
|
305
|
-
publishEvent,
|
|
306
|
-
eventBus,
|
|
307
|
-
};
|
|
308
|
-
}
|
package/src/hooks/useQuery.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useQuery — reactive data fetching hook for workflow instances.
|
|
3
|
-
*
|
|
4
|
-
* Bridges the compiler (which extracts data source declarations) with
|
|
5
|
-
* the runtime (which resolves queries against the backend or local engine).
|
|
6
|
-
*
|
|
7
|
-
* Usage in .workflow.tsx:
|
|
8
|
-
* const { data: channels, loading, error, refetch } = useQuery('channel', {
|
|
9
|
-
* state: 'active',
|
|
10
|
-
* filter: { isPublic: true },
|
|
11
|
-
* limit: 20,
|
|
12
|
-
* });
|
|
13
|
-
*
|
|
14
|
-
* The compiler extracts this as an IRDataSource with type 'workflow'.
|
|
15
|
-
* The runtime resolves it via the configured data adapter.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
19
|
-
import type { ModelDefinition } from '../config/defineModel';
|
|
20
|
-
import type { InferFields } from '../types/workflow-inference';
|
|
21
|
-
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Types
|
|
24
|
-
// =============================================================================
|
|
25
|
-
|
|
26
|
-
export interface QueryParams {
|
|
27
|
-
/** Filter by workflow state. */
|
|
28
|
-
state?: string | string[];
|
|
29
|
-
/** Field-level filters. */
|
|
30
|
-
filter?: Record<string, unknown>;
|
|
31
|
-
/** Maximum number of results. */
|
|
32
|
-
limit?: number;
|
|
33
|
-
/** Offset for pagination. */
|
|
34
|
-
offset?: number;
|
|
35
|
-
/** Sort field. */
|
|
36
|
-
orderBy?: string;
|
|
37
|
-
/** Sort direction. */
|
|
38
|
-
order?: 'asc' | 'desc';
|
|
39
|
-
/** Sort field (prefix with - for descending, e.g. '-created_at'). */
|
|
40
|
-
sort?: string;
|
|
41
|
-
/** Search across text fields. */
|
|
42
|
-
search?: string;
|
|
43
|
-
/** Specific fields to search. */
|
|
44
|
-
searchFields?: string[];
|
|
45
|
-
/** Disable auto-fetching (manual mode). */
|
|
46
|
-
enabled?: boolean;
|
|
47
|
-
/** Refetch interval in milliseconds. */
|
|
48
|
-
refetchInterval?: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Represents a workflow instance as returned by the query API.
|
|
53
|
-
* All query results are instances with id, state, and fields.
|
|
54
|
-
*/
|
|
55
|
-
export interface WorkflowInstance<F = Record<string, unknown>> {
|
|
56
|
-
id: string;
|
|
57
|
-
state: string;
|
|
58
|
-
fields: F;
|
|
59
|
-
slug?: string;
|
|
60
|
-
created_at?: string;
|
|
61
|
-
updated_at?: string;
|
|
62
|
-
[key: string]: unknown;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface QueryResult<T> {
|
|
66
|
-
/** The query results — workflow instances. */
|
|
67
|
-
data: WorkflowInstance<T>[];
|
|
68
|
-
/** Whether the query is currently loading. */
|
|
69
|
-
loading: boolean;
|
|
70
|
-
/** Error from the last query attempt. */
|
|
71
|
-
error: Error | null;
|
|
72
|
-
/** Manually trigger a refetch. */
|
|
73
|
-
refetch: () => Promise<void>;
|
|
74
|
-
/** Total count (if available from backend). */
|
|
75
|
-
total?: number;
|
|
76
|
-
/** Whether there are more results. */
|
|
77
|
-
hasMore?: boolean;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Data resolver interface — the runtime must provide an implementation.
|
|
82
|
-
* This is typically provided by WorkflowProvider or LocalEngineProvider.
|
|
83
|
-
*/
|
|
84
|
-
export interface QueryResolver {
|
|
85
|
-
query: <T>(slug: string, params: QueryParams) => Promise<{ data: T[]; total?: number }>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Global resolver (set by provider)
|
|
89
|
-
let _globalResolver: QueryResolver | null = null;
|
|
90
|
-
|
|
91
|
-
export function setQueryResolver(resolver: QueryResolver | null): void {
|
|
92
|
-
_globalResolver = resolver;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// =============================================================================
|
|
96
|
-
// Hook
|
|
97
|
-
// =============================================================================
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Fetches workflow instances by slug with optional filtering.
|
|
101
|
-
*
|
|
102
|
-
* Overload 1: Type-safe — pass a model definition for typed results.
|
|
103
|
-
* Overload 2: String slug — backward-compatible, untyped.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```tsx
|
|
107
|
-
* // Typed (recommended)
|
|
108
|
-
* import channelModel from '../models/channel';
|
|
109
|
-
* const { data } = useQuery(channelModel, { state: 'active' });
|
|
110
|
-
* data[0].fields.name; // string — autocomplete works
|
|
111
|
-
*
|
|
112
|
-
* // Untyped (backward compatible)
|
|
113
|
-
* const { data } = useQuery('chat-channel');
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export function useQuery<D extends ModelDefinition>(
|
|
117
|
-
definition: D,
|
|
118
|
-
params?: QueryParams,
|
|
119
|
-
): QueryResult<InferFields<D>>;
|
|
120
|
-
export function useQuery<T = Record<string, unknown>>(
|
|
121
|
-
slug: string,
|
|
122
|
-
params?: QueryParams,
|
|
123
|
-
): QueryResult<T>;
|
|
124
|
-
export function useQuery(
|
|
125
|
-
slugOrDef: string | ModelDefinition,
|
|
126
|
-
params: QueryParams = {}
|
|
127
|
-
): QueryResult<any> {
|
|
128
|
-
const slug = typeof slugOrDef === 'string' ? slugOrDef : slugOrDef.slug;
|
|
129
|
-
const [data, setData] = useState<any[]>([]);
|
|
130
|
-
const [loading, setLoading] = useState(true);
|
|
131
|
-
const [error, setError] = useState<Error | null>(null);
|
|
132
|
-
const [total, setTotal] = useState<number | undefined>(undefined);
|
|
133
|
-
const paramsRef = useRef(params);
|
|
134
|
-
paramsRef.current = params;
|
|
135
|
-
|
|
136
|
-
const fetchData = useCallback(async () => {
|
|
137
|
-
if (paramsRef.current.enabled === false) {
|
|
138
|
-
setLoading(false);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const resolver = _globalResolver;
|
|
143
|
-
if (!resolver) {
|
|
144
|
-
setError(new Error(`useQuery: No query resolver configured. Wrap your app in a WorkflowProvider.`));
|
|
145
|
-
setLoading(false);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
setLoading(true);
|
|
151
|
-
setError(null);
|
|
152
|
-
const result = await resolver.query<any>(slug, paramsRef.current);
|
|
153
|
-
setData(result.data);
|
|
154
|
-
setTotal(result.total);
|
|
155
|
-
} catch (err) {
|
|
156
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
157
|
-
} finally {
|
|
158
|
-
setLoading(false);
|
|
159
|
-
}
|
|
160
|
-
}, [slug]);
|
|
161
|
-
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
fetchData();
|
|
164
|
-
}, [fetchData]);
|
|
165
|
-
|
|
166
|
-
// Refetch interval
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
if (!params.refetchInterval || params.refetchInterval <= 0) return;
|
|
169
|
-
const interval = setInterval(fetchData, params.refetchInterval);
|
|
170
|
-
return () => clearInterval(interval);
|
|
171
|
-
}, [fetchData, params.refetchInterval]);
|
|
172
|
-
|
|
173
|
-
const hasMore = total !== undefined && data.length < total;
|
|
174
|
-
|
|
175
|
-
const result: QueryResult<any> = {
|
|
176
|
-
data,
|
|
177
|
-
loading,
|
|
178
|
-
error,
|
|
179
|
-
refetch: fetchData,
|
|
180
|
-
total,
|
|
181
|
-
hasMore,
|
|
182
|
-
};
|
|
183
|
-
return result;
|
|
184
|
-
}
|