@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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR Migration — forward-compatibility layer for workflow definitions.
|
|
3
|
+
*
|
|
4
|
+
* Handles differences between IR versions so that older compiled definitions
|
|
5
|
+
* continue to work with newer runtimes. Each migration transforms from
|
|
6
|
+
* one version to the next; they chain automatically.
|
|
7
|
+
*
|
|
8
|
+
* Version history:
|
|
9
|
+
* - v1.0: Original format (states use state_type, actions use action_type)
|
|
10
|
+
* - v1.1: Normalized format (states use type, actions use type, fields use type)
|
|
11
|
+
* - v1.2: Added extensions, during actions, on_event at workflow level
|
|
12
|
+
*
|
|
13
|
+
* The current version is always the latest. Older definitions are migrated
|
|
14
|
+
* transparently by normalizeDefinition().
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { IRWorkflowDefinition } from './ir-types';
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
export const CURRENT_IR_VERSION = '1.2';
|
|
24
|
+
|
|
25
|
+
export interface MigrationResult {
|
|
26
|
+
/** The migrated definition. */
|
|
27
|
+
definition: IRWorkflowDefinition;
|
|
28
|
+
/** Original IR version detected. */
|
|
29
|
+
fromVersion: string;
|
|
30
|
+
/** Target IR version after migration. */
|
|
31
|
+
toVersion: string;
|
|
32
|
+
/** Whether any migration was applied. */
|
|
33
|
+
migrated: boolean;
|
|
34
|
+
/** Migrations applied (in order). */
|
|
35
|
+
appliedMigrations: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Version Detection
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect the IR version of a definition.
|
|
44
|
+
* Uses heuristics based on field naming conventions.
|
|
45
|
+
*/
|
|
46
|
+
export function detectIRVersion(def: Record<string, unknown>): string {
|
|
47
|
+
// Check explicit ir_version field
|
|
48
|
+
if (typeof def.ir_version === 'string') return def.ir_version;
|
|
49
|
+
|
|
50
|
+
// v1.2: has extensions or workflow-level on_event
|
|
51
|
+
if (def.extensions || (def.on_event && Array.isArray(def.on_event))) {
|
|
52
|
+
return '1.2';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// v1.0 detection: uses state_type/action_type/field_type naming
|
|
56
|
+
const states = def.states as Array<Record<string, unknown>> | undefined;
|
|
57
|
+
const fields = def.fields as Array<Record<string, unknown>> | undefined;
|
|
58
|
+
|
|
59
|
+
if (states && states.length > 0 && states[0].state_type) return '1.0';
|
|
60
|
+
if (fields && fields.length > 0 && fields[0].field_type && !fields[0].type) return '1.0';
|
|
61
|
+
|
|
62
|
+
// v1.1: uses normalized type naming
|
|
63
|
+
if (states && states.length > 0 && states[0].type) return '1.1';
|
|
64
|
+
|
|
65
|
+
return '1.0'; // Default to oldest version
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Migrations
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
type MigrationFn = (def: Record<string, unknown>) => Record<string, unknown>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* v1.0 → v1.1: Normalize field names (state_type → type, etc.)
|
|
76
|
+
*/
|
|
77
|
+
function migrateV10ToV11(def: Record<string, unknown>): Record<string, unknown> {
|
|
78
|
+
const result = { ...def };
|
|
79
|
+
|
|
80
|
+
// Normalize states: state_type → type
|
|
81
|
+
if (Array.isArray(result.states)) {
|
|
82
|
+
result.states = (result.states as Array<Record<string, unknown>>).map(state => {
|
|
83
|
+
const normalized: Record<string, unknown> = { ...state };
|
|
84
|
+
if ('state_type' in normalized && !('type' in normalized)) {
|
|
85
|
+
normalized.type = normalized.state_type;
|
|
86
|
+
delete normalized.state_type;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Normalize actions within states
|
|
90
|
+
if (Array.isArray(normalized.on_enter)) {
|
|
91
|
+
normalized.on_enter = normalizeActions(normalized.on_enter as Array<Record<string, unknown>>);
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(normalized.on_exit)) {
|
|
94
|
+
normalized.on_exit = normalizeActions(normalized.on_exit as Array<Record<string, unknown>>);
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(normalized.during)) {
|
|
97
|
+
normalized.during = (normalized.during as Array<Record<string, unknown>>).map(d => {
|
|
98
|
+
if (Array.isArray(d.actions)) {
|
|
99
|
+
return { ...d, actions: normalizeActions(d.actions as Array<Record<string, unknown>>) };
|
|
100
|
+
}
|
|
101
|
+
return d;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return normalized;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Normalize fields: field_type → type
|
|
110
|
+
if (Array.isArray(result.fields)) {
|
|
111
|
+
result.fields = (result.fields as Array<Record<string, unknown>>).map(field => {
|
|
112
|
+
const normalized: Record<string, unknown> = { ...field };
|
|
113
|
+
if ('field_type' in normalized && !('type' in normalized)) {
|
|
114
|
+
normalized.type = normalized.field_type;
|
|
115
|
+
delete normalized.field_type;
|
|
116
|
+
}
|
|
117
|
+
return normalized;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Normalize transition actions
|
|
122
|
+
if (Array.isArray(result.transitions)) {
|
|
123
|
+
result.transitions = (result.transitions as Array<Record<string, unknown>>).map(t => {
|
|
124
|
+
if (Array.isArray(t.actions)) {
|
|
125
|
+
return { ...t, actions: normalizeActions(t.actions as Array<Record<string, unknown>>) };
|
|
126
|
+
}
|
|
127
|
+
return t;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* v1.1 → v1.2: Add extensions, during, on_event defaults
|
|
136
|
+
*/
|
|
137
|
+
function migrateV11ToV12(def: Record<string, unknown>): Record<string, unknown> {
|
|
138
|
+
const result = { ...def };
|
|
139
|
+
|
|
140
|
+
// Ensure extensions field exists
|
|
141
|
+
if (!result.extensions) {
|
|
142
|
+
result.extensions = {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Ensure states have during and on_event arrays
|
|
146
|
+
if (Array.isArray(result.states)) {
|
|
147
|
+
result.states = (result.states as Array<Record<string, unknown>>).map(state => ({
|
|
148
|
+
...state,
|
|
149
|
+
during: state.during ?? [],
|
|
150
|
+
on_event: state.on_event ?? [],
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeActions(actions: Array<Record<string, unknown>>): Array<Record<string, unknown>> {
|
|
158
|
+
return actions.map(action => {
|
|
159
|
+
const normalized: Record<string, unknown> = { ...action };
|
|
160
|
+
if ('action_type' in normalized && !('type' in normalized)) {
|
|
161
|
+
normalized.type = normalized.action_type;
|
|
162
|
+
delete normalized.action_type;
|
|
163
|
+
}
|
|
164
|
+
return normalized;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// Migration Chain
|
|
170
|
+
// =============================================================================
|
|
171
|
+
|
|
172
|
+
const MIGRATIONS: Array<{ from: string; to: string; fn: MigrationFn }> = [
|
|
173
|
+
{ from: '1.0', to: '1.1', fn: migrateV10ToV11 },
|
|
174
|
+
{ from: '1.1', to: '1.2', fn: migrateV11ToV12 },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Normalize a definition to the current IR version.
|
|
179
|
+
* Applies all necessary migrations in sequence.
|
|
180
|
+
*/
|
|
181
|
+
export function normalizeDefinition(def: Record<string, unknown>): MigrationResult {
|
|
182
|
+
let currentVersion = detectIRVersion(def);
|
|
183
|
+
let current = def;
|
|
184
|
+
const applied: string[] = [];
|
|
185
|
+
|
|
186
|
+
if (currentVersion === CURRENT_IR_VERSION) {
|
|
187
|
+
return {
|
|
188
|
+
definition: current as unknown as IRWorkflowDefinition,
|
|
189
|
+
fromVersion: currentVersion,
|
|
190
|
+
toVersion: CURRENT_IR_VERSION,
|
|
191
|
+
migrated: false,
|
|
192
|
+
appliedMigrations: [],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const fromVersion = currentVersion;
|
|
197
|
+
|
|
198
|
+
// Apply migrations in order
|
|
199
|
+
for (const migration of MIGRATIONS) {
|
|
200
|
+
if (currentVersion === migration.from) {
|
|
201
|
+
current = migration.fn(current);
|
|
202
|
+
currentVersion = migration.to;
|
|
203
|
+
applied.push(`${migration.from} → ${migration.to}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
definition: current as unknown as IRWorkflowDefinition,
|
|
209
|
+
fromVersion,
|
|
210
|
+
toVersion: currentVersion,
|
|
211
|
+
migrated: applied.length > 0,
|
|
212
|
+
appliedMigrations: applied,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Check if a definition needs migration.
|
|
218
|
+
*/
|
|
219
|
+
export function needsMigration(def: Record<string, unknown>): boolean {
|
|
220
|
+
return detectIRVersion(def) !== CURRENT_IR_VERSION;
|
|
221
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR Types — the complete Intermediate Representation contract.
|
|
3
|
+
*
|
|
4
|
+
* These types mirror the MM-IR Reference (0-MM-IR-REFERENCE.md).
|
|
5
|
+
* Standalone — no imports from backend/frontend packages.
|
|
6
|
+
* The compiler transforms AST nodes into these shapes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Enums & Primitives
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
export type IRStateType = 'START' | 'REGULAR' | 'END' | 'CANCELLED';
|
|
14
|
+
export type IRActionMode = 'auto' | 'manual';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Field type is an open string slug referencing an Element definition.
|
|
18
|
+
*
|
|
19
|
+
* Canonical seed types: text, rich_text, number, boolean, date, datetime,
|
|
20
|
+
* select, multi_select, url, email, phone, currency, percentage, rating,
|
|
21
|
+
* duration, color, file, image, link_to_one, link_to_many, formula, rollup,
|
|
22
|
+
* count, lookup, created_at, updated_at, created_by, auto_number, barcode.
|
|
23
|
+
*
|
|
24
|
+
* User-defined types use slugs like 'my-org/custom-address'.
|
|
25
|
+
* The 14 original types remain valid — this is backward compatible.
|
|
26
|
+
*/
|
|
27
|
+
export type IRWorkflowFieldType = string;
|
|
28
|
+
|
|
29
|
+
export type RuntimeProfile = 'backend' | 'collaborative' | 'p2p' | 'local' | 'ephemeral' | 'edge';
|
|
30
|
+
|
|
31
|
+
export interface IRStateHome {
|
|
32
|
+
scope: 'ephemeral' | 'route' | 'instance' | 'global';
|
|
33
|
+
persistence: 'none' | 'local' | 'durable';
|
|
34
|
+
sync: 'none' | 'tenant' | 'p2p';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Action Definitions
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
export interface IRActionDefinition {
|
|
42
|
+
id: string;
|
|
43
|
+
type: string;
|
|
44
|
+
mode: IRActionMode;
|
|
45
|
+
config: Record<string, unknown>;
|
|
46
|
+
condition?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// During Actions (scheduled/recurring while in state)
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
53
|
+
export interface IRDuringAction {
|
|
54
|
+
id: string;
|
|
55
|
+
type: 'interval' | 'timeout' | 'poll' | 'once' | 'cron';
|
|
56
|
+
interval_ms?: number;
|
|
57
|
+
cron?: string;
|
|
58
|
+
delay_ms?: number;
|
|
59
|
+
actions: IRActionDefinition[];
|
|
60
|
+
condition?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// On-Event Subscriptions (cross-instance)
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
export interface IROnEventAction {
|
|
68
|
+
type: 'set_field' | 'set_memory' | 'log_event' | 'create_instance';
|
|
69
|
+
field?: string;
|
|
70
|
+
expression?: string;
|
|
71
|
+
key?: string;
|
|
72
|
+
message?: string;
|
|
73
|
+
config?: Record<string, unknown>;
|
|
74
|
+
conditions?: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IROnEventSubscription {
|
|
78
|
+
match: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
conditions?: string[];
|
|
81
|
+
actions: IROnEventAction[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// State Definition
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
export interface IRStateDefinition {
|
|
89
|
+
name: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
type: IRStateType;
|
|
92
|
+
on_enter: IRActionDefinition[];
|
|
93
|
+
during: IRDuringAction[];
|
|
94
|
+
on_exit: IRActionDefinition[];
|
|
95
|
+
on_event?: IROnEventSubscription[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Condition Definition
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
export interface IRConditionDefinition {
|
|
103
|
+
type?: string;
|
|
104
|
+
field?: string;
|
|
105
|
+
operator?: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'not_in' | 'contains' | 'is_set' | 'is_empty';
|
|
106
|
+
value?: unknown;
|
|
107
|
+
expression?: string;
|
|
108
|
+
OR?: IRConditionDefinition[];
|
|
109
|
+
AND?: IRConditionDefinition[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// =============================================================================
|
|
113
|
+
// Transition Definition
|
|
114
|
+
// =============================================================================
|
|
115
|
+
|
|
116
|
+
export interface IRTransitionDefinition {
|
|
117
|
+
name: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
from: string[];
|
|
120
|
+
to: string;
|
|
121
|
+
conditions?: IRConditionDefinition[];
|
|
122
|
+
actions: IRActionDefinition[];
|
|
123
|
+
roles?: string[];
|
|
124
|
+
auto?: boolean;
|
|
125
|
+
required_fields?: string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// Field Definition
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
132
|
+
export interface IRFieldValidation {
|
|
133
|
+
min?: number;
|
|
134
|
+
max?: number;
|
|
135
|
+
minLength?: number;
|
|
136
|
+
maxLength?: number;
|
|
137
|
+
options?: string[];
|
|
138
|
+
rules?: Array<{
|
|
139
|
+
expression: string;
|
|
140
|
+
message: string;
|
|
141
|
+
severity: 'error' | 'warning';
|
|
142
|
+
}>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface IRFieldDefinition {
|
|
146
|
+
name: string;
|
|
147
|
+
type: IRWorkflowFieldType;
|
|
148
|
+
/** Version pin: { type, typeVersion } is canonical storage/API form */
|
|
149
|
+
typeVersion?: string;
|
|
150
|
+
/** Structural inheritance: storage + widget + primitive intrinsics from base type */
|
|
151
|
+
baseType?: string;
|
|
152
|
+
label?: string;
|
|
153
|
+
required?: boolean;
|
|
154
|
+
default_value?: unknown;
|
|
155
|
+
validation?: IRFieldValidation;
|
|
156
|
+
computed?: string;
|
|
157
|
+
computed_deps?: string[];
|
|
158
|
+
// Field-level ACL (matching backend WorkflowFieldDefinition)
|
|
159
|
+
visible_in_states?: string[];
|
|
160
|
+
editable_in_states?: string[];
|
|
161
|
+
visible_to_roles?: string[];
|
|
162
|
+
editable_by_roles?: string[];
|
|
163
|
+
visible_when?: string;
|
|
164
|
+
editable_when?: string;
|
|
165
|
+
// State home (scope/persistence/sync)
|
|
166
|
+
state_home?: IRStateHome;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// Role Definition
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
export interface IRRoleDefinition {
|
|
174
|
+
name: string;
|
|
175
|
+
description?: string;
|
|
176
|
+
permissions: string[];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// =============================================================================
|
|
180
|
+
// Workflow Definition
|
|
181
|
+
// =============================================================================
|
|
182
|
+
|
|
183
|
+
export interface IRWorkflowDefinition {
|
|
184
|
+
slug: string;
|
|
185
|
+
name: string;
|
|
186
|
+
version: string;
|
|
187
|
+
description?: string;
|
|
188
|
+
category: string | string[];
|
|
189
|
+
states: IRStateDefinition[];
|
|
190
|
+
transitions: IRTransitionDefinition[];
|
|
191
|
+
fields: IRFieldDefinition[];
|
|
192
|
+
roles: IRRoleDefinition[];
|
|
193
|
+
tags?: Array<{ tag_name: string }>;
|
|
194
|
+
metadata?: Record<string, unknown>;
|
|
195
|
+
on_event?: IROnEventSubscription[];
|
|
196
|
+
extensions?: Record<string, IRGrammarIsland[]>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// Grammar Islands
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
export interface IRGrammarIsland {
|
|
204
|
+
slug: string;
|
|
205
|
+
contextTag: string;
|
|
206
|
+
rawSource: string;
|
|
207
|
+
parsed?: unknown;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// =============================================================================
|
|
211
|
+
// Experience Node
|
|
212
|
+
// =============================================================================
|
|
213
|
+
|
|
214
|
+
export interface IRExperienceNode {
|
|
215
|
+
id: string;
|
|
216
|
+
/** User-assigned display name for authoring UIs (e.g., "Hero Section", "Login Form") */
|
|
217
|
+
displayName?: string;
|
|
218
|
+
/** Reference to a registered experience definition (prefab instantiation) */
|
|
219
|
+
experienceId?: string;
|
|
220
|
+
component?: string;
|
|
221
|
+
/** Slot placeholder — rendered by collecting contributions from sub-workflows */
|
|
222
|
+
slot?: string;
|
|
223
|
+
children?: IRExperienceNode[];
|
|
224
|
+
dataSources?: IRDataSource[];
|
|
225
|
+
dataScope?: string;
|
|
226
|
+
layout?: string;
|
|
227
|
+
/** CSS class on the wrapper div */
|
|
228
|
+
className?: string;
|
|
229
|
+
/** Style IR — structured cross-platform styling (opaque to player-core) */
|
|
230
|
+
style?: Record<string, unknown>;
|
|
231
|
+
config?: Record<string, unknown>;
|
|
232
|
+
bindings?: Record<string, string>;
|
|
233
|
+
/** Overrides for a referenced experience's defaults */
|
|
234
|
+
overrides?: Record<string, unknown>;
|
|
235
|
+
visible_when?: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// =============================================================================
|
|
239
|
+
// Data Sources (discriminated union)
|
|
240
|
+
// =============================================================================
|
|
241
|
+
|
|
242
|
+
export interface IRWorkflowDataSource {
|
|
243
|
+
type: 'workflow';
|
|
244
|
+
name: string;
|
|
245
|
+
slug?: string;
|
|
246
|
+
query: 'latest' | 'list' | 'count';
|
|
247
|
+
/** Fetch a specific instance by ID. Supports template interpolation: "{{entity_id}}" */
|
|
248
|
+
instanceId?: string;
|
|
249
|
+
/** Entity scope for the query (null = no filtering, omitted = inherited) */
|
|
250
|
+
entity?: { type: string; id: string } | null;
|
|
251
|
+
paginated?: boolean;
|
|
252
|
+
pageSize?: number;
|
|
253
|
+
/** Static filters always applied (e.g., { current_state: 'todo' }) */
|
|
254
|
+
filter?: Record<string, string>;
|
|
255
|
+
/** Dynamic filters with bind expressions */
|
|
256
|
+
filters?: Record<string, string | { bind: string }>;
|
|
257
|
+
sort?: string;
|
|
258
|
+
search?: string;
|
|
259
|
+
searchFields?: string[];
|
|
260
|
+
facets?: string[];
|
|
261
|
+
/** Range filters: { field: { min?, max? } } */
|
|
262
|
+
range?: Record<string, { min?: unknown; max?: unknown }>;
|
|
263
|
+
/** Aggregate functions: "field:fn,field:fn" */
|
|
264
|
+
aggregate?: string;
|
|
265
|
+
groupBy?: string;
|
|
266
|
+
parentInstanceId?: string;
|
|
267
|
+
autoStart?: boolean;
|
|
268
|
+
initialData?: Record<string, unknown>;
|
|
269
|
+
includeDefinition?: boolean;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface IRApiDataSource {
|
|
273
|
+
type: 'api';
|
|
274
|
+
name: string;
|
|
275
|
+
endpoint: string;
|
|
276
|
+
method?: 'GET' | 'POST';
|
|
277
|
+
/** Map API response to InstanceData shape */
|
|
278
|
+
mapping?: {
|
|
279
|
+
id?: string;
|
|
280
|
+
state?: string;
|
|
281
|
+
data?: string;
|
|
282
|
+
items?: string;
|
|
283
|
+
total?: string;
|
|
284
|
+
};
|
|
285
|
+
staleTime?: number;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface IRRefDataSource {
|
|
289
|
+
type: 'ref';
|
|
290
|
+
name: string;
|
|
291
|
+
expression: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface IRStaticDataSource {
|
|
295
|
+
type: 'static';
|
|
296
|
+
name: string;
|
|
297
|
+
data: Record<string, unknown> | Record<string, unknown>[];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export type IRDataSource =
|
|
301
|
+
| IRWorkflowDataSource
|
|
302
|
+
| IRApiDataSource
|
|
303
|
+
| IRRefDataSource
|
|
304
|
+
| IRStaticDataSource;
|
|
305
|
+
|
|
306
|
+
// =============================================================================
|
|
307
|
+
// Experience Definition
|
|
308
|
+
// =============================================================================
|
|
309
|
+
|
|
310
|
+
export interface IRExperienceDefinition {
|
|
311
|
+
slug: string;
|
|
312
|
+
version: string;
|
|
313
|
+
name: string;
|
|
314
|
+
description?: string;
|
|
315
|
+
category: string | string[];
|
|
316
|
+
view_definition: IRExperienceNode;
|
|
317
|
+
workflows: string[];
|
|
318
|
+
children: string[];
|
|
319
|
+
data_bindings: unknown[];
|
|
320
|
+
is_default: boolean;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// =============================================================================
|
|
324
|
+
// Blueprint Manifest
|
|
325
|
+
// =============================================================================
|
|
326
|
+
|
|
327
|
+
export interface IRBlueprintManifest {
|
|
328
|
+
workflows: Array<{
|
|
329
|
+
slug: string;
|
|
330
|
+
role: 'primary' | 'child' | 'derived' | 'utility';
|
|
331
|
+
}>;
|
|
332
|
+
experience_id: string;
|
|
333
|
+
routes?: Array<{
|
|
334
|
+
path: string;
|
|
335
|
+
node: string;
|
|
336
|
+
entityType?: string;
|
|
337
|
+
entityIdSource?: 'user' | 'param';
|
|
338
|
+
}>;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// =============================================================================
|
|
342
|
+
// Canonical Tree (Pure Form per 041 protocol)
|
|
343
|
+
// =============================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Canonical workflow tree per spec 041 §1.1.
|
|
347
|
+
* This is the universal type: { slug, category, parts, metadata }.
|
|
348
|
+
*
|
|
349
|
+
* Category invariants (INV-1):
|
|
350
|
+
* category.length >= 1
|
|
351
|
+
* category[0] = primary (dispatch target, must be IDENT)
|
|
352
|
+
* category[1..] = tags (sorted bytewise UTF-8, unique, primary not in tags)
|
|
353
|
+
*/
|
|
354
|
+
export interface PureFormWorkflow {
|
|
355
|
+
slug: string;
|
|
356
|
+
category: string[];
|
|
357
|
+
parts?: PureFormWorkflow[];
|
|
358
|
+
metadata?: Record<string, unknown>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Compiled output: canonical tree (truth) + lowered IR (cache).
|
|
363
|
+
* Per spec 048 §1.2: canonical tree IS the storage format,
|
|
364
|
+
* IR is a derivable runtime cache.
|
|
365
|
+
*/
|
|
366
|
+
export interface CompiledOutput {
|
|
367
|
+
canonical: PureFormWorkflow;
|
|
368
|
+
ir: IRWorkflowDefinition;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Normalizes a category array to satisfy INV-1:
|
|
373
|
+
* - category[0] = primary (unchanged)
|
|
374
|
+
* - category[1..] = sorted bytewise UTF-8, unique, primary excluded
|
|
375
|
+
*/
|
|
376
|
+
export function normalizeCategory(primary: string, ...tags: string[]): string[] {
|
|
377
|
+
const uniqueTags = [...new Set(tags.filter(t => t !== primary))];
|
|
378
|
+
uniqueTags.sort();
|
|
379
|
+
return [primary, ...uniqueTags];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// =============================================================================
|
|
383
|
+
// Compilation Output
|
|
384
|
+
// =============================================================================
|
|
385
|
+
|
|
386
|
+
export type CompilerErrorCode =
|
|
387
|
+
| 'MISSING_STARTS_AT'
|
|
388
|
+
| 'UNKNOWN_TARGET_STATE'
|
|
389
|
+
| 'DUPLICATE_FIELD'
|
|
390
|
+
| 'DUPLICATE_STATE'
|
|
391
|
+
| 'UNKNOWN_FIELD_TYPE'
|
|
392
|
+
| 'INVALID_EXPRESSION'
|
|
393
|
+
| 'UNKNOWN_FRAGMENT'
|
|
394
|
+
| 'EMPTY_WORKFLOW'
|
|
395
|
+
| 'STRICT_USE_EFFECT'
|
|
396
|
+
| 'STRICT_USE_REF'
|
|
397
|
+
| 'STRICT_USE_MEMO'
|
|
398
|
+
| 'STRICT_USE_CALLBACK'
|
|
399
|
+
| 'STRICT_USE_LAYOUT_EFFECT'
|
|
400
|
+
| 'STRICT_FORBIDDEN_IMPORT'
|
|
401
|
+
| 'INFER_RAW_JSX';
|
|
402
|
+
|
|
403
|
+
export interface CompilerError {
|
|
404
|
+
code: CompilerErrorCode;
|
|
405
|
+
message: string;
|
|
406
|
+
lineNumber?: number;
|
|
407
|
+
severity: 'error' | 'warning';
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export interface CompilationResult {
|
|
411
|
+
workflows: IRWorkflowDefinition[];
|
|
412
|
+
experiences: IRExperienceDefinition[];
|
|
413
|
+
manifest?: IRBlueprintManifest;
|
|
414
|
+
errors: CompilerError[];
|
|
415
|
+
warnings: CompilerError[];
|
|
416
|
+
}
|