@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/useModule.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useMutation.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useOnChange.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useOnEnter.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useOnEvent.ts
DELETED
|
@@ -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
|
-
}
|
package/src/hooks/useOnExit.ts
DELETED
|
@@ -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
|
-
}
|