@mmapp/player-core 0.1.0-alpha.1
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/dist/index.d.mts +1436 -0
- package/dist/index.d.ts +1436 -0
- package/dist/index.js +4828 -0
- package/dist/index.mjs +4762 -0
- package/package.json +35 -0
- package/package.json.backup +35 -0
- package/src/__tests__/actions.test.ts +187 -0
- package/src/__tests__/blueprint-e2e.test.ts +706 -0
- package/src/__tests__/blueprint-test-runner.test.ts +680 -0
- package/src/__tests__/core-functions.test.ts +78 -0
- package/src/__tests__/dsl-compiler.test.ts +1382 -0
- package/src/__tests__/dsl-grammar.test.ts +1682 -0
- package/src/__tests__/events.test.ts +200 -0
- package/src/__tests__/expression.test.ts +296 -0
- package/src/__tests__/failure-policies.test.ts +110 -0
- package/src/__tests__/frontend-context.test.ts +182 -0
- package/src/__tests__/integration.test.ts +256 -0
- package/src/__tests__/security.test.ts +190 -0
- package/src/__tests__/state-machine.test.ts +450 -0
- package/src/__tests__/testing-engine.test.ts +671 -0
- package/src/actions/dispatcher.ts +80 -0
- package/src/actions/index.ts +7 -0
- package/src/actions/types.ts +25 -0
- package/src/dsl/compiler/component-mapper.ts +289 -0
- package/src/dsl/compiler/field-mapper.ts +187 -0
- package/src/dsl/compiler/index.ts +82 -0
- package/src/dsl/compiler/manifest-compiler.ts +76 -0
- package/src/dsl/compiler/symbol-table.ts +214 -0
- package/src/dsl/compiler/utils.ts +48 -0
- package/src/dsl/compiler/view-compiler.ts +286 -0
- package/src/dsl/compiler/workflow-compiler.ts +600 -0
- package/src/dsl/index.ts +66 -0
- package/src/dsl/ir-migration.ts +221 -0
- package/src/dsl/ir-types.ts +416 -0
- package/src/dsl/lexer.ts +579 -0
- package/src/dsl/parser.ts +115 -0
- package/src/dsl/types.ts +256 -0
- package/src/events/event-bus.ts +68 -0
- package/src/events/index.ts +9 -0
- package/src/events/pattern-matcher.ts +61 -0
- package/src/events/types.ts +27 -0
- package/src/expression/evaluator.ts +676 -0
- package/src/expression/functions.ts +214 -0
- package/src/expression/index.ts +13 -0
- package/src/expression/types.ts +64 -0
- package/src/index.ts +61 -0
- package/src/state-machine/index.ts +16 -0
- package/src/state-machine/interpreter.ts +319 -0
- package/src/state-machine/types.ts +89 -0
- package/src/testing/action-trace.ts +209 -0
- package/src/testing/blueprint-test-runner.ts +214 -0
- package/src/testing/graph-walker.ts +249 -0
- package/src/testing/index.ts +69 -0
- package/src/testing/nrt-comparator.ts +199 -0
- package/src/testing/nrt-types.ts +230 -0
- package/src/testing/test-actions.ts +645 -0
- package/src/testing/test-compiler.ts +278 -0
- package/src/testing/test-runner.ts +444 -0
- package/src/testing/types.ts +231 -0
- package/src/validation/definition-validator.ts +812 -0
- package/src/validation/index.ts +13 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +8 -0
package/src/dsl/types.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST types for the noun-first DSL grammar.
|
|
3
|
+
*
|
|
4
|
+
* The DSL is line-oriented: each line is one thought, and indentation
|
|
5
|
+
* defines parent-child relationships. This module defines the types
|
|
6
|
+
* for line-level classification and parsed data.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Line Classification
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
export type LineType =
|
|
14
|
+
| 'comment'
|
|
15
|
+
| 'blank'
|
|
16
|
+
| 'space_decl'
|
|
17
|
+
| 'thing_decl'
|
|
18
|
+
| 'thing_ref'
|
|
19
|
+
| 'fragment_def'
|
|
20
|
+
| 'field_def'
|
|
21
|
+
| 'state_decl'
|
|
22
|
+
| 'starts_at'
|
|
23
|
+
| 'transition'
|
|
24
|
+
| 'when'
|
|
25
|
+
| 'set_action'
|
|
26
|
+
| 'do_action'
|
|
27
|
+
| 'go_action'
|
|
28
|
+
| 'tell_action'
|
|
29
|
+
| 'show_action'
|
|
30
|
+
| 'data_source'
|
|
31
|
+
| 'iteration'
|
|
32
|
+
| 'grouping'
|
|
33
|
+
| 'content'
|
|
34
|
+
| 'string_literal'
|
|
35
|
+
| 'search'
|
|
36
|
+
| 'qualifier'
|
|
37
|
+
| 'navigation'
|
|
38
|
+
| 'path_mapping'
|
|
39
|
+
| 'section'
|
|
40
|
+
| 'tagged'
|
|
41
|
+
| 'level_def'
|
|
42
|
+
| 'pages'
|
|
43
|
+
| 'unknown';
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Emphasis & Modifiers
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
export type Emphasis = 'big' | 'small';
|
|
50
|
+
|
|
51
|
+
export interface Constraint {
|
|
52
|
+
kind: string; // max, min, default, between, unique
|
|
53
|
+
value: string | number;
|
|
54
|
+
value2?: string | number; // for "between X and Y"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Parsed Line Data (per line type)
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
export interface SpaceDeclData {
|
|
62
|
+
name: string;
|
|
63
|
+
version: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ThingDeclData {
|
|
67
|
+
name: string;
|
|
68
|
+
version?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ThingRefData {
|
|
72
|
+
name: string;
|
|
73
|
+
kind?: string; // primary, child, derived
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface FragmentDefData {
|
|
77
|
+
name: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface FieldDefData {
|
|
81
|
+
name: string;
|
|
82
|
+
adjectives: string[];
|
|
83
|
+
baseType: string;
|
|
84
|
+
constraints: Constraint[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface StateDeclData {
|
|
88
|
+
name: string;
|
|
89
|
+
isFinal: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface StartsAtData {
|
|
93
|
+
state: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TransitionData {
|
|
97
|
+
verb: string;
|
|
98
|
+
target: string;
|
|
99
|
+
guard?: string; // role restriction like "admin only"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface WhenData {
|
|
103
|
+
condition: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SetActionData {
|
|
107
|
+
field: string;
|
|
108
|
+
expression: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface DoActionData {
|
|
112
|
+
action: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface GoActionData {
|
|
116
|
+
path: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface TellActionData {
|
|
120
|
+
target: string;
|
|
121
|
+
message: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface ShowActionData {
|
|
125
|
+
content: string;
|
|
126
|
+
modifier?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface DataSourceData {
|
|
130
|
+
alias: string;
|
|
131
|
+
source: string;
|
|
132
|
+
scope?: string; // "for this project"
|
|
133
|
+
isLive?: boolean;
|
|
134
|
+
qualifier?: string; // "5 newest", "current"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface IterationData {
|
|
138
|
+
subject: string;
|
|
139
|
+
role?: string; // "as card"
|
|
140
|
+
emphasis?: Emphasis;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface GroupingData {
|
|
144
|
+
collection: string;
|
|
145
|
+
key: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface ContentData {
|
|
149
|
+
pronoun?: string; // its, my, the
|
|
150
|
+
field: string;
|
|
151
|
+
emphasis?: Emphasis;
|
|
152
|
+
role?: string;
|
|
153
|
+
label?: string; // with "XP"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface StringLiteralData {
|
|
157
|
+
text: string;
|
|
158
|
+
emphasis?: Emphasis;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface SearchData {
|
|
162
|
+
target: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface QualifierData {
|
|
166
|
+
kind: 'order' | 'searchable' | 'filterable' | 'pagination';
|
|
167
|
+
value: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface NavigationData {
|
|
171
|
+
trigger: string; // tap, label text
|
|
172
|
+
target: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface PathMappingData {
|
|
176
|
+
path: string;
|
|
177
|
+
view: string;
|
|
178
|
+
context?: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface SectionData {
|
|
182
|
+
name: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface TaggedData {
|
|
186
|
+
tags: string[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface LevelDefData {
|
|
190
|
+
level: number;
|
|
191
|
+
title: string;
|
|
192
|
+
fromXp: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Unified Line Token
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
export type LineData =
|
|
200
|
+
| { type: 'comment'; text: string }
|
|
201
|
+
| { type: 'blank' }
|
|
202
|
+
| { type: 'space_decl' } & SpaceDeclData
|
|
203
|
+
| { type: 'thing_decl' } & ThingDeclData
|
|
204
|
+
| { type: 'thing_ref' } & ThingRefData
|
|
205
|
+
| { type: 'fragment_def' } & FragmentDefData
|
|
206
|
+
| { type: 'field_def' } & FieldDefData
|
|
207
|
+
| { type: 'state_decl' } & StateDeclData
|
|
208
|
+
| { type: 'starts_at' } & StartsAtData
|
|
209
|
+
| { type: 'transition' } & TransitionData
|
|
210
|
+
| { type: 'when' } & WhenData
|
|
211
|
+
| { type: 'set_action' } & SetActionData
|
|
212
|
+
| { type: 'do_action' } & DoActionData
|
|
213
|
+
| { type: 'go_action' } & GoActionData
|
|
214
|
+
| { type: 'tell_action' } & TellActionData
|
|
215
|
+
| { type: 'show_action' } & ShowActionData
|
|
216
|
+
| { type: 'data_source' } & DataSourceData
|
|
217
|
+
| { type: 'iteration' } & IterationData
|
|
218
|
+
| { type: 'grouping' } & GroupingData
|
|
219
|
+
| { type: 'content' } & ContentData
|
|
220
|
+
| { type: 'string_literal' } & StringLiteralData
|
|
221
|
+
| { type: 'search' } & SearchData
|
|
222
|
+
| { type: 'qualifier' } & QualifierData
|
|
223
|
+
| { type: 'navigation' } & NavigationData
|
|
224
|
+
| { type: 'path_mapping' } & PathMappingData
|
|
225
|
+
| { type: 'section' } & SectionData
|
|
226
|
+
| { type: 'tagged' } & TaggedData
|
|
227
|
+
| { type: 'level_def' } & LevelDefData
|
|
228
|
+
| { type: 'pages' }
|
|
229
|
+
| { type: 'unknown'; text: string };
|
|
230
|
+
|
|
231
|
+
export interface LineToken {
|
|
232
|
+
indent: number;
|
|
233
|
+
lineNumber: number;
|
|
234
|
+
raw: string;
|
|
235
|
+
data: LineData;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// =============================================================================
|
|
239
|
+
// AST Tree (structural nesting)
|
|
240
|
+
// =============================================================================
|
|
241
|
+
|
|
242
|
+
export interface ASTNode {
|
|
243
|
+
token: LineToken;
|
|
244
|
+
children: ASTNode[];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface ParseResult {
|
|
248
|
+
nodes: ASTNode[];
|
|
249
|
+
errors: ParseError[];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface ParseError {
|
|
253
|
+
lineNumber: number;
|
|
254
|
+
message: string;
|
|
255
|
+
raw: string;
|
|
256
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Bus — browser-side pub/sub for on_event subscriptions.
|
|
3
|
+
*
|
|
4
|
+
* Lightweight event bus that matches published topics against
|
|
5
|
+
* glob-pattern subscriptions. Used by the player to wire
|
|
6
|
+
* on_event declarations to action handlers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { compilePattern, matchTopic } from './pattern-matcher';
|
|
10
|
+
import type { BusEvent, EventHandler, EventSubscription, Unsubscribe } from './types';
|
|
11
|
+
|
|
12
|
+
export class EventBus {
|
|
13
|
+
private subscriptions: EventSubscription[] = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe to events matching a glob pattern.
|
|
17
|
+
* Returns an unsubscribe function.
|
|
18
|
+
*/
|
|
19
|
+
subscribe(pattern: string, handler: EventHandler): Unsubscribe {
|
|
20
|
+
const regex = compilePattern(pattern);
|
|
21
|
+
const subscription: EventSubscription = { pattern, regex, handler };
|
|
22
|
+
this.subscriptions.push(subscription);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
const idx = this.subscriptions.indexOf(subscription);
|
|
26
|
+
if (idx !== -1) this.subscriptions.splice(idx, 1);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Publish an event. All matching subscriptions fire (async).
|
|
32
|
+
* Errors in handlers are caught and logged, never propagated.
|
|
33
|
+
*/
|
|
34
|
+
async publish(topic: string, payload: Record<string, unknown> = {}): Promise<void> {
|
|
35
|
+
const event: BusEvent = { topic, payload };
|
|
36
|
+
|
|
37
|
+
for (const sub of this.subscriptions) {
|
|
38
|
+
if (matchTopic(sub.regex, topic)) {
|
|
39
|
+
try {
|
|
40
|
+
await sub.handler(event);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.warn(
|
|
43
|
+
`[player-core] Event handler error for pattern "${sub.pattern}" on topic "${topic}":`,
|
|
44
|
+
error,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Publish synchronously (fire-and-forget).
|
|
53
|
+
* Useful when you don't need to await handler completion.
|
|
54
|
+
*/
|
|
55
|
+
emit(topic: string, payload: Record<string, unknown> = {}): void {
|
|
56
|
+
void this.publish(topic, payload);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Get count of active subscriptions */
|
|
60
|
+
get size(): number {
|
|
61
|
+
return this.subscriptions.length;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Remove all subscriptions */
|
|
65
|
+
clear(): void {
|
|
66
|
+
this.subscriptions.length = 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Matcher — glob-style topic matching for on_event subscriptions.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Exact match: "workflow.order:state_enter"
|
|
6
|
+
* - Single-level wildcard (*): "workflow.*:state_enter" matches "workflow.order:state_enter"
|
|
7
|
+
* - Multi-level wildcard (**): "workflow.**" matches "workflow.order:state_enter:completed"
|
|
8
|
+
* - Colon-separated segments (like EventEmitter2)
|
|
9
|
+
*
|
|
10
|
+
* Compiled regex is cached per pattern for performance.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const patternCache = new Map<string, RegExp>();
|
|
14
|
+
const MAX_CACHE = 200;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compile a glob pattern into a RegExp.
|
|
18
|
+
*
|
|
19
|
+
* Pattern rules:
|
|
20
|
+
* - Segments separated by `:` or `.`
|
|
21
|
+
* - `*` matches exactly one segment (any chars except `:` and `.`)
|
|
22
|
+
* - `**` matches zero or more segments (greedy)
|
|
23
|
+
* - Everything else is literal
|
|
24
|
+
*/
|
|
25
|
+
export function compilePattern(pattern: string): RegExp {
|
|
26
|
+
const cached = patternCache.get(pattern);
|
|
27
|
+
if (cached) return cached;
|
|
28
|
+
|
|
29
|
+
// Escape regex special chars except * and separators
|
|
30
|
+
const escaped = pattern
|
|
31
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
32
|
+
// ** (double star) → placeholder
|
|
33
|
+
.replace(/\*\*/g, '<<DOUBLESTAR>>')
|
|
34
|
+
// * (single star) → match one segment
|
|
35
|
+
.replace(/\*/g, '[^:.]+')
|
|
36
|
+
// Restore ** → match anything
|
|
37
|
+
.replace(/<<DOUBLESTAR>>/g, '.*');
|
|
38
|
+
|
|
39
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
40
|
+
|
|
41
|
+
// LRU eviction
|
|
42
|
+
if (patternCache.size >= MAX_CACHE) {
|
|
43
|
+
const firstKey = patternCache.keys().next().value;
|
|
44
|
+
if (firstKey) patternCache.delete(firstKey);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
patternCache.set(pattern, regex);
|
|
48
|
+
return regex;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Test if a topic matches a compiled pattern.
|
|
53
|
+
*/
|
|
54
|
+
export function matchTopic(pattern: RegExp, topic: string): boolean {
|
|
55
|
+
return pattern.test(topic);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Clear pattern cache (for testing) */
|
|
59
|
+
export function clearPatternCache(): void {
|
|
60
|
+
patternCache.clear();
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event System Types — browser-side on_event matching and dispatch.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Subscription registered with the event bus */
|
|
6
|
+
export interface EventSubscription {
|
|
7
|
+
/** Glob-style pattern: "workflow.*:state_enter" or "*:*:completed" */
|
|
8
|
+
pattern: string;
|
|
9
|
+
/** Compiled regex for fast matching (derived from pattern) */
|
|
10
|
+
regex: RegExp;
|
|
11
|
+
/** Conditions evaluated before actions fire */
|
|
12
|
+
conditions?: string[];
|
|
13
|
+
/** Handler called when pattern matches and conditions pass */
|
|
14
|
+
handler: EventHandler;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Event payload passed to handlers */
|
|
18
|
+
export interface BusEvent {
|
|
19
|
+
topic: string;
|
|
20
|
+
payload: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Handler function for matched events */
|
|
24
|
+
export type EventHandler = (event: BusEvent) => void | Promise<void>;
|
|
25
|
+
|
|
26
|
+
/** Unsubscribe function returned by subscribe */
|
|
27
|
+
export type Unsubscribe = () => void;
|