@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmapp/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "React integration for the MindMatrix Player — hooks, components, and WebSocket bridge for browser-side workflow engines",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
"dev": "tsup src/index.ts --format cjs,esm --dts --external react --watch",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest --watch",
|
|
25
|
-
"type-check": "tsc --noEmit"
|
|
25
|
+
"type-check": "tsc --noEmit",
|
|
26
|
+
"publish:alpha": "cd ../.. && ./scripts/publish-npm.sh"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
28
29
|
"react": ">=18.0.0",
|
|
29
30
|
"@tanstack/react-query": ">=5.0.0"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@mmapp/player-core": "
|
|
33
|
+
"@mmapp/player-core": "workspace:*"
|
|
33
34
|
},
|
|
34
35
|
"publishConfig": {
|
|
35
36
|
"access": "public"
|
package/package.json.backup
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mindmatrix/react",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"description": "React integration for the MindMatrix Player — hooks, components, and WebSocket bridge for browser-side workflow engines",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
},
|
|
14
|
-
"./atoms": {
|
|
15
|
-
"types": "./dist/atoms/index.d.ts",
|
|
16
|
-
"import": "./dist/atoms/index.mjs",
|
|
17
|
-
"require": "./dist/atoms/index.js"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --external react",
|
|
22
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --external react --watch",
|
|
23
|
-
"test": "vitest run",
|
|
24
|
-
"test:watch": "vitest --watch",
|
|
25
|
-
"type-check": "tsc --noEmit"
|
|
26
|
-
},
|
|
27
|
-
"peerDependencies": {
|
|
28
|
-
"react": ">=18.0.0",
|
|
29
|
-
"@tanstack/react-query": ">=5.0.0"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@mindmatrix/player-core": "workspace:*"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/react": "^19.0.0",
|
|
36
|
-
"react": "^19.0.0",
|
|
37
|
-
"tsup": "^8.0.0",
|
|
38
|
-
"typescript": "^5.4.0",
|
|
39
|
-
"vitest": "^1.5.0"
|
|
40
|
-
}
|
|
41
|
-
}
|
package/src/Blueprint.ts
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Blueprint — base class for declarative workflow module authoring.
|
|
3
|
-
*
|
|
4
|
-
* Users extend this class and override define() to declare models,
|
|
5
|
-
* views, server actions, and sub-modules using a clean imperative API.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { Blueprint, field, state, transition } from '@mindmatrix/react';
|
|
10
|
-
*
|
|
11
|
-
* class AuthModule extends Blueprint {
|
|
12
|
-
* slug = 'mod-authentication';
|
|
13
|
-
* version = '1.0.0';
|
|
14
|
-
* category = 'module';
|
|
15
|
-
*
|
|
16
|
-
* define() {
|
|
17
|
-
* this.model('authentication', m => m
|
|
18
|
-
* .field('email', field.email().required())
|
|
19
|
-
* .state('unauthenticated', state.initial())
|
|
20
|
-
* .state('authenticated')
|
|
21
|
-
* .transition('login', transition.from('unauthenticated').to('authenticated'))
|
|
22
|
-
* );
|
|
23
|
-
* this.view('login', { route: '/login', component: 'LoginForm' });
|
|
24
|
-
* this.serverAction('authenticate', { handler: 'actions/auth.server.ts' });
|
|
25
|
-
* }
|
|
26
|
-
* }
|
|
27
|
-
*
|
|
28
|
-
* export default new AuthModule().build();
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
import {
|
|
33
|
-
type ModelDefinition,
|
|
34
|
-
} from './config/defineModel';
|
|
35
|
-
import { ModelBuilder } from './builders';
|
|
36
|
-
import { defineBlueprint, type BlueprintConfig, type BlueprintManifest } from './config/defineBlueprint';
|
|
37
|
-
|
|
38
|
-
// =============================================================================
|
|
39
|
-
// Types
|
|
40
|
-
// =============================================================================
|
|
41
|
-
|
|
42
|
-
/** A view declaration within a blueprint. */
|
|
43
|
-
export interface BlueprintViewDeclaration {
|
|
44
|
-
/** Route path (e.g., '/login'). */
|
|
45
|
-
route?: string;
|
|
46
|
-
/** Component reference (file path or component name). */
|
|
47
|
-
component: string;
|
|
48
|
-
/** Route guard expression. */
|
|
49
|
-
guard?: string;
|
|
50
|
-
/** Label for navigation. */
|
|
51
|
-
label?: string;
|
|
52
|
-
/** Route group. */
|
|
53
|
-
group?: string;
|
|
54
|
-
/** Icon name. */
|
|
55
|
-
icon?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** A server action declaration within a blueprint. */
|
|
59
|
-
export interface BlueprintServerActionDeclaration {
|
|
60
|
-
/** Handler file path. */
|
|
61
|
-
handler: string;
|
|
62
|
-
/** Function name in the handler file. */
|
|
63
|
-
functionName?: string;
|
|
64
|
-
/** Description. */
|
|
65
|
-
description?: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// =============================================================================
|
|
69
|
-
// Blueprint Base Class
|
|
70
|
-
// =============================================================================
|
|
71
|
-
|
|
72
|
-
export abstract class Blueprint {
|
|
73
|
-
/** Unique identifier (kebab-case). Must be set by subclass. */
|
|
74
|
-
abstract slug: string;
|
|
75
|
-
/** Semantic version. Must be set by subclass. */
|
|
76
|
-
abstract version: string;
|
|
77
|
-
|
|
78
|
-
/** Display name (defaults to slug). */
|
|
79
|
-
name?: string;
|
|
80
|
-
/** Description. */
|
|
81
|
-
description?: string;
|
|
82
|
-
/** Category or categories. */
|
|
83
|
-
category?: string | string[];
|
|
84
|
-
/** Author. */
|
|
85
|
-
author?: string;
|
|
86
|
-
/** Tags. */
|
|
87
|
-
tags?: string[];
|
|
88
|
-
|
|
89
|
-
// Internal collections populated by define()
|
|
90
|
-
private _models = new Map<string, ModelDefinition>();
|
|
91
|
-
private _views = new Map<string, BlueprintViewDeclaration>();
|
|
92
|
-
private _serverActions = new Map<string, BlueprintServerActionDeclaration>();
|
|
93
|
-
private _dependencies: { slug: string; version?: string }[] = [];
|
|
94
|
-
private _capabilities: string[] = [];
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Override this method to declare models, views, actions, etc.
|
|
98
|
-
* Called automatically by build().
|
|
99
|
-
*/
|
|
100
|
-
abstract define(): void;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Declare a model within this blueprint.
|
|
104
|
-
*
|
|
105
|
-
* @param name - Model name (will be prefixed with blueprint slug)
|
|
106
|
-
* @param builderFn - Callback that configures a ModelBuilder
|
|
107
|
-
*/
|
|
108
|
-
protected model(
|
|
109
|
-
name: string,
|
|
110
|
-
builderFn: (m: ModelBuilder) => ModelBuilder,
|
|
111
|
-
): void {
|
|
112
|
-
const slug = `${this.slug}-${name}`;
|
|
113
|
-
const builder = new ModelBuilder(slug);
|
|
114
|
-
const configured = builderFn(builder);
|
|
115
|
-
this._models.set(name, configured.build());
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Declare a view/page within this blueprint.
|
|
120
|
-
*
|
|
121
|
-
* @param name - View name
|
|
122
|
-
* @param declaration - Route, component, guard, etc.
|
|
123
|
-
*/
|
|
124
|
-
protected view(name: string, declaration: BlueprintViewDeclaration): void {
|
|
125
|
-
this._views.set(name, declaration);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Declare a server action within this blueprint.
|
|
130
|
-
*
|
|
131
|
-
* @param name - Action name (becomes server:{name})
|
|
132
|
-
* @param declaration - Handler file and function
|
|
133
|
-
*/
|
|
134
|
-
protected serverAction(name: string, declaration: BlueprintServerActionDeclaration): void {
|
|
135
|
-
this._serverActions.set(name, declaration);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Declare a dependency on another module/blueprint.
|
|
140
|
-
*/
|
|
141
|
-
protected dependency(slug: string, version?: string): void {
|
|
142
|
-
this._dependencies.push({ slug, version });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Declare a required capability.
|
|
147
|
-
*/
|
|
148
|
-
protected capability(...caps: string[]): void {
|
|
149
|
-
this._capabilities.push(...caps);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Build the blueprint. Calls define(), collects all declarations,
|
|
154
|
-
* and returns a BlueprintManifest suitable for defineBlueprint().
|
|
155
|
-
*/
|
|
156
|
-
build(): BlueprintManifest {
|
|
157
|
-
// Call user's define() to populate collections
|
|
158
|
-
this._models.clear();
|
|
159
|
-
this._views.clear();
|
|
160
|
-
this._serverActions.clear();
|
|
161
|
-
this._dependencies = [];
|
|
162
|
-
this._capabilities = [];
|
|
163
|
-
this.define();
|
|
164
|
-
|
|
165
|
-
// Build blueprint config
|
|
166
|
-
const config: BlueprintConfig = {
|
|
167
|
-
slug: this.slug,
|
|
168
|
-
name: this.name || this.slug,
|
|
169
|
-
version: this.version,
|
|
170
|
-
description: this.description,
|
|
171
|
-
author: this.author,
|
|
172
|
-
tags: this.tags,
|
|
173
|
-
category: this.category || 'blueprint',
|
|
174
|
-
models: Array.from(this._models.keys()).map(k => `models/${k}`),
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Routes from views
|
|
178
|
-
if (this._views.size > 0) {
|
|
179
|
-
config.routes = Array.from(this._views.entries()).map(([name, v]) => ({
|
|
180
|
-
path: v.route || `/${name}`,
|
|
181
|
-
view: `app/${name}`,
|
|
182
|
-
label: v.label,
|
|
183
|
-
guard: v.guard,
|
|
184
|
-
group: v.group,
|
|
185
|
-
icon: v.icon,
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Actions from server actions
|
|
190
|
-
if (this._serverActions.size > 0) {
|
|
191
|
-
config.actions = Array.from(this._serverActions.entries()).map(([name, a]) => ({
|
|
192
|
-
id: `server:${name}`,
|
|
193
|
-
handler: a.handler,
|
|
194
|
-
functionName: a.functionName || name,
|
|
195
|
-
description: a.description,
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (this._dependencies.length > 0) {
|
|
200
|
-
config.dependencies = this._dependencies;
|
|
201
|
-
}
|
|
202
|
-
if (this._capabilities.length > 0) {
|
|
203
|
-
config.capabilities = this._capabilities;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return defineBlueprint(config);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get all model definitions declared by this blueprint.
|
|
211
|
-
*/
|
|
212
|
-
getModels(): Map<string, ModelDefinition> {
|
|
213
|
-
if (this._models.size === 0) this.define();
|
|
214
|
-
return new Map(this._models);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Blueprint } from '../Blueprint';
|
|
3
|
-
import { field, state, transition } from '../builders';
|
|
4
|
-
|
|
5
|
-
class TestModule extends Blueprint {
|
|
6
|
-
slug = 'test-module';
|
|
7
|
-
version = '1.0.0';
|
|
8
|
-
name = 'Test Module';
|
|
9
|
-
category = 'module';
|
|
10
|
-
|
|
11
|
-
define() {
|
|
12
|
-
this.model('user', m => m
|
|
13
|
-
.field('email', field.email().required())
|
|
14
|
-
.field('name', field.string())
|
|
15
|
-
.state('active', state.initial())
|
|
16
|
-
.state('inactive', state.end())
|
|
17
|
-
.transition('deactivate', transition.from('active').to('inactive'))
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
this.view('login', { route: '/login', component: 'LoginForm', label: 'Login' });
|
|
21
|
-
this.view('profile', { route: '/profile', component: 'Profile', guard: 'context.actor_id != null' });
|
|
22
|
-
|
|
23
|
-
this.serverAction('authenticate', { handler: 'actions/auth.server.ts', description: 'Validate credentials' });
|
|
24
|
-
|
|
25
|
-
this.dependency('other-module', '>=1.0.0');
|
|
26
|
-
this.capability('email');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('Blueprint', () => {
|
|
31
|
-
it('builds a valid BlueprintManifest', () => {
|
|
32
|
-
const module = new TestModule();
|
|
33
|
-
const manifest = module.build();
|
|
34
|
-
|
|
35
|
-
expect(manifest.slug).toBe('test-module');
|
|
36
|
-
expect(manifest.version).toBe('1.0.0');
|
|
37
|
-
expect(manifest.name).toBe('Test Module');
|
|
38
|
-
expect(manifest.category).toBe('module');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('collects model declarations', () => {
|
|
42
|
-
const module = new TestModule();
|
|
43
|
-
const manifest = module.build();
|
|
44
|
-
|
|
45
|
-
expect(manifest.models).toEqual(['models/user']);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('collects view declarations as routes', () => {
|
|
49
|
-
const module = new TestModule();
|
|
50
|
-
const manifest = module.build();
|
|
51
|
-
|
|
52
|
-
expect(manifest.routes).toHaveLength(2);
|
|
53
|
-
expect(manifest.routes![0].path).toBe('/login');
|
|
54
|
-
expect(manifest.routes![0].label).toBe('Login');
|
|
55
|
-
expect(manifest.routes![1].path).toBe('/profile');
|
|
56
|
-
expect(manifest.routes![1].guard).toBe('context.actor_id != null');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('collects server action declarations', () => {
|
|
60
|
-
const module = new TestModule();
|
|
61
|
-
const manifest = module.build();
|
|
62
|
-
|
|
63
|
-
expect(manifest.actions).toHaveLength(1);
|
|
64
|
-
expect(manifest.actions![0].id).toBe('server:authenticate');
|
|
65
|
-
expect(manifest.actions![0].handler).toBe('actions/auth.server.ts');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('collects dependencies and capabilities', () => {
|
|
69
|
-
const module = new TestModule();
|
|
70
|
-
const manifest = module.build();
|
|
71
|
-
|
|
72
|
-
expect(manifest.dependencies).toHaveLength(1);
|
|
73
|
-
expect(manifest.dependencies![0].slug).toBe('other-module');
|
|
74
|
-
expect(manifest.capabilities).toEqual(['email']);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('provides model definitions via getModels()', () => {
|
|
78
|
-
const module = new TestModule();
|
|
79
|
-
const models = module.getModels();
|
|
80
|
-
|
|
81
|
-
expect(models.size).toBe(1);
|
|
82
|
-
const userModel = models.get('user')!;
|
|
83
|
-
expect(userModel.slug).toBe('test-module-user');
|
|
84
|
-
expect(Object.keys(userModel.fields)).toContain('email');
|
|
85
|
-
expect(Object.keys(userModel.states)).toContain('active');
|
|
86
|
-
expect(Object.keys(userModel.transitions)).toContain('deactivate');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('resets state on each build() call', () => {
|
|
90
|
-
const module = new TestModule();
|
|
91
|
-
const m1 = module.build();
|
|
92
|
-
const m2 = module.build();
|
|
93
|
-
|
|
94
|
-
// Should not accumulate
|
|
95
|
-
expect(m1.routes).toHaveLength(2);
|
|
96
|
-
expect(m2.routes).toHaveLength(2);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('applies default mode and runtime', () => {
|
|
100
|
-
const module = new TestModule();
|
|
101
|
-
const manifest = module.build();
|
|
102
|
-
|
|
103
|
-
expect(manifest.mode).toBe('infer');
|
|
104
|
-
expect(manifest.defaultRuntime).toBe('local');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { model, state, field, transition } from '../builders';
|
|
3
|
-
import type { ActionHandler } from '../builders';
|
|
4
|
-
import { setField } from '../actions';
|
|
5
|
-
|
|
6
|
-
describe('Inline action bodies — ActionContext handlers', () => {
|
|
7
|
-
describe('StateBuilder.onEnter with handler', () => {
|
|
8
|
-
it('stores handler as inline_handler action', () => {
|
|
9
|
-
const handler: ActionHandler = async (ctx) => {
|
|
10
|
-
ctx.setField('activatedAt', new Date().toISOString());
|
|
11
|
-
ctx.log('User activated');
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const result = model('test')
|
|
15
|
-
.state('active', state.initial().onEnter(handler))
|
|
16
|
-
.state('done', state.end())
|
|
17
|
-
.transition('finish', transition.from('active').to('done'))
|
|
18
|
-
.build();
|
|
19
|
-
|
|
20
|
-
const actions = result.states.active.onEnter!;
|
|
21
|
-
expect(actions).toHaveLength(1);
|
|
22
|
-
expect(actions[0].type).toBe('inline_handler');
|
|
23
|
-
expect(actions[0].id).toMatch(/^inline-on-enter-/);
|
|
24
|
-
expect(actions[0].config!.handler).toContain('setField');
|
|
25
|
-
expect(actions[0].config!.handler).toContain('activatedAt');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('still accepts regular ActionDefinition array', () => {
|
|
29
|
-
const result = model('test')
|
|
30
|
-
.state('active', state.initial().onEnter(
|
|
31
|
-
setField('status', '"active"'),
|
|
32
|
-
))
|
|
33
|
-
.state('done', state.end())
|
|
34
|
-
.transition('finish', transition.from('active').to('done'))
|
|
35
|
-
.build();
|
|
36
|
-
|
|
37
|
-
const actions = result.states.active.onEnter!;
|
|
38
|
-
expect(actions).toHaveLength(1);
|
|
39
|
-
expect(actions[0].type).toBe('set_field');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('StateBuilder.onExit with handler', () => {
|
|
44
|
-
it('stores handler as inline_handler action', () => {
|
|
45
|
-
const handler: ActionHandler = (ctx) => {
|
|
46
|
-
ctx.log('Leaving state');
|
|
47
|
-
ctx.setFields({ cleanedUp: true });
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const result = model('test')
|
|
51
|
-
.state('active', state.initial().onExit(handler))
|
|
52
|
-
.state('done', state.end())
|
|
53
|
-
.transition('finish', transition.from('active').to('done'))
|
|
54
|
-
.build();
|
|
55
|
-
|
|
56
|
-
const actions = result.states.active.onExit!;
|
|
57
|
-
expect(actions).toHaveLength(1);
|
|
58
|
-
expect(actions[0].type).toBe('inline_handler');
|
|
59
|
-
expect(actions[0].id).toMatch(/^inline-on-exit-/);
|
|
60
|
-
expect(actions[0].config!.handler).toContain('cleanedUp');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('TransitionBuilder.do with handler', () => {
|
|
65
|
-
it('stores handler as inline_handler action on transition', () => {
|
|
66
|
-
const handler: ActionHandler = async (ctx) => {
|
|
67
|
-
ctx.setField('approvedAt', new Date().toISOString());
|
|
68
|
-
await ctx.notify('requester', 'Your request was approved');
|
|
69
|
-
await ctx.serverAction('send_email', { template: 'approved' });
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const result = model('test')
|
|
73
|
-
.state('review', state.initial())
|
|
74
|
-
.state('approved', state.end())
|
|
75
|
-
.transition('approve', transition.from('review').to('approved').do(handler))
|
|
76
|
-
.build();
|
|
77
|
-
|
|
78
|
-
const actions = result.transitions.approve.actions!;
|
|
79
|
-
expect(actions).toHaveLength(1);
|
|
80
|
-
expect(actions[0].type).toBe('inline_handler');
|
|
81
|
-
expect(actions[0].config!.handler).toContain('notify');
|
|
82
|
-
expect(actions[0].config!.handler).toContain('serverAction');
|
|
83
|
-
expect(actions[0].config!.handler).toContain('send_email');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('can mix handler with regular actions', () => {
|
|
87
|
-
const result = model('test')
|
|
88
|
-
.state('a', state.initial())
|
|
89
|
-
.state('b', state.end())
|
|
90
|
-
.transition('go', transition.from('a').to('b')
|
|
91
|
-
.do(setField('step', '"1"'))
|
|
92
|
-
.do(async (ctx) => { ctx.log('step 2'); })
|
|
93
|
-
)
|
|
94
|
-
.build();
|
|
95
|
-
|
|
96
|
-
const actions = result.transitions.go.actions!;
|
|
97
|
-
expect(actions).toHaveLength(2);
|
|
98
|
-
expect(actions[0].type).toBe('set_field');
|
|
99
|
-
expect(actions[1].type).toBe('inline_handler');
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('TypedTransitionBuilder.do with handler', () => {
|
|
104
|
-
it('stores handler via typed callback form', () => {
|
|
105
|
-
const result = model('test')
|
|
106
|
-
.state('draft', state.initial())
|
|
107
|
-
.state('published', state.end())
|
|
108
|
-
.transition('publish', t =>
|
|
109
|
-
t.from('draft').to('published').do(async (ctx) => {
|
|
110
|
-
ctx.setField('publishedAt', new Date().toISOString());
|
|
111
|
-
await ctx.spawn('notification', { type: 'published' });
|
|
112
|
-
})
|
|
113
|
-
)
|
|
114
|
-
.build();
|
|
115
|
-
|
|
116
|
-
const actions = result.transitions.publish.actions!;
|
|
117
|
-
expect(actions).toHaveLength(1);
|
|
118
|
-
expect(actions[0].type).toBe('inline_handler');
|
|
119
|
-
expect(actions[0].config!.handler).toContain('spawn');
|
|
120
|
-
expect(actions[0].config!.handler).toContain('publishedAt');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('Handler function serialization', () => {
|
|
125
|
-
it('serializes arrow functions', () => {
|
|
126
|
-
const result = model('test')
|
|
127
|
-
.state('a', state.initial().onEnter((ctx) => { ctx.log('hello'); }))
|
|
128
|
-
.state('b', state.end())
|
|
129
|
-
.transition('go', transition.from('a').to('b'))
|
|
130
|
-
.build();
|
|
131
|
-
|
|
132
|
-
const handler = result.states.a.onEnter![0].config!.handler as string;
|
|
133
|
-
expect(handler).toContain('log');
|
|
134
|
-
expect(handler).toContain('hello');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('serializes async arrow functions', () => {
|
|
138
|
-
const result = model('test')
|
|
139
|
-
.state('a', state.initial().onEnter(async (ctx) => {
|
|
140
|
-
await ctx.notify('user', 'hi');
|
|
141
|
-
}))
|
|
142
|
-
.state('b', state.end())
|
|
143
|
-
.transition('go', transition.from('a').to('b'))
|
|
144
|
-
.build();
|
|
145
|
-
|
|
146
|
-
const handler = result.states.a.onEnter![0].config!.handler as string;
|
|
147
|
-
expect(handler).toContain('notify');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('each handler gets a unique id', () => {
|
|
151
|
-
const result = model('test')
|
|
152
|
-
.state('a', state.initial()
|
|
153
|
-
.onEnter((ctx) => { ctx.log('enter'); })
|
|
154
|
-
)
|
|
155
|
-
.state('b', state.end())
|
|
156
|
-
.transition('go', transition.from('a').to('b')
|
|
157
|
-
.do((ctx) => { ctx.log('transition'); })
|
|
158
|
-
)
|
|
159
|
-
.build();
|
|
160
|
-
|
|
161
|
-
const enterId = result.states.a.onEnter![0].id;
|
|
162
|
-
const transId = result.transitions.go.actions![0].id;
|
|
163
|
-
expect(enterId).not.toBe(transId);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
});
|