@mmapp/react 0.1.0-alpha.1 → 0.1.0-alpha.4
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 +1378 -94
- package/dist/index.d.ts +1378 -94
- package/dist/index.js +1094 -1309
- package/dist/index.mjs +1038 -1296
- 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/useRouter.ts
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useRouter — Client-side routing with nested routes, dynamic params, and route guards.
|
|
3
|
-
*
|
|
4
|
-
* Provides Next.js-style routing within workflow applications.
|
|
5
|
-
* Supports dynamic segments (/ride/:id), nested routes, query params,
|
|
6
|
-
* and guard functions for auth/permission checks before navigation.
|
|
7
|
-
*
|
|
8
|
-
* Usage in .workflow.tsx:
|
|
9
|
-
* const router = useRouter();
|
|
10
|
-
* router.push('/rides/abc123');
|
|
11
|
-
* router.push('/rides/abc123?tab=details');
|
|
12
|
-
*
|
|
13
|
-
* // With guard
|
|
14
|
-
* const router = useRouter({
|
|
15
|
-
* guards: [requireAuth, requireRole('driver')],
|
|
16
|
-
* });
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// Types
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
/** Route definition for configuring application routes. */
|
|
26
|
-
export interface RouteDefinition {
|
|
27
|
-
/** URL path pattern (supports :param dynamic segments). */
|
|
28
|
-
path: string;
|
|
29
|
-
/** Route guard functions — must all return true to allow navigation. */
|
|
30
|
-
guards?: RouteGuard[];
|
|
31
|
-
/** Redirect path if guards fail. */
|
|
32
|
-
redirectOnFail?: string;
|
|
33
|
-
/** Nested child routes. */
|
|
34
|
-
children?: RouteDefinition[];
|
|
35
|
-
/** Metadata attached to the route. */
|
|
36
|
-
meta?: Record<string, unknown>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Guard function that determines if navigation is allowed. */
|
|
40
|
-
export type RouteGuard = (to: RouteLocation, from: RouteLocation | null) => boolean | Promise<boolean>;
|
|
41
|
-
|
|
42
|
-
/** Represents a parsed route location. */
|
|
43
|
-
export interface RouteLocation {
|
|
44
|
-
/** The full pathname (e.g., '/rides/abc123'). */
|
|
45
|
-
pathname: string;
|
|
46
|
-
/** Parsed dynamic params (e.g., { id: 'abc123' }). */
|
|
47
|
-
params: Record<string, string>;
|
|
48
|
-
/** Parsed query string params. */
|
|
49
|
-
query: Record<string, string>;
|
|
50
|
-
/** URL hash fragment (without #). */
|
|
51
|
-
hash: string;
|
|
52
|
-
/** The matched route definition, if any. */
|
|
53
|
-
matched?: RouteDefinition;
|
|
54
|
-
/** Route metadata from matched definition. */
|
|
55
|
-
meta: Record<string, unknown>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Options for configuring the router. */
|
|
59
|
-
export interface RouterOptions {
|
|
60
|
-
/** Route definitions for matching and guards. */
|
|
61
|
-
routes?: RouteDefinition[];
|
|
62
|
-
/** Global guards applied to every navigation. */
|
|
63
|
-
guards?: RouteGuard[];
|
|
64
|
-
/** Called when a guard blocks navigation. */
|
|
65
|
-
onGuardReject?: (to: RouteLocation) => void;
|
|
66
|
-
/** Base path prefix (e.g., '/app'). */
|
|
67
|
-
basePath?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Router handle returned by useRouter. */
|
|
71
|
-
export interface RouterHandle {
|
|
72
|
-
/** Current route location. */
|
|
73
|
-
location: RouteLocation;
|
|
74
|
-
/** Current pathname. */
|
|
75
|
-
pathname: string;
|
|
76
|
-
/** Current dynamic params. */
|
|
77
|
-
params: Record<string, string>;
|
|
78
|
-
/** Current query params. */
|
|
79
|
-
query: Record<string, string>;
|
|
80
|
-
/** Current hash. */
|
|
81
|
-
hash: string;
|
|
82
|
-
/** Navigate to a new path (pushState). */
|
|
83
|
-
push: (path: string) => Promise<boolean>;
|
|
84
|
-
/** Replace current path (replaceState). */
|
|
85
|
-
replace: (path: string) => Promise<boolean>;
|
|
86
|
-
/** Go back in history. */
|
|
87
|
-
back: () => void;
|
|
88
|
-
/** Go forward in history. */
|
|
89
|
-
forward: () => void;
|
|
90
|
-
/** Check if a path matches a pattern. */
|
|
91
|
-
isActive: (pattern: string) => boolean;
|
|
92
|
-
/** Whether a navigation is in progress (guards running). */
|
|
93
|
-
isNavigating: boolean;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// =============================================================================
|
|
97
|
-
// Internal helpers
|
|
98
|
-
// =============================================================================
|
|
99
|
-
|
|
100
|
-
/** Parse a URL path into segments. */
|
|
101
|
-
function parsePath(fullPath: string, basePath: string): { pathname: string; query: Record<string, string>; hash: string } {
|
|
102
|
-
let path = fullPath;
|
|
103
|
-
|
|
104
|
-
// Strip base path prefix
|
|
105
|
-
if (basePath && path.startsWith(basePath)) {
|
|
106
|
-
path = path.slice(basePath.length) || '/';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Extract hash
|
|
110
|
-
const hashIdx = path.indexOf('#');
|
|
111
|
-
let hash = '';
|
|
112
|
-
if (hashIdx !== -1) {
|
|
113
|
-
hash = path.slice(hashIdx + 1);
|
|
114
|
-
path = path.slice(0, hashIdx);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Extract query
|
|
118
|
-
const queryIdx = path.indexOf('?');
|
|
119
|
-
const query: Record<string, string> = {};
|
|
120
|
-
if (queryIdx !== -1) {
|
|
121
|
-
const qs = path.slice(queryIdx + 1);
|
|
122
|
-
path = path.slice(0, queryIdx);
|
|
123
|
-
for (const pair of qs.split('&')) {
|
|
124
|
-
const eqIdx = pair.indexOf('=');
|
|
125
|
-
if (eqIdx !== -1) {
|
|
126
|
-
query[decodeURIComponent(pair.slice(0, eqIdx))] = decodeURIComponent(pair.slice(eqIdx + 1));
|
|
127
|
-
} else if (pair) {
|
|
128
|
-
query[decodeURIComponent(pair)] = '';
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { pathname: path || '/', query, hash };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Match a path against a route pattern, extracting dynamic params. */
|
|
137
|
-
function matchRoute(
|
|
138
|
-
pathname: string,
|
|
139
|
-
pattern: string,
|
|
140
|
-
): { match: boolean; params: Record<string, string> } {
|
|
141
|
-
const pathParts = pathname.split('/').filter(Boolean);
|
|
142
|
-
const patternParts = pattern.split('/').filter(Boolean);
|
|
143
|
-
|
|
144
|
-
// Wildcard catch-all
|
|
145
|
-
if (patternParts[patternParts.length - 1] === '*') {
|
|
146
|
-
const staticParts = patternParts.slice(0, -1);
|
|
147
|
-
if (pathParts.length < staticParts.length) return { match: false, params: {} };
|
|
148
|
-
const params: Record<string, string> = {};
|
|
149
|
-
for (let i = 0; i < staticParts.length; i++) {
|
|
150
|
-
const pp = staticParts[i];
|
|
151
|
-
if (pp.startsWith(':')) {
|
|
152
|
-
params[pp.slice(1)] = pathParts[i];
|
|
153
|
-
} else if (pp !== pathParts[i]) {
|
|
154
|
-
return { match: false, params: {} };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
params['*'] = pathParts.slice(staticParts.length).join('/');
|
|
158
|
-
return { match: true, params };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (pathParts.length !== patternParts.length) return { match: false, params: {} };
|
|
162
|
-
|
|
163
|
-
const params: Record<string, string> = {};
|
|
164
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
165
|
-
const pp = patternParts[i];
|
|
166
|
-
if (pp.startsWith(':')) {
|
|
167
|
-
params[pp.slice(1)] = pathParts[i];
|
|
168
|
-
} else if (pp !== pathParts[i]) {
|
|
169
|
-
return { match: false, params: {} };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { match: true, params };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Find a matching route definition (supports nested routes). */
|
|
177
|
-
function findRoute(
|
|
178
|
-
pathname: string,
|
|
179
|
-
routes: RouteDefinition[],
|
|
180
|
-
parentPath: string = '',
|
|
181
|
-
): { route: RouteDefinition; params: Record<string, string> } | null {
|
|
182
|
-
for (const route of routes) {
|
|
183
|
-
const fullPattern = parentPath + route.path;
|
|
184
|
-
const result = matchRoute(pathname, fullPattern);
|
|
185
|
-
|
|
186
|
-
if (result.match) {
|
|
187
|
-
return { route, params: result.params };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Check children
|
|
191
|
-
if (route.children) {
|
|
192
|
-
const childResult = findRoute(pathname, route.children, fullPattern);
|
|
193
|
-
if (childResult) return childResult;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** Build a RouteLocation from current browser state. */
|
|
201
|
-
function buildLocation(basePath: string, routes: RouteDefinition[]): RouteLocation {
|
|
202
|
-
const { pathname, query, hash } = parsePath(
|
|
203
|
-
typeof window !== 'undefined' ? window.location.pathname + window.location.search + window.location.hash : '/',
|
|
204
|
-
basePath,
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
const found = findRoute(pathname, routes);
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
pathname,
|
|
211
|
-
params: found?.params ?? {},
|
|
212
|
-
query,
|
|
213
|
-
hash,
|
|
214
|
-
matched: found?.route,
|
|
215
|
-
meta: found?.route?.meta ?? {},
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// =============================================================================
|
|
220
|
-
// Hook
|
|
221
|
-
// =============================================================================
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Client-side router with nested routes, dynamic params, and guards.
|
|
225
|
-
*
|
|
226
|
-
* @param options - Router configuration (routes, guards, basePath).
|
|
227
|
-
* @returns Router handle with navigation methods and current location.
|
|
228
|
-
*/
|
|
229
|
-
export function useRouter(options: RouterOptions = {}): RouterHandle {
|
|
230
|
-
const { routes = [], guards: globalGuards = [], onGuardReject, basePath = '' } = options;
|
|
231
|
-
|
|
232
|
-
const routesRef = useRef(routes);
|
|
233
|
-
routesRef.current = routes;
|
|
234
|
-
const guardsRef = useRef(globalGuards);
|
|
235
|
-
guardsRef.current = globalGuards;
|
|
236
|
-
const onRejectRef = useRef(onGuardReject);
|
|
237
|
-
onRejectRef.current = onGuardReject;
|
|
238
|
-
const basePathRef = useRef(basePath);
|
|
239
|
-
basePathRef.current = basePath;
|
|
240
|
-
|
|
241
|
-
const [location, setLocation] = useState<RouteLocation>(() =>
|
|
242
|
-
buildLocation(basePath, routes),
|
|
243
|
-
);
|
|
244
|
-
const [isNavigating, setIsNavigating] = useState(false);
|
|
245
|
-
|
|
246
|
-
// Listen to popstate (back/forward)
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
const handler = () => {
|
|
249
|
-
setLocation(buildLocation(basePathRef.current, routesRef.current));
|
|
250
|
-
};
|
|
251
|
-
window.addEventListener('popstate', handler);
|
|
252
|
-
return () => window.removeEventListener('popstate', handler);
|
|
253
|
-
}, []);
|
|
254
|
-
|
|
255
|
-
const navigate = useCallback(
|
|
256
|
-
async (path: string, replace: boolean): Promise<boolean> => {
|
|
257
|
-
const { pathname, query, hash } = parsePath(path, basePathRef.current);
|
|
258
|
-
const found = findRoute(pathname, routesRef.current);
|
|
259
|
-
|
|
260
|
-
const to: RouteLocation = {
|
|
261
|
-
pathname,
|
|
262
|
-
params: found?.params ?? {},
|
|
263
|
-
query,
|
|
264
|
-
hash,
|
|
265
|
-
matched: found?.route,
|
|
266
|
-
meta: found?.route?.meta ?? {},
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
// Run guards
|
|
270
|
-
setIsNavigating(true);
|
|
271
|
-
try {
|
|
272
|
-
// Global guards
|
|
273
|
-
for (const guard of guardsRef.current) {
|
|
274
|
-
const allowed = await guard(to, location);
|
|
275
|
-
if (!allowed) {
|
|
276
|
-
onRejectRef.current?.(to);
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Route-specific guards
|
|
282
|
-
if (found?.route?.guards) {
|
|
283
|
-
for (const guard of found.route.guards) {
|
|
284
|
-
const allowed = await guard(to, location);
|
|
285
|
-
if (!allowed) {
|
|
286
|
-
if (found.route.redirectOnFail) {
|
|
287
|
-
const fullRedirect = basePathRef.current + found.route.redirectOnFail;
|
|
288
|
-
window.history.pushState(null, '', fullRedirect);
|
|
289
|
-
setLocation(buildLocation(basePathRef.current, routesRef.current));
|
|
290
|
-
}
|
|
291
|
-
onRejectRef.current?.(to);
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Navigate
|
|
298
|
-
const fullPath = basePathRef.current + path;
|
|
299
|
-
if (replace) {
|
|
300
|
-
window.history.replaceState(null, '', fullPath);
|
|
301
|
-
} else {
|
|
302
|
-
window.history.pushState(null, '', fullPath);
|
|
303
|
-
}
|
|
304
|
-
setLocation(to);
|
|
305
|
-
return true;
|
|
306
|
-
} finally {
|
|
307
|
-
setIsNavigating(false);
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
[location],
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
const push = useCallback((path: string) => navigate(path, false), [navigate]);
|
|
314
|
-
const replace = useCallback((path: string) => navigate(path, true), [navigate]);
|
|
315
|
-
|
|
316
|
-
const back = useCallback(() => {
|
|
317
|
-
window.history.back();
|
|
318
|
-
}, []);
|
|
319
|
-
|
|
320
|
-
const forward = useCallback(() => {
|
|
321
|
-
window.history.forward();
|
|
322
|
-
}, []);
|
|
323
|
-
|
|
324
|
-
const isActive = useCallback(
|
|
325
|
-
(pattern: string) => {
|
|
326
|
-
return matchRoute(location.pathname, pattern).match;
|
|
327
|
-
},
|
|
328
|
-
[location.pathname],
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
return useMemo(
|
|
332
|
-
(): RouterHandle => ({
|
|
333
|
-
location,
|
|
334
|
-
pathname: location.pathname,
|
|
335
|
-
params: location.params,
|
|
336
|
-
query: location.query,
|
|
337
|
-
hash: location.hash,
|
|
338
|
-
push,
|
|
339
|
-
replace,
|
|
340
|
-
back,
|
|
341
|
-
forward,
|
|
342
|
-
isActive,
|
|
343
|
-
isNavigating,
|
|
344
|
-
}),
|
|
345
|
-
[location, push, replace, back, forward, isActive, isNavigating],
|
|
346
|
-
);
|
|
347
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useServerAction — invoke server-side workflow actions from .workflow.tsx.
|
|
3
|
-
*
|
|
4
|
-
* Registers a named action that POSTs to the Rust API for server-side execution.
|
|
5
|
-
* Handles loading, error, and optimistic state.
|
|
6
|
-
*
|
|
7
|
-
* Usage in .workflow.tsx:
|
|
8
|
-
* const approve = useServerAction('approve-order', {
|
|
9
|
-
* instanceId: orderId,
|
|
10
|
-
* onSuccess: () => refetch(),
|
|
11
|
-
* });
|
|
12
|
-
*
|
|
13
|
-
* <Button onClick={() => approve.execute({ comment: 'Looks good' })}>
|
|
14
|
-
* {approve.loading ? 'Approving...' : 'Approve'}
|
|
15
|
-
* </Button>
|
|
16
|
-
*
|
|
17
|
-
* The compiler extracts this as a server action dependency in the IR.
|
|
18
|
-
* At runtime, the hook POSTs to /api/v1/workflow/instances/{id}/actions/{name}.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { useState, useCallback, useRef } from 'react';
|
|
22
|
-
|
|
23
|
-
// =============================================================================
|
|
24
|
-
// Types
|
|
25
|
-
// =============================================================================
|
|
26
|
-
|
|
27
|
-
export interface ServerActionOptions {
|
|
28
|
-
/** The workflow instance ID to execute the action against. */
|
|
29
|
-
instanceId: string;
|
|
30
|
-
/** Optional definition ID — when present, uses the path-param endpoint. */
|
|
31
|
-
definitionId?: string;
|
|
32
|
-
/** Called after a successful action execution. */
|
|
33
|
-
onSuccess?: (result: ServerActionResult) => void;
|
|
34
|
-
/** Called after a failed action execution. */
|
|
35
|
-
onError?: (error: Error) => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface ServerActionResult {
|
|
39
|
-
success: boolean;
|
|
40
|
-
action_id: string;
|
|
41
|
-
action_type?: string;
|
|
42
|
-
triggered_by?: string;
|
|
43
|
-
data?: Record<string, unknown>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface ServerActionHandle {
|
|
47
|
-
/** Execute the server action with optional input payload. */
|
|
48
|
-
execute: (input?: Record<string, unknown>) => Promise<ServerActionResult>;
|
|
49
|
-
/** Whether the action is currently executing. */
|
|
50
|
-
loading: boolean;
|
|
51
|
-
/** Error from the last execution attempt. */
|
|
52
|
-
error: Error | null;
|
|
53
|
-
/** Result from the last successful execution. */
|
|
54
|
-
result: ServerActionResult | null;
|
|
55
|
-
/** Reset error and result state. */
|
|
56
|
-
reset: () => void;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Resolver interface — the runtime must provide an implementation.
|
|
61
|
-
* Typically set by WorkflowProvider or a top-level setup call.
|
|
62
|
-
*/
|
|
63
|
-
export interface ServerActionResolver {
|
|
64
|
-
executeAction: (
|
|
65
|
-
instanceId: string,
|
|
66
|
-
actionName: string,
|
|
67
|
-
input?: Record<string, unknown>,
|
|
68
|
-
definitionId?: string,
|
|
69
|
-
) => Promise<ServerActionResult>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Global resolver (set by provider)
|
|
73
|
-
let _globalServerActionResolver: ServerActionResolver | null = null;
|
|
74
|
-
|
|
75
|
-
export function setServerActionResolver(resolver: ServerActionResolver | null): void {
|
|
76
|
-
_globalServerActionResolver = resolver;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Default resolver that POSTs to the Rust API.
|
|
81
|
-
* Used when no custom resolver is configured.
|
|
82
|
-
*/
|
|
83
|
-
function getDefaultResolver(): ServerActionResolver {
|
|
84
|
-
return {
|
|
85
|
-
async executeAction(instanceId, actionName, input, definitionId) {
|
|
86
|
-
const token = typeof localStorage !== 'undefined'
|
|
87
|
-
? localStorage.getItem('auth_token')
|
|
88
|
-
: null;
|
|
89
|
-
|
|
90
|
-
// Use path-param endpoint when definitionId is provided
|
|
91
|
-
const url = definitionId
|
|
92
|
-
? `/api/v1/workflow/server-actions/${encodeURIComponent(definitionId)}/${encodeURIComponent(actionName)}`
|
|
93
|
-
: `/api/v1/workflow/instances/${encodeURIComponent(instanceId)}/actions/${encodeURIComponent(actionName)}`;
|
|
94
|
-
|
|
95
|
-
const body = definitionId
|
|
96
|
-
? JSON.stringify({ instance_id: instanceId || undefined, payload: input })
|
|
97
|
-
: input ? JSON.stringify(input) : undefined;
|
|
98
|
-
|
|
99
|
-
const res = await fetch(url, {
|
|
100
|
-
method: 'POST',
|
|
101
|
-
headers: {
|
|
102
|
-
'Content-Type': 'application/json',
|
|
103
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
104
|
-
},
|
|
105
|
-
body,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!res.ok) {
|
|
109
|
-
const errBody = await res.json().catch(() => ({ message: res.statusText }));
|
|
110
|
-
throw new Error(errBody.message || errBody.error || `Server action failed: ${res.status}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return res.json();
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// =============================================================================
|
|
119
|
-
// Hook
|
|
120
|
-
// =============================================================================
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Registers a server-side action and returns a callable handle.
|
|
124
|
-
*
|
|
125
|
-
* @param actionName - The action identifier (matches pending_actions or transition name).
|
|
126
|
-
* @param options - Instance ID and optional callbacks.
|
|
127
|
-
* @returns Handle with execute(), loading, error, and result.
|
|
128
|
-
*/
|
|
129
|
-
export function useServerAction(
|
|
130
|
-
actionName: string,
|
|
131
|
-
options: ServerActionOptions,
|
|
132
|
-
): ServerActionHandle {
|
|
133
|
-
const [loading, setLoading] = useState(false);
|
|
134
|
-
const [error, setError] = useState<Error | null>(null);
|
|
135
|
-
const [result, setResult] = useState<ServerActionResult | null>(null);
|
|
136
|
-
|
|
137
|
-
const optionsRef = useRef(options);
|
|
138
|
-
optionsRef.current = options;
|
|
139
|
-
|
|
140
|
-
const execute = useCallback(
|
|
141
|
-
async (input?: Record<string, unknown>): Promise<ServerActionResult> => {
|
|
142
|
-
const resolver = _globalServerActionResolver || getDefaultResolver();
|
|
143
|
-
const { instanceId, definitionId, onSuccess, onError } = optionsRef.current;
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
setLoading(true);
|
|
147
|
-
setError(null);
|
|
148
|
-
|
|
149
|
-
const actionResult = await resolver.executeAction(instanceId, actionName, input, definitionId);
|
|
150
|
-
setResult(actionResult);
|
|
151
|
-
onSuccess?.(actionResult);
|
|
152
|
-
return actionResult;
|
|
153
|
-
} catch (err) {
|
|
154
|
-
const e = err instanceof Error ? err : new Error(String(err));
|
|
155
|
-
setError(e);
|
|
156
|
-
onError?.(e);
|
|
157
|
-
throw e;
|
|
158
|
-
} finally {
|
|
159
|
-
setLoading(false);
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
[actionName],
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const reset = useCallback(() => {
|
|
166
|
-
setError(null);
|
|
167
|
-
setResult(null);
|
|
168
|
-
}, []);
|
|
169
|
-
|
|
170
|
-
const handle: ServerActionHandle = {
|
|
171
|
-
execute,
|
|
172
|
-
loading,
|
|
173
|
-
error,
|
|
174
|
-
result,
|
|
175
|
-
reset,
|
|
176
|
-
};
|
|
177
|
-
return handle;
|
|
178
|
-
}
|