@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,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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }