@mmapp/react 0.1.0-alpha.22 → 0.1.0-alpha.24
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.js +1124 -39
- package/dist/index.mjs +1098 -25
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
|
@@ -66,14 +66,1094 @@ import {
|
|
|
66
66
|
useScope
|
|
67
67
|
} from "./chunk-WV7DVCP6.mjs";
|
|
68
68
|
|
|
69
|
+
// ../player-core/dist/index.mjs
|
|
70
|
+
var add = {
|
|
71
|
+
name: "add",
|
|
72
|
+
fn: (a, b) => Number(a) + Number(b),
|
|
73
|
+
arity: 2
|
|
74
|
+
};
|
|
75
|
+
var subtract = {
|
|
76
|
+
name: "subtract",
|
|
77
|
+
fn: (a, b) => Number(a) - Number(b),
|
|
78
|
+
arity: 2
|
|
79
|
+
};
|
|
80
|
+
var multiply = {
|
|
81
|
+
name: "multiply",
|
|
82
|
+
fn: (a, b) => Number(a) * Number(b),
|
|
83
|
+
arity: 2
|
|
84
|
+
};
|
|
85
|
+
var divide = {
|
|
86
|
+
name: "divide",
|
|
87
|
+
fn: (a, b) => {
|
|
88
|
+
const d = Number(b);
|
|
89
|
+
return d === 0 ? 0 : Number(a) / d;
|
|
90
|
+
},
|
|
91
|
+
arity: 2
|
|
92
|
+
};
|
|
93
|
+
var abs = {
|
|
94
|
+
name: "abs",
|
|
95
|
+
fn: (a) => Math.abs(Number(a)),
|
|
96
|
+
arity: 1
|
|
97
|
+
};
|
|
98
|
+
var round = {
|
|
99
|
+
name: "round",
|
|
100
|
+
fn: (a, decimals) => {
|
|
101
|
+
const d = decimals != null ? Number(decimals) : 0;
|
|
102
|
+
const factor = Math.pow(10, d);
|
|
103
|
+
return Math.round(Number(a) * factor) / factor;
|
|
104
|
+
},
|
|
105
|
+
arity: -1
|
|
106
|
+
};
|
|
107
|
+
var min = {
|
|
108
|
+
name: "min",
|
|
109
|
+
fn: (...args) => {
|
|
110
|
+
const nums = args.flat().map(Number).filter((n) => !isNaN(n));
|
|
111
|
+
return nums.length === 0 ? 0 : Math.min(...nums);
|
|
112
|
+
},
|
|
113
|
+
arity: -1
|
|
114
|
+
};
|
|
115
|
+
var max = {
|
|
116
|
+
name: "max",
|
|
117
|
+
fn: (...args) => {
|
|
118
|
+
const nums = args.flat().map(Number).filter((n) => !isNaN(n));
|
|
119
|
+
return nums.length === 0 ? 0 : Math.max(...nums);
|
|
120
|
+
},
|
|
121
|
+
arity: -1
|
|
122
|
+
};
|
|
123
|
+
var eq = {
|
|
124
|
+
name: "eq",
|
|
125
|
+
fn: (a, b) => a === b || String(a) === String(b),
|
|
126
|
+
arity: 2
|
|
127
|
+
};
|
|
128
|
+
var neq = {
|
|
129
|
+
name: "neq",
|
|
130
|
+
fn: (a, b) => a !== b && String(a) !== String(b),
|
|
131
|
+
arity: 2
|
|
132
|
+
};
|
|
133
|
+
var gt = {
|
|
134
|
+
name: "gt",
|
|
135
|
+
fn: (a, b) => Number(a) > Number(b),
|
|
136
|
+
arity: 2
|
|
137
|
+
};
|
|
138
|
+
var gte = {
|
|
139
|
+
name: "gte",
|
|
140
|
+
fn: (a, b) => Number(a) >= Number(b),
|
|
141
|
+
arity: 2
|
|
142
|
+
};
|
|
143
|
+
var lt = {
|
|
144
|
+
name: "lt",
|
|
145
|
+
fn: (a, b) => Number(a) < Number(b),
|
|
146
|
+
arity: 2
|
|
147
|
+
};
|
|
148
|
+
var lte = {
|
|
149
|
+
name: "lte",
|
|
150
|
+
fn: (a, b) => Number(a) <= Number(b),
|
|
151
|
+
arity: 2
|
|
152
|
+
};
|
|
153
|
+
var if_fn = {
|
|
154
|
+
name: "if",
|
|
155
|
+
fn: (cond, then, else_) => cond ? then : else_,
|
|
156
|
+
arity: 3
|
|
157
|
+
};
|
|
158
|
+
var and = {
|
|
159
|
+
name: "and",
|
|
160
|
+
fn: (...args) => args.every(Boolean),
|
|
161
|
+
arity: -1
|
|
162
|
+
};
|
|
163
|
+
var or = {
|
|
164
|
+
name: "or",
|
|
165
|
+
fn: (...args) => args.some(Boolean),
|
|
166
|
+
arity: -1
|
|
167
|
+
};
|
|
168
|
+
var not = {
|
|
169
|
+
name: "not",
|
|
170
|
+
fn: (a) => !a,
|
|
171
|
+
arity: 1
|
|
172
|
+
};
|
|
173
|
+
var coalesce = {
|
|
174
|
+
name: "coalesce",
|
|
175
|
+
fn: (...args) => {
|
|
176
|
+
for (const arg of args) {
|
|
177
|
+
if (arg != null) return arg;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
},
|
|
181
|
+
arity: -1
|
|
182
|
+
};
|
|
183
|
+
var concat = {
|
|
184
|
+
name: "concat",
|
|
185
|
+
fn: (...args) => args.map(String).join(""),
|
|
186
|
+
arity: -1
|
|
187
|
+
};
|
|
188
|
+
var upper = {
|
|
189
|
+
name: "upper",
|
|
190
|
+
fn: (s) => String(s ?? "").toUpperCase(),
|
|
191
|
+
arity: 1
|
|
192
|
+
};
|
|
193
|
+
var lower = {
|
|
194
|
+
name: "lower",
|
|
195
|
+
fn: (s) => String(s ?? "").toLowerCase(),
|
|
196
|
+
arity: 1
|
|
197
|
+
};
|
|
198
|
+
var trim = {
|
|
199
|
+
name: "trim",
|
|
200
|
+
fn: (s) => String(s ?? "").trim(),
|
|
201
|
+
arity: 1
|
|
202
|
+
};
|
|
203
|
+
var format = {
|
|
204
|
+
name: "format",
|
|
205
|
+
fn: (template, ...args) => {
|
|
206
|
+
let result = String(template ?? "");
|
|
207
|
+
args.forEach((arg, i) => {
|
|
208
|
+
result = result.replace(`{${i}}`, String(arg ?? ""));
|
|
209
|
+
});
|
|
210
|
+
return result;
|
|
211
|
+
},
|
|
212
|
+
arity: -1
|
|
213
|
+
};
|
|
214
|
+
var length = {
|
|
215
|
+
name: "length",
|
|
216
|
+
fn: (v) => {
|
|
217
|
+
if (Array.isArray(v)) return v.length;
|
|
218
|
+
if (typeof v === "string") return v.length;
|
|
219
|
+
if (v && typeof v === "object") return Object.keys(v).length;
|
|
220
|
+
return 0;
|
|
221
|
+
},
|
|
222
|
+
arity: 1
|
|
223
|
+
};
|
|
224
|
+
var get = {
|
|
225
|
+
name: "get",
|
|
226
|
+
fn: (obj, path) => {
|
|
227
|
+
if (obj == null || typeof path !== "string") return void 0;
|
|
228
|
+
const parts = path.split(".");
|
|
229
|
+
let current = obj;
|
|
230
|
+
for (const part of parts) {
|
|
231
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
232
|
+
current = current[part];
|
|
233
|
+
}
|
|
234
|
+
return current;
|
|
235
|
+
},
|
|
236
|
+
arity: 2
|
|
237
|
+
};
|
|
238
|
+
var includes = {
|
|
239
|
+
name: "includes",
|
|
240
|
+
fn: (collection, value) => {
|
|
241
|
+
if (Array.isArray(collection)) return collection.includes(value);
|
|
242
|
+
if (typeof collection === "string") return collection.includes(String(value));
|
|
243
|
+
return false;
|
|
244
|
+
},
|
|
245
|
+
arity: 2
|
|
246
|
+
};
|
|
247
|
+
var is_defined = {
|
|
248
|
+
name: "is_defined",
|
|
249
|
+
fn: (v) => v !== void 0 && v !== null,
|
|
250
|
+
arity: 1
|
|
251
|
+
};
|
|
252
|
+
var is_empty = {
|
|
253
|
+
name: "is_empty",
|
|
254
|
+
fn: (v) => {
|
|
255
|
+
if (v == null) return true;
|
|
256
|
+
if (typeof v === "string") return v.length === 0;
|
|
257
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
258
|
+
if (typeof v === "object") return Object.keys(v).length === 0;
|
|
259
|
+
return false;
|
|
260
|
+
},
|
|
261
|
+
arity: 1
|
|
262
|
+
};
|
|
263
|
+
var is_null = {
|
|
264
|
+
name: "is_null",
|
|
265
|
+
fn: (v) => v === null || v === void 0,
|
|
266
|
+
arity: 1
|
|
267
|
+
};
|
|
268
|
+
var to_string = {
|
|
269
|
+
name: "to_string",
|
|
270
|
+
fn: (v) => {
|
|
271
|
+
if (v == null) return "";
|
|
272
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
273
|
+
return String(v);
|
|
274
|
+
},
|
|
275
|
+
arity: 1
|
|
276
|
+
};
|
|
277
|
+
var CORE_FUNCTIONS = [
|
|
278
|
+
// Math (8)
|
|
279
|
+
add,
|
|
280
|
+
subtract,
|
|
281
|
+
multiply,
|
|
282
|
+
divide,
|
|
283
|
+
abs,
|
|
284
|
+
round,
|
|
285
|
+
min,
|
|
286
|
+
max,
|
|
287
|
+
// Comparison (6)
|
|
288
|
+
eq,
|
|
289
|
+
neq,
|
|
290
|
+
gt,
|
|
291
|
+
gte,
|
|
292
|
+
lt,
|
|
293
|
+
lte,
|
|
294
|
+
// Logic (5)
|
|
295
|
+
if_fn,
|
|
296
|
+
and,
|
|
297
|
+
or,
|
|
298
|
+
not,
|
|
299
|
+
coalesce,
|
|
300
|
+
// String (6)
|
|
301
|
+
concat,
|
|
302
|
+
upper,
|
|
303
|
+
lower,
|
|
304
|
+
trim,
|
|
305
|
+
format,
|
|
306
|
+
length,
|
|
307
|
+
// Path (3)
|
|
308
|
+
get,
|
|
309
|
+
includes,
|
|
310
|
+
is_defined,
|
|
311
|
+
// Type (3)
|
|
312
|
+
is_empty,
|
|
313
|
+
is_null,
|
|
314
|
+
to_string
|
|
315
|
+
];
|
|
316
|
+
function buildFunctionMap(functions) {
|
|
317
|
+
const map = /* @__PURE__ */ new Map();
|
|
318
|
+
for (const fn of functions) {
|
|
319
|
+
map.set(fn.name, fn.fn);
|
|
320
|
+
}
|
|
321
|
+
return map;
|
|
322
|
+
}
|
|
323
|
+
var MAX_DEPTH = 50;
|
|
324
|
+
var Parser = class {
|
|
325
|
+
pos = 0;
|
|
326
|
+
depth = 0;
|
|
327
|
+
input;
|
|
328
|
+
constructor(input) {
|
|
329
|
+
this.input = input;
|
|
330
|
+
}
|
|
331
|
+
parse() {
|
|
332
|
+
this.skipWhitespace();
|
|
333
|
+
const node = this.parseExpression();
|
|
334
|
+
this.skipWhitespace();
|
|
335
|
+
if (this.pos < this.input.length) {
|
|
336
|
+
throw new Error(`Unexpected character at position ${this.pos}: '${this.input[this.pos]}'`);
|
|
337
|
+
}
|
|
338
|
+
return node;
|
|
339
|
+
}
|
|
340
|
+
guardDepth() {
|
|
341
|
+
if (++this.depth > MAX_DEPTH) {
|
|
342
|
+
throw new Error("Expression too deeply nested");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
parseExpression() {
|
|
346
|
+
this.guardDepth();
|
|
347
|
+
try {
|
|
348
|
+
return this.parseTernary();
|
|
349
|
+
} finally {
|
|
350
|
+
this.depth--;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
parseTernary() {
|
|
354
|
+
let node = this.parseLogicalOr();
|
|
355
|
+
this.skipWhitespace();
|
|
356
|
+
if (this.peek() === "?") {
|
|
357
|
+
this.advance();
|
|
358
|
+
const consequent = this.parseExpression();
|
|
359
|
+
this.skipWhitespace();
|
|
360
|
+
this.expect(":");
|
|
361
|
+
const alternate = this.parseExpression();
|
|
362
|
+
node = { type: "ternary", condition: node, consequent, alternate };
|
|
363
|
+
}
|
|
364
|
+
return node;
|
|
365
|
+
}
|
|
366
|
+
parseLogicalOr() {
|
|
367
|
+
let left = this.parseLogicalAnd();
|
|
368
|
+
this.skipWhitespace();
|
|
369
|
+
while (this.match("||")) {
|
|
370
|
+
const right = this.parseLogicalAnd();
|
|
371
|
+
left = { type: "binary", operator: "||", left, right };
|
|
372
|
+
this.skipWhitespace();
|
|
373
|
+
}
|
|
374
|
+
return left;
|
|
375
|
+
}
|
|
376
|
+
parseLogicalAnd() {
|
|
377
|
+
let left = this.parseEquality();
|
|
378
|
+
this.skipWhitespace();
|
|
379
|
+
while (this.match("&&")) {
|
|
380
|
+
const right = this.parseEquality();
|
|
381
|
+
left = { type: "binary", operator: "&&", left, right };
|
|
382
|
+
this.skipWhitespace();
|
|
383
|
+
}
|
|
384
|
+
return left;
|
|
385
|
+
}
|
|
386
|
+
parseEquality() {
|
|
387
|
+
let left = this.parseComparison();
|
|
388
|
+
this.skipWhitespace();
|
|
389
|
+
while (true) {
|
|
390
|
+
if (this.match("==")) {
|
|
391
|
+
const right = this.parseComparison();
|
|
392
|
+
left = { type: "binary", operator: "==", left, right };
|
|
393
|
+
} else if (this.match("!=")) {
|
|
394
|
+
const right = this.parseComparison();
|
|
395
|
+
left = { type: "binary", operator: "!=", left, right };
|
|
396
|
+
} else {
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
this.skipWhitespace();
|
|
400
|
+
}
|
|
401
|
+
return left;
|
|
402
|
+
}
|
|
403
|
+
parseComparison() {
|
|
404
|
+
let left = this.parseUnary();
|
|
405
|
+
this.skipWhitespace();
|
|
406
|
+
while (true) {
|
|
407
|
+
if (this.match(">=")) {
|
|
408
|
+
const right = this.parseUnary();
|
|
409
|
+
left = { type: "binary", operator: ">=", left, right };
|
|
410
|
+
} else if (this.match("<=")) {
|
|
411
|
+
const right = this.parseUnary();
|
|
412
|
+
left = { type: "binary", operator: "<=", left, right };
|
|
413
|
+
} else if (this.peek() === ">" && !this.lookAhead(">=")) {
|
|
414
|
+
this.advance();
|
|
415
|
+
const right = this.parseUnary();
|
|
416
|
+
left = { type: "binary", operator: ">", left, right };
|
|
417
|
+
} else if (this.peek() === "<" && !this.lookAhead("<=")) {
|
|
418
|
+
this.advance();
|
|
419
|
+
const right = this.parseUnary();
|
|
420
|
+
left = { type: "binary", operator: "<", left, right };
|
|
421
|
+
} else {
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
this.skipWhitespace();
|
|
425
|
+
}
|
|
426
|
+
return left;
|
|
427
|
+
}
|
|
428
|
+
parseUnary() {
|
|
429
|
+
this.skipWhitespace();
|
|
430
|
+
if (this.peek() === "!") {
|
|
431
|
+
this.advance();
|
|
432
|
+
const operand = this.parseUnary();
|
|
433
|
+
return { type: "unary", operator: "!", operand };
|
|
434
|
+
}
|
|
435
|
+
if (this.peek() === "-") {
|
|
436
|
+
const nextChar = this.input[this.pos + 1];
|
|
437
|
+
if (nextChar !== void 0 && (nextChar >= "0" && nextChar <= "9" || nextChar === ".")) {
|
|
438
|
+
return this.parseCallChain();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return this.parseCallChain();
|
|
442
|
+
}
|
|
443
|
+
parseCallChain() {
|
|
444
|
+
let node = this.parsePrimary();
|
|
445
|
+
while (true) {
|
|
446
|
+
this.skipWhitespace();
|
|
447
|
+
if (this.peek() === "(") {
|
|
448
|
+
this.advance();
|
|
449
|
+
const args = this.parseArgList();
|
|
450
|
+
this.expect(")");
|
|
451
|
+
if (node.type === "identifier") {
|
|
452
|
+
node = { type: "call", name: node.name, args };
|
|
453
|
+
} else if (node.type === "path") {
|
|
454
|
+
const name = node.segments.join(".");
|
|
455
|
+
node = { type: "call", name, args };
|
|
456
|
+
} else if (node.type === "member") {
|
|
457
|
+
node = { type: "method_call", object: node.object, method: node.property, args };
|
|
458
|
+
} else {
|
|
459
|
+
throw new Error("Cannot call non-function");
|
|
460
|
+
}
|
|
461
|
+
} else if (this.peek() === ".") {
|
|
462
|
+
this.advance();
|
|
463
|
+
const prop = this.parseIdentifierName();
|
|
464
|
+
node = { type: "member", object: node, property: prop };
|
|
465
|
+
} else {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return node;
|
|
470
|
+
}
|
|
471
|
+
parsePrimary() {
|
|
472
|
+
this.skipWhitespace();
|
|
473
|
+
const ch = this.peek();
|
|
474
|
+
if (ch === "(") {
|
|
475
|
+
this.advance();
|
|
476
|
+
const expr2 = this.parseExpression();
|
|
477
|
+
this.skipWhitespace();
|
|
478
|
+
this.expect(")");
|
|
479
|
+
return expr2;
|
|
480
|
+
}
|
|
481
|
+
if (ch === "'" || ch === '"') {
|
|
482
|
+
return this.parseString();
|
|
483
|
+
}
|
|
484
|
+
if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
485
|
+
return this.parseNumber();
|
|
486
|
+
}
|
|
487
|
+
if (this.isIdentStart(ch)) {
|
|
488
|
+
return this.parseIdentifierOrPath();
|
|
489
|
+
}
|
|
490
|
+
throw new Error(
|
|
491
|
+
`Unexpected character at position ${this.pos}: '${ch || "EOF"}'`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
parseString() {
|
|
495
|
+
const quote = this.advance();
|
|
496
|
+
let value = "";
|
|
497
|
+
while (this.pos < this.input.length && this.peek() !== quote) {
|
|
498
|
+
if (this.peek() === "\\") {
|
|
499
|
+
this.advance();
|
|
500
|
+
const esc = this.advance();
|
|
501
|
+
if (esc === "n") value += "\n";
|
|
502
|
+
else if (esc === "t") value += " ";
|
|
503
|
+
else if (esc === "r") value += "\r";
|
|
504
|
+
else value += esc;
|
|
505
|
+
} else {
|
|
506
|
+
value += this.advance();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (this.pos >= this.input.length) {
|
|
510
|
+
throw new Error("Unterminated string literal");
|
|
511
|
+
}
|
|
512
|
+
this.advance();
|
|
513
|
+
return { type: "string", value };
|
|
514
|
+
}
|
|
515
|
+
parseNumber() {
|
|
516
|
+
let numStr = "";
|
|
517
|
+
if (this.peek() === "-") {
|
|
518
|
+
numStr += this.advance();
|
|
519
|
+
}
|
|
520
|
+
while (this.pos < this.input.length && (this.input[this.pos] >= "0" && this.input[this.pos] <= "9")) {
|
|
521
|
+
numStr += this.advance();
|
|
522
|
+
}
|
|
523
|
+
if (this.peek() === "." && this.pos + 1 < this.input.length && this.input[this.pos + 1] >= "0" && this.input[this.pos + 1] <= "9") {
|
|
524
|
+
numStr += this.advance();
|
|
525
|
+
while (this.pos < this.input.length && (this.input[this.pos] >= "0" && this.input[this.pos] <= "9")) {
|
|
526
|
+
numStr += this.advance();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return { type: "number", value: Number(numStr) };
|
|
530
|
+
}
|
|
531
|
+
parseIdentifierOrPath() {
|
|
532
|
+
const name = this.parseIdentifierName();
|
|
533
|
+
if (name === "true") return { type: "boolean", value: true };
|
|
534
|
+
if (name === "false") return { type: "boolean", value: false };
|
|
535
|
+
if (name === "null") return { type: "null" };
|
|
536
|
+
if (name === "undefined") return { type: "null" };
|
|
537
|
+
return { type: "identifier", name };
|
|
538
|
+
}
|
|
539
|
+
parseIdentifierName() {
|
|
540
|
+
let name = "";
|
|
541
|
+
if (this.peek() === "$") name += this.advance();
|
|
542
|
+
while (this.pos < this.input.length && this.isIdentPart(this.input[this.pos])) {
|
|
543
|
+
name += this.advance();
|
|
544
|
+
}
|
|
545
|
+
if (!name) {
|
|
546
|
+
throw new Error(`Expected identifier at position ${this.pos}`);
|
|
547
|
+
}
|
|
548
|
+
return name;
|
|
549
|
+
}
|
|
550
|
+
parseArgList() {
|
|
551
|
+
this.skipWhitespace();
|
|
552
|
+
if (this.peek() === ")") return [];
|
|
553
|
+
const args = [];
|
|
554
|
+
args.push(this.parseExpression());
|
|
555
|
+
this.skipWhitespace();
|
|
556
|
+
while (this.peek() === ",") {
|
|
557
|
+
this.advance();
|
|
558
|
+
args.push(this.parseExpression());
|
|
559
|
+
this.skipWhitespace();
|
|
560
|
+
}
|
|
561
|
+
return args;
|
|
562
|
+
}
|
|
563
|
+
// Character utilities
|
|
564
|
+
peek() {
|
|
565
|
+
return this.input[this.pos] ?? "";
|
|
566
|
+
}
|
|
567
|
+
advance() {
|
|
568
|
+
return this.input[this.pos++] ?? "";
|
|
569
|
+
}
|
|
570
|
+
match(str) {
|
|
571
|
+
if (this.input.startsWith(str, this.pos)) {
|
|
572
|
+
this.pos += str.length;
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
lookAhead(str) {
|
|
578
|
+
return this.input.startsWith(str, this.pos);
|
|
579
|
+
}
|
|
580
|
+
expect(ch) {
|
|
581
|
+
this.skipWhitespace();
|
|
582
|
+
if (this.peek() !== ch) {
|
|
583
|
+
throw new Error(`Expected '${ch}' at position ${this.pos}, got '${this.peek() || "EOF"}'`);
|
|
584
|
+
}
|
|
585
|
+
this.advance();
|
|
586
|
+
}
|
|
587
|
+
skipWhitespace() {
|
|
588
|
+
while (this.pos < this.input.length && " \n\r".includes(this.input[this.pos])) {
|
|
589
|
+
this.pos++;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
isIdentStart(ch) {
|
|
593
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$";
|
|
594
|
+
}
|
|
595
|
+
isIdentPart(ch) {
|
|
596
|
+
return this.isIdentStart(ch) || ch >= "0" && ch <= "9";
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
function evaluateAST(node, context, fnMap) {
|
|
600
|
+
switch (node.type) {
|
|
601
|
+
case "number":
|
|
602
|
+
return node.value;
|
|
603
|
+
case "string":
|
|
604
|
+
return node.value;
|
|
605
|
+
case "boolean":
|
|
606
|
+
return node.value;
|
|
607
|
+
case "null":
|
|
608
|
+
return null;
|
|
609
|
+
case "identifier":
|
|
610
|
+
return resolvePath(node.name, context);
|
|
611
|
+
case "path":
|
|
612
|
+
return resolvePath(node.segments.join("."), context);
|
|
613
|
+
case "member": {
|
|
614
|
+
const obj = evaluateAST(node.object, context, fnMap);
|
|
615
|
+
if (obj == null || typeof obj !== "object") return void 0;
|
|
616
|
+
return obj[node.property];
|
|
617
|
+
}
|
|
618
|
+
case "call": {
|
|
619
|
+
const fn = fnMap.get(node.name);
|
|
620
|
+
if (!fn) return void 0;
|
|
621
|
+
const args = node.args.map((a) => evaluateAST(a, context, fnMap));
|
|
622
|
+
return fn(...args);
|
|
623
|
+
}
|
|
624
|
+
case "method_call": {
|
|
625
|
+
const obj = evaluateAST(node.object, context, fnMap);
|
|
626
|
+
if (obj != null && typeof obj === "object") {
|
|
627
|
+
const method = obj[node.method];
|
|
628
|
+
if (typeof method === "function") {
|
|
629
|
+
const args = node.args.map((a) => evaluateAST(a, context, fnMap));
|
|
630
|
+
return method.apply(obj, args);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
case "unary": {
|
|
636
|
+
const operand = evaluateAST(node.operand, context, fnMap);
|
|
637
|
+
return !operand;
|
|
638
|
+
}
|
|
639
|
+
case "binary": {
|
|
640
|
+
if (node.operator === "&&") {
|
|
641
|
+
const left2 = evaluateAST(node.left, context, fnMap);
|
|
642
|
+
if (!left2) return left2;
|
|
643
|
+
return evaluateAST(node.right, context, fnMap);
|
|
644
|
+
}
|
|
645
|
+
if (node.operator === "||") {
|
|
646
|
+
const left2 = evaluateAST(node.left, context, fnMap);
|
|
647
|
+
if (left2) return left2;
|
|
648
|
+
return evaluateAST(node.right, context, fnMap);
|
|
649
|
+
}
|
|
650
|
+
const left = evaluateAST(node.left, context, fnMap);
|
|
651
|
+
const right = evaluateAST(node.right, context, fnMap);
|
|
652
|
+
switch (node.operator) {
|
|
653
|
+
// eslint-disable-next-line eqeqeq
|
|
654
|
+
case "==":
|
|
655
|
+
return left == right;
|
|
656
|
+
// eslint-disable-next-line eqeqeq
|
|
657
|
+
case "!=":
|
|
658
|
+
return left != right;
|
|
659
|
+
case ">":
|
|
660
|
+
return Number(left) > Number(right);
|
|
661
|
+
case "<":
|
|
662
|
+
return Number(left) < Number(right);
|
|
663
|
+
case ">=":
|
|
664
|
+
return Number(left) >= Number(right);
|
|
665
|
+
case "<=":
|
|
666
|
+
return Number(left) <= Number(right);
|
|
667
|
+
default:
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
case "ternary": {
|
|
672
|
+
const condition = evaluateAST(node.condition, context, fnMap);
|
|
673
|
+
return condition ? evaluateAST(node.consequent, context, fnMap) : evaluateAST(node.alternate, context, fnMap);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
var MAX_CACHE = 500;
|
|
678
|
+
var astCache = /* @__PURE__ */ new Map();
|
|
679
|
+
function evictIfNeeded() {
|
|
680
|
+
if (astCache.size > MAX_CACHE) {
|
|
681
|
+
const keys = Array.from(astCache.keys());
|
|
682
|
+
const evictCount = Math.floor(MAX_CACHE * 0.25);
|
|
683
|
+
for (let i = 0; i < evictCount; i++) {
|
|
684
|
+
astCache.delete(keys[i]);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function parseAndCache(expr2) {
|
|
689
|
+
const cached = astCache.get(expr2);
|
|
690
|
+
if (cached) return cached;
|
|
691
|
+
const parser = new Parser(expr2);
|
|
692
|
+
const ast = parser.parse();
|
|
693
|
+
astCache.set(expr2, ast);
|
|
694
|
+
evictIfNeeded();
|
|
695
|
+
return ast;
|
|
696
|
+
}
|
|
697
|
+
var TEMPLATE_RE = /\{\{(.+?)\}\}/g;
|
|
698
|
+
function resolvePath(path, context) {
|
|
699
|
+
const parts = path.split(".");
|
|
700
|
+
let current = context;
|
|
701
|
+
for (const part of parts) {
|
|
702
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
703
|
+
current = current[part];
|
|
704
|
+
}
|
|
705
|
+
return current;
|
|
706
|
+
}
|
|
707
|
+
function evaluateExpression2(expr2, context, fnMap) {
|
|
708
|
+
const trimmed = expr2.trim();
|
|
709
|
+
if (trimmed === "true") return true;
|
|
710
|
+
if (trimmed === "false") return false;
|
|
711
|
+
if (trimmed === "null") return null;
|
|
712
|
+
if (trimmed === "undefined") return void 0;
|
|
713
|
+
const num = Number(trimmed);
|
|
714
|
+
if (!isNaN(num) && trimmed !== "") return num;
|
|
715
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
716
|
+
return trimmed.slice(1, -1);
|
|
717
|
+
}
|
|
718
|
+
if (/^[a-zA-Z_$][\w$.]*$/.test(trimmed)) {
|
|
719
|
+
return resolvePath(trimmed, context);
|
|
720
|
+
}
|
|
721
|
+
const ast = parseAndCache(trimmed);
|
|
722
|
+
return evaluateAST(ast, context, fnMap);
|
|
723
|
+
}
|
|
724
|
+
var WEB_FAILURE_POLICIES = {
|
|
725
|
+
VIEW_BINDING: {
|
|
726
|
+
on_error: "return_fallback",
|
|
727
|
+
fallback_value: "",
|
|
728
|
+
log_level: "warn"
|
|
729
|
+
},
|
|
730
|
+
EVENT_REACTION: {
|
|
731
|
+
on_error: "log_and_skip",
|
|
732
|
+
fallback_value: void 0,
|
|
733
|
+
log_level: "error"
|
|
734
|
+
},
|
|
735
|
+
DURING_ACTION: {
|
|
736
|
+
on_error: "log_and_skip",
|
|
737
|
+
fallback_value: void 0,
|
|
738
|
+
log_level: "error"
|
|
739
|
+
},
|
|
740
|
+
CONDITIONAL_VISIBILITY: {
|
|
741
|
+
on_error: "return_fallback",
|
|
742
|
+
fallback_value: true,
|
|
743
|
+
// Show by default if condition fails
|
|
744
|
+
log_level: "warn"
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
function createEvaluator(config) {
|
|
748
|
+
const allFunctions = [...CORE_FUNCTIONS, ...config.functions];
|
|
749
|
+
const fnMap = buildFunctionMap(allFunctions);
|
|
750
|
+
const policy = config.failurePolicy;
|
|
751
|
+
function handleError(expr2, error) {
|
|
752
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
753
|
+
if (policy.log_level === "error") {
|
|
754
|
+
console.error(`[player-core] Expression error: "${expr2}" \u2014 ${message}`);
|
|
755
|
+
} else if (policy.log_level === "warn") {
|
|
756
|
+
console.warn(`[player-core] Expression error: "${expr2}" \u2014 ${message}`);
|
|
757
|
+
}
|
|
758
|
+
switch (policy.on_error) {
|
|
759
|
+
case "throw":
|
|
760
|
+
throw error;
|
|
761
|
+
case "return_fallback":
|
|
762
|
+
return { value: policy.fallback_value, status: "fallback", error: message };
|
|
763
|
+
case "log_and_skip":
|
|
764
|
+
default:
|
|
765
|
+
return { value: policy.fallback_value, status: "error", error: message };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
evaluate(expression, context) {
|
|
770
|
+
try {
|
|
771
|
+
const value = evaluateExpression2(expression, context, fnMap);
|
|
772
|
+
return { value, status: "ok" };
|
|
773
|
+
} catch (error) {
|
|
774
|
+
return handleError(expression, error);
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
evaluateTemplate(template, context) {
|
|
778
|
+
try {
|
|
779
|
+
if (!template.includes("{{")) {
|
|
780
|
+
return { value: template, status: "ok" };
|
|
781
|
+
}
|
|
782
|
+
const result = template.replace(TEMPLATE_RE, (_match, expr2) => {
|
|
783
|
+
const value = evaluateExpression2(expr2, context, fnMap);
|
|
784
|
+
return value != null ? String(value) : "";
|
|
785
|
+
});
|
|
786
|
+
return { value: result, status: "ok" };
|
|
787
|
+
} catch (error) {
|
|
788
|
+
return handleError(template, error);
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
validate(expression) {
|
|
792
|
+
const errors = [];
|
|
793
|
+
try {
|
|
794
|
+
parseAndCache(expression);
|
|
795
|
+
} catch (e) {
|
|
796
|
+
errors.push(e instanceof Error ? e.message : String(e));
|
|
797
|
+
}
|
|
798
|
+
return { valid: errors.length === 0, errors };
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
var MAX_AUTO_CHAIN = 10;
|
|
803
|
+
var StateMachine = class {
|
|
804
|
+
evaluator;
|
|
805
|
+
actionHandlers;
|
|
806
|
+
listeners = /* @__PURE__ */ new Set();
|
|
807
|
+
instance;
|
|
808
|
+
constructor(definition, initialData = {}, config) {
|
|
809
|
+
this.evaluator = config.evaluator;
|
|
810
|
+
this.actionHandlers = config.actionHandlers ?? /* @__PURE__ */ new Map();
|
|
811
|
+
const startState = definition.states.find((s) => s.type === "START");
|
|
812
|
+
if (!startState) {
|
|
813
|
+
throw new Error(`No START state found in definition ${definition.slug}`);
|
|
814
|
+
}
|
|
815
|
+
this.instance = {
|
|
816
|
+
definition,
|
|
817
|
+
current_state: startState.name,
|
|
818
|
+
state_data: { ...initialData },
|
|
819
|
+
memory: {},
|
|
820
|
+
status: "ACTIVE"
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/** Get the current instance snapshot (immutable copy) */
|
|
824
|
+
getSnapshot() {
|
|
825
|
+
return { ...this.instance, state_data: { ...this.instance.state_data }, memory: { ...this.instance.memory } };
|
|
826
|
+
}
|
|
827
|
+
/** Get current state name */
|
|
828
|
+
get currentState() {
|
|
829
|
+
return this.instance.current_state;
|
|
830
|
+
}
|
|
831
|
+
/** Get current state_data */
|
|
832
|
+
get stateData() {
|
|
833
|
+
return this.instance.state_data;
|
|
834
|
+
}
|
|
835
|
+
/** Get current status */
|
|
836
|
+
get status() {
|
|
837
|
+
return this.instance.status;
|
|
838
|
+
}
|
|
839
|
+
/** Subscribe to state machine events */
|
|
840
|
+
on(listener) {
|
|
841
|
+
this.listeners.add(listener);
|
|
842
|
+
return () => this.listeners.delete(listener);
|
|
843
|
+
}
|
|
844
|
+
/** Register an action handler */
|
|
845
|
+
registerAction(type, handler) {
|
|
846
|
+
this.actionHandlers.set(type, handler);
|
|
847
|
+
}
|
|
848
|
+
/** Execute a named transition */
|
|
849
|
+
async transition(transitionName, data) {
|
|
850
|
+
if (this.instance.status !== "ACTIVE") {
|
|
851
|
+
return {
|
|
852
|
+
success: false,
|
|
853
|
+
from_state: this.instance.current_state,
|
|
854
|
+
to_state: this.instance.current_state,
|
|
855
|
+
actions_executed: [],
|
|
856
|
+
error: `Cannot transition: instance status is ${this.instance.status}`
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
const transition2 = this.instance.definition.transitions.find(
|
|
860
|
+
(t) => t.name === transitionName && t.from.includes(this.instance.current_state)
|
|
861
|
+
);
|
|
862
|
+
if (!transition2) {
|
|
863
|
+
return {
|
|
864
|
+
success: false,
|
|
865
|
+
from_state: this.instance.current_state,
|
|
866
|
+
to_state: this.instance.current_state,
|
|
867
|
+
actions_executed: [],
|
|
868
|
+
error: `Transition "${transitionName}" not valid from state "${this.instance.current_state}"`
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
if (data) {
|
|
872
|
+
this.instance.state_data = { ...this.instance.state_data, ...data };
|
|
873
|
+
}
|
|
874
|
+
if (transition2.conditions && transition2.conditions.length > 0) {
|
|
875
|
+
const ctx = this.buildContext();
|
|
876
|
+
for (const condition of transition2.conditions) {
|
|
877
|
+
const result2 = this.evaluator.evaluate(condition, ctx);
|
|
878
|
+
if (!result2.value) {
|
|
879
|
+
return {
|
|
880
|
+
success: false,
|
|
881
|
+
from_state: this.instance.current_state,
|
|
882
|
+
to_state: this.instance.current_state,
|
|
883
|
+
actions_executed: [],
|
|
884
|
+
error: `Transition condition not met: ${condition}`
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const result = await this.executeTransition(transition2);
|
|
890
|
+
if (result.success) {
|
|
891
|
+
await this.drainAutoTransitions();
|
|
892
|
+
}
|
|
893
|
+
return result;
|
|
894
|
+
}
|
|
895
|
+
/** Update state_data directly (for on_event set_field actions) */
|
|
896
|
+
setField(field2, value) {
|
|
897
|
+
this.instance.state_data = { ...this.instance.state_data, [field2]: value };
|
|
898
|
+
}
|
|
899
|
+
/** Update memory */
|
|
900
|
+
setMemory(key, value) {
|
|
901
|
+
this.instance.memory = { ...this.instance.memory, [key]: value };
|
|
902
|
+
}
|
|
903
|
+
/** Get available transitions from the current state */
|
|
904
|
+
getAvailableTransitions() {
|
|
905
|
+
return this.instance.definition.transitions.filter(
|
|
906
|
+
(t) => t.from.includes(this.instance.current_state) && !t.auto
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
/** Get the current state definition */
|
|
910
|
+
getCurrentStateDefinition() {
|
|
911
|
+
return this.instance.definition.states.find((s) => s.name === this.instance.current_state);
|
|
912
|
+
}
|
|
913
|
+
// ===========================================================================
|
|
914
|
+
// Private implementation
|
|
915
|
+
// ===========================================================================
|
|
916
|
+
async executeTransition(transition2) {
|
|
917
|
+
const fromState = this.instance.current_state;
|
|
918
|
+
const allActionsExecuted = [];
|
|
919
|
+
const fromStateDef = this.getCurrentStateDefinition();
|
|
920
|
+
if (fromStateDef?.on_exit) {
|
|
921
|
+
await this.executeActions(fromStateDef.on_exit, allActionsExecuted);
|
|
922
|
+
}
|
|
923
|
+
this.emit({
|
|
924
|
+
type: "state_exit",
|
|
925
|
+
instance_id: this.instance.definition.id,
|
|
926
|
+
from_state: fromState
|
|
927
|
+
});
|
|
928
|
+
if (transition2.actions) {
|
|
929
|
+
await this.executeActions(transition2.actions, allActionsExecuted);
|
|
930
|
+
}
|
|
931
|
+
this.instance.current_state = transition2.to;
|
|
932
|
+
const toStateDef = this.instance.definition.states.find((s) => s.name === transition2.to);
|
|
933
|
+
if (toStateDef?.type === "END") {
|
|
934
|
+
this.instance.status = "COMPLETED";
|
|
935
|
+
} else if (toStateDef?.type === "CANCELLED") {
|
|
936
|
+
this.instance.status = "CANCELLED";
|
|
937
|
+
}
|
|
938
|
+
this.emit({
|
|
939
|
+
type: "state_enter",
|
|
940
|
+
instance_id: this.instance.definition.id,
|
|
941
|
+
to_state: transition2.to
|
|
942
|
+
});
|
|
943
|
+
if (toStateDef?.on_enter) {
|
|
944
|
+
await this.executeActions(toStateDef.on_enter, allActionsExecuted);
|
|
945
|
+
}
|
|
946
|
+
this.emit({
|
|
947
|
+
type: "transition",
|
|
948
|
+
instance_id: this.instance.definition.id,
|
|
949
|
+
from_state: fromState,
|
|
950
|
+
to_state: transition2.to
|
|
951
|
+
});
|
|
952
|
+
return {
|
|
953
|
+
success: true,
|
|
954
|
+
from_state: fromState,
|
|
955
|
+
to_state: transition2.to,
|
|
956
|
+
actions_executed: allActionsExecuted
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
async drainAutoTransitions() {
|
|
960
|
+
for (let depth = 0; depth < MAX_AUTO_CHAIN; depth++) {
|
|
961
|
+
if (this.instance.status !== "ACTIVE") break;
|
|
962
|
+
const autoTransition = this.findMatchingAutoTransition();
|
|
963
|
+
if (!autoTransition) break;
|
|
964
|
+
const result = await this.executeTransition(autoTransition);
|
|
965
|
+
if (!result.success) break;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
findMatchingAutoTransition() {
|
|
969
|
+
const candidates = this.instance.definition.transitions.filter(
|
|
970
|
+
(t) => t.auto && t.from.includes(this.instance.current_state)
|
|
971
|
+
);
|
|
972
|
+
const ctx = this.buildContext();
|
|
973
|
+
for (const candidate of candidates) {
|
|
974
|
+
if (!candidate.conditions || candidate.conditions.length === 0) {
|
|
975
|
+
return candidate;
|
|
976
|
+
}
|
|
977
|
+
const allMet = candidate.conditions.every((condition) => {
|
|
978
|
+
const result = this.evaluator.evaluate(condition, ctx);
|
|
979
|
+
return result.value === true;
|
|
980
|
+
});
|
|
981
|
+
if (allMet) return candidate;
|
|
982
|
+
}
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
async executeActions(actions, collector) {
|
|
986
|
+
const ctx = this.buildContext();
|
|
987
|
+
for (const action2 of actions) {
|
|
988
|
+
if (action2.condition) {
|
|
989
|
+
const condResult = this.evaluator.evaluate(action2.condition, ctx);
|
|
990
|
+
if (!condResult.value) continue;
|
|
991
|
+
}
|
|
992
|
+
const handler = this.actionHandlers.get(action2.type);
|
|
993
|
+
if (handler) {
|
|
994
|
+
try {
|
|
995
|
+
await handler(action2, ctx);
|
|
996
|
+
collector.push(action2);
|
|
997
|
+
this.emit({
|
|
998
|
+
type: "action_executed",
|
|
999
|
+
instance_id: this.instance.definition.id,
|
|
1000
|
+
action: action2
|
|
1001
|
+
});
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
this.emit({
|
|
1004
|
+
type: "error",
|
|
1005
|
+
instance_id: this.instance.definition.id,
|
|
1006
|
+
action: action2,
|
|
1007
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
buildContext() {
|
|
1014
|
+
return {
|
|
1015
|
+
state_data: this.instance.state_data,
|
|
1016
|
+
memory: this.instance.memory,
|
|
1017
|
+
current_state: this.instance.current_state,
|
|
1018
|
+
status: this.instance.status,
|
|
1019
|
+
// Spread state_data for direct field access (e.g., "title" instead of "state_data.title")
|
|
1020
|
+
...this.instance.state_data
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
emit(event) {
|
|
1024
|
+
for (const listener of this.listeners) {
|
|
1025
|
+
try {
|
|
1026
|
+
listener(event);
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
var patternCache = /* @__PURE__ */ new Map();
|
|
1033
|
+
var MAX_CACHE2 = 200;
|
|
1034
|
+
function compilePattern(pattern) {
|
|
1035
|
+
const cached = patternCache.get(pattern);
|
|
1036
|
+
if (cached) return cached;
|
|
1037
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<DOUBLESTAR>>").replace(/\*/g, "[^:.]+").replace(/<<DOUBLESTAR>>/g, ".*");
|
|
1038
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
1039
|
+
if (patternCache.size >= MAX_CACHE2) {
|
|
1040
|
+
const firstKey = patternCache.keys().next().value;
|
|
1041
|
+
if (firstKey) patternCache.delete(firstKey);
|
|
1042
|
+
}
|
|
1043
|
+
patternCache.set(pattern, regex);
|
|
1044
|
+
return regex;
|
|
1045
|
+
}
|
|
1046
|
+
function matchTopic(pattern, topic) {
|
|
1047
|
+
return pattern.test(topic);
|
|
1048
|
+
}
|
|
1049
|
+
var EventBus = class {
|
|
1050
|
+
subscriptions = [];
|
|
1051
|
+
/**
|
|
1052
|
+
* Subscribe to events matching a glob pattern.
|
|
1053
|
+
* Returns an unsubscribe function.
|
|
1054
|
+
*/
|
|
1055
|
+
subscribe(pattern, handler) {
|
|
1056
|
+
const regex = compilePattern(pattern);
|
|
1057
|
+
const subscription = { pattern, regex, handler };
|
|
1058
|
+
this.subscriptions.push(subscription);
|
|
1059
|
+
return () => {
|
|
1060
|
+
const idx = this.subscriptions.indexOf(subscription);
|
|
1061
|
+
if (idx !== -1) this.subscriptions.splice(idx, 1);
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Publish an event. All matching subscriptions fire (async).
|
|
1066
|
+
* Errors in handlers are caught and logged, never propagated.
|
|
1067
|
+
*/
|
|
1068
|
+
async publish(topic, payload = {}) {
|
|
1069
|
+
const event = { topic, payload };
|
|
1070
|
+
for (const sub of this.subscriptions) {
|
|
1071
|
+
if (matchTopic(sub.regex, topic)) {
|
|
1072
|
+
try {
|
|
1073
|
+
await sub.handler(event);
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
console.warn(
|
|
1076
|
+
`[player-core] Event handler error for pattern "${sub.pattern}" on topic "${topic}":`,
|
|
1077
|
+
error
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Publish synchronously (fire-and-forget).
|
|
1085
|
+
* Useful when you don't need to await handler completion.
|
|
1086
|
+
*/
|
|
1087
|
+
emit(topic, payload = {}) {
|
|
1088
|
+
void this.publish(topic, payload);
|
|
1089
|
+
}
|
|
1090
|
+
/** Get count of active subscriptions */
|
|
1091
|
+
get size() {
|
|
1092
|
+
return this.subscriptions.length;
|
|
1093
|
+
}
|
|
1094
|
+
/** Remove all subscriptions */
|
|
1095
|
+
clear() {
|
|
1096
|
+
this.subscriptions.length = 0;
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
var ActionDispatcher = class {
|
|
1100
|
+
handlers = /* @__PURE__ */ new Map();
|
|
1101
|
+
/** Register a handler for an action type */
|
|
1102
|
+
register(type, handler) {
|
|
1103
|
+
this.handlers.set(type, handler);
|
|
1104
|
+
}
|
|
1105
|
+
/** Unregister a handler */
|
|
1106
|
+
unregister(type) {
|
|
1107
|
+
this.handlers.delete(type);
|
|
1108
|
+
}
|
|
1109
|
+
/** Check if a handler is registered for the given type */
|
|
1110
|
+
has(type) {
|
|
1111
|
+
return this.handlers.has(type);
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Execute a list of actions sequentially.
|
|
1115
|
+
* Each action's condition is evaluated first (if present).
|
|
1116
|
+
* Missing handlers are skipped with a warning.
|
|
1117
|
+
*/
|
|
1118
|
+
async execute(actions, context, evaluator) {
|
|
1119
|
+
const results = [];
|
|
1120
|
+
for (const action2 of actions) {
|
|
1121
|
+
if (action2.condition && evaluator) {
|
|
1122
|
+
const condResult = evaluator.evaluate(action2.condition, context);
|
|
1123
|
+
if (!condResult.value) continue;
|
|
1124
|
+
}
|
|
1125
|
+
const handler = this.handlers.get(action2.type);
|
|
1126
|
+
if (!handler) {
|
|
1127
|
+
console.warn(`[player-core] No handler registered for action type "${action2.type}" \u2014 unsupported action`);
|
|
1128
|
+
results.push({ type: action2.type, success: false, error: `unsupported action: "${action2.type}"` });
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
await handler(action2.config, context);
|
|
1133
|
+
results.push({ type: action2.type, success: true });
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1136
|
+
console.warn(`[player-core] Action "${action2.type}" failed: ${message}`);
|
|
1137
|
+
results.push({ type: action2.type, success: false, error: message });
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return results;
|
|
1141
|
+
}
|
|
1142
|
+
/** Get count of registered handlers */
|
|
1143
|
+
get size() {
|
|
1144
|
+
return this.handlers.size;
|
|
1145
|
+
}
|
|
1146
|
+
/** Get all registered action type names */
|
|
1147
|
+
getRegisteredTypes() {
|
|
1148
|
+
return Array.from(this.handlers.keys());
|
|
1149
|
+
}
|
|
1150
|
+
/** Remove all handlers */
|
|
1151
|
+
clear() {
|
|
1152
|
+
this.handlers.clear();
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
|
|
69
1156
|
// src/core/WorkflowRuntime.ts
|
|
70
|
-
import {
|
|
71
|
-
StateMachine,
|
|
72
|
-
EventBus,
|
|
73
|
-
ActionDispatcher,
|
|
74
|
-
createEvaluator,
|
|
75
|
-
WEB_FAILURE_POLICIES
|
|
76
|
-
} from "@mmapp/player-core";
|
|
77
1157
|
var WorkflowRuntime = class {
|
|
78
1158
|
sm;
|
|
79
1159
|
eventBus;
|
|
@@ -3303,13 +4383,13 @@ function inState(state2) {
|
|
|
3303
4383
|
function notInState(state2) {
|
|
3304
4384
|
return { type: "expression", expression: `current_state != "${state2}"` };
|
|
3305
4385
|
}
|
|
3306
|
-
function
|
|
4386
|
+
function and2(...conditions) {
|
|
3307
4387
|
return { AND: conditions.map(normalize) };
|
|
3308
4388
|
}
|
|
3309
|
-
function
|
|
4389
|
+
function or2(...conditions) {
|
|
3310
4390
|
return { OR: conditions.map(normalize) };
|
|
3311
4391
|
}
|
|
3312
|
-
function
|
|
4392
|
+
function not2(condition) {
|
|
3313
4393
|
const c = normalize(condition);
|
|
3314
4394
|
if (c.type === "expression" && c.expression) {
|
|
3315
4395
|
return { type: "expression", expression: `NOT(${c.expression})` };
|
|
@@ -5860,13 +6940,6 @@ function describeModel(def) {
|
|
|
5860
6940
|
|
|
5861
6941
|
// src/hooks/usePlayer.ts
|
|
5862
6942
|
import { useCallback as useCallback20, useEffect as useEffect21, useMemo as useMemo19, useRef as useRef23, useState as useState21 } from "react";
|
|
5863
|
-
import {
|
|
5864
|
-
StateMachine as StateMachine2,
|
|
5865
|
-
EventBus as EventBus2,
|
|
5866
|
-
ActionDispatcher as ActionDispatcher2,
|
|
5867
|
-
createEvaluator as createEvaluator2,
|
|
5868
|
-
WEB_FAILURE_POLICIES as WEB_FAILURE_POLICIES2
|
|
5869
|
-
} from "@mmapp/player-core";
|
|
5870
6943
|
|
|
5871
6944
|
// src/logger.ts
|
|
5872
6945
|
var debugEnabled = false;
|
|
@@ -5912,9 +6985,9 @@ function usePlayer(config) {
|
|
|
5912
6985
|
if (config.debug) setPlayerDebug(true);
|
|
5913
6986
|
}, [config.debug]);
|
|
5914
6987
|
const evaluator = useMemo19(() => {
|
|
5915
|
-
return
|
|
6988
|
+
return createEvaluator({
|
|
5916
6989
|
functions: config.functions ?? [],
|
|
5917
|
-
failurePolicy:
|
|
6990
|
+
failurePolicy: WEB_FAILURE_POLICIES.EVENT_REACTION
|
|
5918
6991
|
});
|
|
5919
6992
|
}, [config.definition.id]);
|
|
5920
6993
|
const engine = useMemo19(() => {
|
|
@@ -5945,14 +7018,14 @@ function usePlayer(config) {
|
|
|
5945
7018
|
});
|
|
5946
7019
|
}
|
|
5947
7020
|
});
|
|
5948
|
-
const sm2 = new
|
|
7021
|
+
const sm2 = new StateMachine(
|
|
5949
7022
|
config.definition,
|
|
5950
7023
|
config.initialData ?? {},
|
|
5951
7024
|
{ evaluator, actionHandlers }
|
|
5952
7025
|
);
|
|
5953
7026
|
smRef = sm2;
|
|
5954
|
-
const eventBus2 = new
|
|
5955
|
-
const dispatcher = new
|
|
7027
|
+
const eventBus2 = new EventBus();
|
|
7028
|
+
const dispatcher = new ActionDispatcher();
|
|
5956
7029
|
dispatcher.register("set_field", (cfg) => {
|
|
5957
7030
|
if (smRef && typeof cfg.field === "string") {
|
|
5958
7031
|
smRef.setField(cfg.field, cfg.value);
|
|
@@ -7398,7 +8471,7 @@ export {
|
|
|
7398
8471
|
actor,
|
|
7399
8472
|
after,
|
|
7400
8473
|
allowTransition,
|
|
7401
|
-
and,
|
|
8474
|
+
and2 as and,
|
|
7402
8475
|
applyMixins,
|
|
7403
8476
|
approval,
|
|
7404
8477
|
assertModelValid,
|
|
@@ -7486,11 +8559,11 @@ export {
|
|
|
7486
8559
|
model,
|
|
7487
8560
|
named,
|
|
7488
8561
|
normalizeDefinition,
|
|
7489
|
-
not,
|
|
8562
|
+
not2 as not,
|
|
7490
8563
|
notInState,
|
|
7491
8564
|
notify,
|
|
7492
8565
|
on,
|
|
7493
|
-
or,
|
|
8566
|
+
or2 as or,
|
|
7494
8567
|
orchestration,
|
|
7495
8568
|
patch,
|
|
7496
8569
|
pipe,
|