@almadar/runtime 6.32.0 → 6.34.0
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/{OrbitalServerRuntime-DpAwIrmv.d.ts → OrbitalServerRuntime-BrHSXDfp.d.ts} +63 -19
- package/dist/OrbitalServerRuntime.d.ts +2 -2
- package/dist/OrbitalServerRuntime.js +238 -156
- package/dist/ServerBridge.d.ts +1 -1
- package/dist/chunk-O5VGKPTG.js +435 -0
- package/dist/chunk-OQJIK6PZ.js +163 -0
- package/dist/chunk-SCRAHWOC.js +82 -0
- package/dist/{chunk-YFH577FH.js → chunk-TRA44DDY.js} +753 -99
- package/dist/createOsHandlers.d.ts +1 -1
- package/dist/{external-loader-OPXVTNC4.js → external-loader-FNK5AU6U.js} +27 -1
- package/dist/index.d.ts +43 -20
- package/dist/index.js +95 -5
- package/dist/mockRandom.d.ts +54 -0
- package/dist/mockRandom.js +2 -0
- package/dist/{types-D-9feVsj.d.ts → types-C8RsO0xa.d.ts} +54 -4
- package/dist/ui/index.d.ts +419 -0
- package/dist/ui/index.js +3 -0
- package/package.json +18 -8
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { parseCron, cronMinuteKey, cronMatches } from './chunk-OU3ITB5S.js';
|
|
2
|
+
import { seedRandom, randomArrayElement, randomInt, shuffleArray, randomPastDate, randomBoolean, randomRecentDate, randomUuid, randomPhone, randomUrl, randomEmail, randomWords } from './chunk-OQJIK6PZ.js';
|
|
3
|
+
import { collectTraitRefsFromValue, collectTraitRefsFromEffects } from './chunk-SCRAHWOC.js';
|
|
2
4
|
import { createLogger, setNamespaceLevel } from '@almadar/logger';
|
|
3
|
-
import { resolveBinding, evaluate,
|
|
5
|
+
import { createMinimalContext, resolveBinding, evaluate, evaluateGuard, SExpressionEvaluator } from '@almadar/evaluator';
|
|
4
6
|
export { createMinimalContext } from '@almadar/evaluator';
|
|
5
7
|
import { isKnownStdOperator } from '@almadar/std/registry';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
+
import { OrbitalSchemaSchema, isInlineTrait, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef, isReferenceConfigType, configRefEventKnob, normalizeCallSiteConfigToValues, resolveConfigRefEventName } from '@almadar/core';
|
|
9
|
+
export { normalizeCallSiteConfigToValues } from '@almadar/core';
|
|
8
10
|
|
|
9
11
|
var log = createLogger("almadar:runtime:eventbus");
|
|
10
12
|
var EventBus = class {
|
|
@@ -24,7 +26,7 @@ var EventBus = class {
|
|
|
24
26
|
* beyond `maxDepth`, the event is dropped and an error is logged.
|
|
25
27
|
* This prevents infinite loops from circular emit/listen chains.
|
|
26
28
|
*/
|
|
27
|
-
emit(type, payload, source) {
|
|
29
|
+
emit(type, payload, source, routingKey) {
|
|
28
30
|
if (this.depth >= this.maxDepth) {
|
|
29
31
|
log.error("circular event loop dropped", { type, depth: this.depth, maxDepth: this.maxDepth });
|
|
30
32
|
return;
|
|
@@ -35,12 +37,13 @@ var EventBus = class {
|
|
|
35
37
|
timestamp: Date.now(),
|
|
36
38
|
source
|
|
37
39
|
};
|
|
38
|
-
const
|
|
40
|
+
const deliveryKey = routingKey ?? type;
|
|
41
|
+
const listeners = this.listeners.get(deliveryKey);
|
|
39
42
|
const listenerCount = listeners?.size ?? 0;
|
|
40
43
|
if (listenerCount > 0) {
|
|
41
44
|
log.debug("emit", { type, listenerCount, depth: this.depth });
|
|
42
45
|
} else {
|
|
43
|
-
log.
|
|
46
|
+
log.debug("emit no listeners", { type });
|
|
44
47
|
}
|
|
45
48
|
this.depth++;
|
|
46
49
|
try {
|
|
@@ -289,6 +292,33 @@ var bindLog = createLogger("almadar:runtime:bindings");
|
|
|
289
292
|
setNamespaceLevel("almadar:runtime:bindings", "WARN");
|
|
290
293
|
var renderLog = createLogger("almadar:runtime:render-ui");
|
|
291
294
|
var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
|
|
295
|
+
var CALLSITE_PAYLOAD_PREFIX = "@callsitePayload.";
|
|
296
|
+
function payloadValueToConfigValue(v) {
|
|
297
|
+
if (v === null || v === void 0) return null;
|
|
298
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return v;
|
|
299
|
+
if (v instanceof Date) return v.toISOString();
|
|
300
|
+
if (Array.isArray(v)) return v.map(payloadValueToConfigValue);
|
|
301
|
+
if (typeof v === "object") {
|
|
302
|
+
const obj = {};
|
|
303
|
+
for (const [k, val] of Object.entries(v)) obj[k] = payloadValueToConfigValue(val);
|
|
304
|
+
return obj;
|
|
305
|
+
}
|
|
306
|
+
return String(v);
|
|
307
|
+
}
|
|
308
|
+
function resolveCallSitePayloadCaptures(config, payload) {
|
|
309
|
+
let ctx;
|
|
310
|
+
const out = {};
|
|
311
|
+
for (const [key, value] of Object.entries(config)) {
|
|
312
|
+
if (typeof value === "string" && value.startsWith(CALLSITE_PAYLOAD_PREFIX)) {
|
|
313
|
+
const field = value.slice(CALLSITE_PAYLOAD_PREFIX.length);
|
|
314
|
+
if (!ctx) ctx = createMinimalContext({}, payload ?? {}, "idle");
|
|
315
|
+
out[key] = payloadValueToConfigValue(resolveBinding(`@payload.${field}`, ctx));
|
|
316
|
+
} else {
|
|
317
|
+
out[key] = value;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
292
322
|
function isClientOnlyBinding(value) {
|
|
293
323
|
if (!value.startsWith("@")) return false;
|
|
294
324
|
const afterAt = value.slice(1);
|
|
@@ -365,6 +395,7 @@ function interpolateValue(value, ctx) {
|
|
|
365
395
|
}
|
|
366
396
|
return value;
|
|
367
397
|
}
|
|
398
|
+
var inFlightConfigRecursions = /* @__PURE__ */ new Set();
|
|
368
399
|
function interpolateString(value, ctx) {
|
|
369
400
|
if (value.startsWith("@") && isPureBinding(value)) {
|
|
370
401
|
if (isClientOnlyBinding(value)) {
|
|
@@ -373,6 +404,15 @@ function interpolateString(value, ctx) {
|
|
|
373
404
|
}
|
|
374
405
|
const resolved = resolveBinding(value, ctx);
|
|
375
406
|
bindLog.debug("resolve", { binding: value, resolvedType: typeof resolved });
|
|
407
|
+
if (value.startsWith("@config.") && resolved !== null && typeof resolved === "object" && containsBindings(resolved) && !inFlightConfigRecursions.has(value)) {
|
|
408
|
+
inFlightConfigRecursions.add(value);
|
|
409
|
+
try {
|
|
410
|
+
bindLog.debug("resolve:config-recurse", { binding: value });
|
|
411
|
+
return interpolateValue(resolved, ctx);
|
|
412
|
+
} finally {
|
|
413
|
+
inFlightConfigRecursions.delete(value);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
376
416
|
return resolved;
|
|
377
417
|
}
|
|
378
418
|
if (value.includes("@")) {
|
|
@@ -414,12 +454,25 @@ function interpolateArray(value, ctx) {
|
|
|
414
454
|
let anyChanged = false;
|
|
415
455
|
for (let i = 0; i < value.length; i++) {
|
|
416
456
|
const item = value[i];
|
|
457
|
+
if (Array.isArray(item) && isRenderChildrenMap(item)) {
|
|
458
|
+
const expanded = evaluate(item, ctx);
|
|
459
|
+
if (Array.isArray(expanded)) {
|
|
460
|
+
for (const node of expanded) mapped.push(node);
|
|
461
|
+
}
|
|
462
|
+
anyChanged = true;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
417
465
|
const interpolated = interpolateValue(item, ctx);
|
|
418
466
|
mapped.push(interpolated);
|
|
419
467
|
if (interpolated !== item) anyChanged = true;
|
|
420
468
|
}
|
|
421
469
|
return anyChanged ? mapped : value;
|
|
422
470
|
}
|
|
471
|
+
function isRenderChildrenMap(value) {
|
|
472
|
+
if (value.length !== 3 || value[0] !== "array/map") return false;
|
|
473
|
+
const lambda = value[2];
|
|
474
|
+
return Array.isArray(lambda) && lambda.length === 3 && lambda[0] === "fn" && typeof lambda[1] === "string";
|
|
475
|
+
}
|
|
423
476
|
function isSExpression(value) {
|
|
424
477
|
if (value.length === 0) return false;
|
|
425
478
|
const first = value[0];
|
|
@@ -497,19 +550,21 @@ function createInitialTraitState(trait) {
|
|
|
497
550
|
context: {}
|
|
498
551
|
};
|
|
499
552
|
}
|
|
500
|
-
function findMatchingTransitions(trait, currentState, eventKey) {
|
|
553
|
+
function findMatchingTransitions(trait, currentState, eventKey, eventId) {
|
|
501
554
|
if (!trait.transitions || trait.transitions.length === 0) {
|
|
502
555
|
return [];
|
|
503
556
|
}
|
|
504
557
|
return trait.transitions.filter((t) => {
|
|
505
|
-
|
|
506
|
-
|
|
558
|
+
const fromMatches = Array.isArray(t.from) ? t.from.includes(currentState) : t.from === currentState;
|
|
559
|
+
if (!fromMatches) return false;
|
|
560
|
+
if (eventId && t.eventId) {
|
|
561
|
+
return t.eventId === eventId;
|
|
507
562
|
}
|
|
508
|
-
return t.
|
|
563
|
+
return t.event === eventKey;
|
|
509
564
|
});
|
|
510
565
|
}
|
|
511
|
-
function findTransition(trait, currentState, eventKey) {
|
|
512
|
-
return findMatchingTransitions(trait, currentState, eventKey)[0];
|
|
566
|
+
function findTransition(trait, currentState, eventKey, eventId) {
|
|
567
|
+
return findMatchingTransitions(trait, currentState, eventKey, eventId)[0];
|
|
513
568
|
}
|
|
514
569
|
function normalizeEventKey(eventKey) {
|
|
515
570
|
if (!eventKey) return "";
|
|
@@ -520,6 +575,7 @@ function processEvent(options) {
|
|
|
520
575
|
traitState,
|
|
521
576
|
trait,
|
|
522
577
|
eventKey,
|
|
578
|
+
eventId,
|
|
523
579
|
payload,
|
|
524
580
|
entityData,
|
|
525
581
|
config,
|
|
@@ -528,7 +584,7 @@ function processEvent(options) {
|
|
|
528
584
|
contextExtensions
|
|
529
585
|
} = options;
|
|
530
586
|
const normalizedEvent = normalizeEventKey(eventKey);
|
|
531
|
-
const candidates = findMatchingTransitions(trait, traitState.currentState, normalizedEvent);
|
|
587
|
+
const candidates = findMatchingTransitions(trait, traitState.currentState, normalizedEvent, eventId);
|
|
532
588
|
if (candidates.length === 0) {
|
|
533
589
|
smLog.debug("noTransition", { trait: trait.name, event: normalizedEvent, currentState: traitState.currentState });
|
|
534
590
|
return {
|
|
@@ -647,6 +703,15 @@ function compositeKey(traitName, scope) {
|
|
|
647
703
|
}
|
|
648
704
|
var StateMachineManager = class {
|
|
649
705
|
traits = /* @__PURE__ */ new Map();
|
|
706
|
+
/**
|
|
707
|
+
* V4 identity index: trait id → trait name. Populated for traits that
|
|
708
|
+
* carry an `id` (ledger-backed schemas). Lets callers resolve a trait's
|
|
709
|
+
* state by its stable id, which survives a mid-session rename — the
|
|
710
|
+
* name-keyed maps are re-pointed on rename via {@link renameTrait}, but
|
|
711
|
+
* an id holder never has to observe the rename at all. Empty for legacy
|
|
712
|
+
* id-free schemas, where every lookup stays name-keyed (unchanged).
|
|
713
|
+
*/
|
|
714
|
+
traitIdToName = /* @__PURE__ */ new Map();
|
|
650
715
|
/**
|
|
651
716
|
* Per-trait call-site config, surfaced to guard expressions so
|
|
652
717
|
* `@config.X` resolves at runtime. Populated by the orbital's
|
|
@@ -697,6 +762,69 @@ var StateMachineManager = class {
|
|
|
697
762
|
*/
|
|
698
763
|
addTrait(trait) {
|
|
699
764
|
this.traits.set(trait.name, trait);
|
|
765
|
+
if (trait.id !== void 0) {
|
|
766
|
+
this.traitIdToName.set(trait.id, trait.name);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Resolve a trait by its V4 id. Returns undefined when no trait carries
|
|
771
|
+
* that id (legacy schema, or unknown id). Exact-match only — no name
|
|
772
|
+
* similarity.
|
|
773
|
+
*/
|
|
774
|
+
getTraitById(traitId) {
|
|
775
|
+
const name = this.traitIdToName.get(traitId);
|
|
776
|
+
return name === void 0 ? void 0 : this.traits.get(name);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Get a trait's current state by its V4 id (id-first lookup). Falls back
|
|
780
|
+
* to `undefined` when the id is unknown. The id survives a rename, so a
|
|
781
|
+
* holder of the id reads the right state without ever seeing the new name.
|
|
782
|
+
*/
|
|
783
|
+
getStateById(traitId, entityId) {
|
|
784
|
+
const name = this.traitIdToName.get(traitId);
|
|
785
|
+
return name === void 0 ? void 0 : this.getState(name, entityId);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Apply a mid-session trait rename: re-point the name-keyed maps from
|
|
789
|
+
* `oldName` to `newName`, preserving live state, queues, and the id
|
|
790
|
+
* index. A no-op when the trait isn't registered under `oldName`. This
|
|
791
|
+
* is the interpreter-side of a ledger `curName` edit for the trait's own
|
|
792
|
+
* name-keyed storage; id-keyed references need no update.
|
|
793
|
+
*/
|
|
794
|
+
renameTrait(oldName, newName) {
|
|
795
|
+
const trait = this.traits.get(oldName);
|
|
796
|
+
if (!trait || oldName === newName) return;
|
|
797
|
+
const renamed = { ...trait, name: newName };
|
|
798
|
+
this.traits.delete(oldName);
|
|
799
|
+
this.traits.set(newName, renamed);
|
|
800
|
+
if (renamed.id !== void 0) {
|
|
801
|
+
this.traitIdToName.set(renamed.id, newName);
|
|
802
|
+
}
|
|
803
|
+
const remap = (m) => {
|
|
804
|
+
const prefix2 = `${oldName}::`;
|
|
805
|
+
for (const key of [...m.keys()]) {
|
|
806
|
+
if (key.startsWith(prefix2)) {
|
|
807
|
+
const scope = key.slice(prefix2.length);
|
|
808
|
+
const v = m.get(key);
|
|
809
|
+
m.delete(key);
|
|
810
|
+
m.set(`${newName}::${scope}`, v);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
remap(this.states);
|
|
815
|
+
remap(this.queues);
|
|
816
|
+
const prefix = `${oldName}::`;
|
|
817
|
+
for (const key of [...this.processing]) {
|
|
818
|
+
if (key.startsWith(prefix)) {
|
|
819
|
+
this.processing.delete(key);
|
|
820
|
+
this.processing.add(`${newName}::${key.slice(prefix.length)}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const cfg = this.traitConfigs.get(oldName);
|
|
824
|
+
if (cfg !== void 0) {
|
|
825
|
+
this.traitConfigs.set(newName, cfg);
|
|
826
|
+
this.traitConfigs.delete(oldName);
|
|
827
|
+
}
|
|
700
828
|
}
|
|
701
829
|
/**
|
|
702
830
|
* Bind the call-site config for a trait so guard `@config.X`
|
|
@@ -715,6 +843,10 @@ var StateMachineManager = class {
|
|
|
715
843
|
* Remove a trait from the manager.
|
|
716
844
|
*/
|
|
717
845
|
removeTrait(traitName) {
|
|
846
|
+
const removed = this.traits.get(traitName);
|
|
847
|
+
if (removed?.id !== void 0) {
|
|
848
|
+
this.traitIdToName.delete(removed.id);
|
|
849
|
+
}
|
|
718
850
|
this.traits.delete(traitName);
|
|
719
851
|
const prefix = `${traitName}::`;
|
|
720
852
|
for (const key of [...this.states.keys()]) {
|
|
@@ -802,11 +934,11 @@ var StateMachineManager = class {
|
|
|
802
934
|
/**
|
|
803
935
|
* Check if a trait can handle an event from its current state.
|
|
804
936
|
*/
|
|
805
|
-
canHandleEvent(traitName, eventKey, entityId) {
|
|
937
|
+
canHandleEvent(traitName, eventKey, entityId, eventId) {
|
|
806
938
|
const trait = this.traits.get(traitName);
|
|
807
939
|
const state = this.getOrInitState(traitName, entityId ?? SINGLETON_SCOPE);
|
|
808
940
|
if (!trait || !state) return false;
|
|
809
|
-
return !!findTransition(trait, state.currentState, normalizeEventKey(eventKey));
|
|
941
|
+
return !!findTransition(trait, state.currentState, normalizeEventKey(eventKey), eventId);
|
|
810
942
|
}
|
|
811
943
|
/**
|
|
812
944
|
* Send an event to all traits.
|
|
@@ -818,9 +950,13 @@ var StateMachineManager = class {
|
|
|
818
950
|
* `@entity.X` see prior step writes — required for [runtime] entities
|
|
819
951
|
* that have no persistence row to reload.
|
|
820
952
|
*
|
|
953
|
+
* `eventId` is the V4 dual-carry id sibling of `eventKey` (see
|
|
954
|
+
* {@link ProcessEventOptions.eventId}) — additive and optional, threaded
|
|
955
|
+
* through to `processEvent` for id-primary transition matching.
|
|
956
|
+
*
|
|
821
957
|
* @returns Array of transition results (one per trait that had a matching transition)
|
|
822
958
|
*/
|
|
823
|
-
sendEvent(eventKey, payload, entityData, entityByTrait) {
|
|
959
|
+
sendEvent(eventKey, payload, entityData, entityByTrait, eventId) {
|
|
824
960
|
const results = [];
|
|
825
961
|
const scope = scopeOf(entityData);
|
|
826
962
|
for (const [traitName, trait] of this.traits) {
|
|
@@ -832,6 +968,7 @@ var StateMachineManager = class {
|
|
|
832
968
|
traitState,
|
|
833
969
|
trait,
|
|
834
970
|
eventKey,
|
|
971
|
+
eventId,
|
|
835
972
|
payload,
|
|
836
973
|
entityData: perTraitEntity,
|
|
837
974
|
config: this.traitConfigs.get(traitName),
|
|
@@ -873,12 +1010,12 @@ var StateMachineManager = class {
|
|
|
873
1010
|
* trait to process them sequentially (actor-model guarantee: one event
|
|
874
1011
|
* at a time per trait, effects fully awaited before the next event).
|
|
875
1012
|
*/
|
|
876
|
-
enqueueEvent(eventKey, payload, entityData, entityByTrait) {
|
|
1013
|
+
enqueueEvent(eventKey, payload, entityData, entityByTrait, eventId) {
|
|
877
1014
|
const scope = scopeOf(entityData);
|
|
878
1015
|
for (const [traitName] of this.traits) {
|
|
879
1016
|
const key = compositeKey(traitName, scope);
|
|
880
1017
|
const queue = this.queues.get(key) ?? [];
|
|
881
|
-
queue.push({ eventKey, payload, entityData, entityByTrait });
|
|
1018
|
+
queue.push({ eventKey, eventId, payload, entityData, entityByTrait });
|
|
882
1019
|
this.queues.set(key, queue);
|
|
883
1020
|
}
|
|
884
1021
|
}
|
|
@@ -903,6 +1040,7 @@ var StateMachineManager = class {
|
|
|
903
1040
|
traitState,
|
|
904
1041
|
trait,
|
|
905
1042
|
eventKey: entry.eventKey,
|
|
1043
|
+
eventId: entry.eventId,
|
|
906
1044
|
payload: entry.payload,
|
|
907
1045
|
entityData: perTraitEntity,
|
|
908
1046
|
config: this.traitConfigs.get(traitName),
|
|
@@ -982,7 +1120,7 @@ var StateMachineManager = class {
|
|
|
982
1120
|
|
|
983
1121
|
// src/types.ts
|
|
984
1122
|
var HANDLER_MANIFEST = {
|
|
985
|
-
client: ["render-ui", "render", "navigate", "notify", "emit", "set", "log", "ref", "deref", "watch", "send-server"],
|
|
1123
|
+
client: ["render-ui", "render", "navigate", "notify", "emit", "set", "log", "ref", "deref", "watch", "send-server", "browser/open-file-picker", "browser/clipboard-read", "browser/clipboard-write", "browser/geolocation-current"],
|
|
986
1124
|
server: ["persist", "fetch", "fetch-stream", "call-service", "emit", "set", "spawn", "despawn", "log", "ref", "deref", "swap!", "atomic", "os/watch-files", "os/watch-process", "os/watch-port", "os/watch-http", "os/watch-cron", "os/watch-signal", "os/watch-env", "os/debounce"],
|
|
987
1125
|
test: [
|
|
988
1126
|
"render-ui",
|
|
@@ -1002,7 +1140,11 @@ var HANDLER_MANIFEST = {
|
|
|
1002
1140
|
"swap!",
|
|
1003
1141
|
"watch",
|
|
1004
1142
|
"atomic",
|
|
1005
|
-
"send-server"
|
|
1143
|
+
"send-server",
|
|
1144
|
+
"browser/open-file-picker",
|
|
1145
|
+
"browser/clipboard-read",
|
|
1146
|
+
"browser/clipboard-write",
|
|
1147
|
+
"browser/geolocation-current"
|
|
1006
1148
|
],
|
|
1007
1149
|
ssr: ["render-ui", "render", "fetch", "emit", "set", "log", "ref", "deref"]
|
|
1008
1150
|
};
|
|
@@ -1938,6 +2080,58 @@ var EffectExecutor = class _EffectExecutor {
|
|
|
1938
2080
|
}, emitCfg);
|
|
1939
2081
|
break;
|
|
1940
2082
|
}
|
|
2083
|
+
// === Browser device operators (client-side, user-gesture) ===
|
|
2084
|
+
// Async host APIs routed to dedicated handler methods. Uniform
|
|
2085
|
+
// `{ result }` / `{ error }` payload via runSubstrate. On the
|
|
2086
|
+
// server (or any host without the API) the handler is absent →
|
|
2087
|
+
// unsupported warning; a thrown error fires `emit.failure`.
|
|
2088
|
+
case "browser/open-file-picker": {
|
|
2089
|
+
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
2090
|
+
const options = positional[0];
|
|
2091
|
+
await this.runSubstrate(async () => {
|
|
2092
|
+
if (!this.handlers.browserOpenFilePicker) {
|
|
2093
|
+
this.logUnsupported("browser/open-file-picker");
|
|
2094
|
+
return null;
|
|
2095
|
+
}
|
|
2096
|
+
return this.handlers.browserOpenFilePicker(options);
|
|
2097
|
+
}, emitCfg);
|
|
2098
|
+
break;
|
|
2099
|
+
}
|
|
2100
|
+
case "browser/clipboard-read": {
|
|
2101
|
+
const [, emitCfg] = this.splitSubstrateEmit(args);
|
|
2102
|
+
await this.runSubstrate(async () => {
|
|
2103
|
+
if (!this.handlers.browserClipboardRead) {
|
|
2104
|
+
this.logUnsupported("browser/clipboard-read");
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
return this.handlers.browserClipboardRead();
|
|
2108
|
+
}, emitCfg);
|
|
2109
|
+
break;
|
|
2110
|
+
}
|
|
2111
|
+
case "browser/clipboard-write": {
|
|
2112
|
+
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
2113
|
+
const text = positional[0];
|
|
2114
|
+
await this.runSubstrate(async () => {
|
|
2115
|
+
if (!this.handlers.browserClipboardWrite) {
|
|
2116
|
+
this.logUnsupported("browser/clipboard-write");
|
|
2117
|
+
return null;
|
|
2118
|
+
}
|
|
2119
|
+
return this.handlers.browserClipboardWrite(text);
|
|
2120
|
+
}, emitCfg);
|
|
2121
|
+
break;
|
|
2122
|
+
}
|
|
2123
|
+
case "browser/geolocation-current": {
|
|
2124
|
+
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
2125
|
+
const options = positional[0];
|
|
2126
|
+
await this.runSubstrate(async () => {
|
|
2127
|
+
if (!this.handlers.browserGeolocationCurrent) {
|
|
2128
|
+
this.logUnsupported("browser/geolocation-current");
|
|
2129
|
+
return null;
|
|
2130
|
+
}
|
|
2131
|
+
return this.handlers.browserGeolocationCurrent(options);
|
|
2132
|
+
}, emitCfg);
|
|
2133
|
+
break;
|
|
2134
|
+
}
|
|
1941
2135
|
default: {
|
|
1942
2136
|
if (operator.includes("/")) {
|
|
1943
2137
|
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
@@ -2104,21 +2298,6 @@ function collectDeclaredConfigDefaults(trait) {
|
|
|
2104
2298
|
}
|
|
2105
2299
|
return hasAny ? defaults : void 0;
|
|
2106
2300
|
}
|
|
2107
|
-
function normalizeCallSiteConfigToValues(config) {
|
|
2108
|
-
if (config === void 0) {
|
|
2109
|
-
return void 0;
|
|
2110
|
-
}
|
|
2111
|
-
const out = {};
|
|
2112
|
-
let hasAny = false;
|
|
2113
|
-
for (const [key, entry] of Object.entries(config)) {
|
|
2114
|
-
const value = isCallSiteConfigDeclaration(entry) ? entry.default : entry;
|
|
2115
|
-
if (value !== void 0) {
|
|
2116
|
-
out[key] = value;
|
|
2117
|
-
hasAny = true;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
return hasAny ? out : void 0;
|
|
2121
|
-
}
|
|
2122
2301
|
function collectDeclaredEntityDefaults(entity) {
|
|
2123
2302
|
if (!entity) return void 0;
|
|
2124
2303
|
const defaults = {};
|
|
@@ -2134,7 +2313,7 @@ function collectDeclaredEntityDefaults(entity) {
|
|
|
2134
2313
|
var mockLog = createLogger("almadar:runtime:mock");
|
|
2135
2314
|
var DEFAULT_MOCK_SEED = 42;
|
|
2136
2315
|
function picsumUrl(entityName, fieldName, width = 400, height = 400) {
|
|
2137
|
-
const seed = `${entityName}-${fieldName}-${
|
|
2316
|
+
const seed = `${entityName}-${fieldName}-${randomInt({ min: 0, max: 1e3 })}`;
|
|
2138
2317
|
return `https://picsum.photos/seed/${encodeURIComponent(seed)}/${width}/${height}`;
|
|
2139
2318
|
}
|
|
2140
2319
|
var SEED_REFERENCE_TIMESTAMP = "2024-01-01T00:00:00.000Z";
|
|
@@ -2142,6 +2321,8 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2142
2321
|
stores = /* @__PURE__ */ new Map();
|
|
2143
2322
|
schemas = /* @__PURE__ */ new Map();
|
|
2144
2323
|
idCounters = /* @__PURE__ */ new Map();
|
|
2324
|
+
/** entityId -> normalized store name, so relation lookups can prefer the id sibling over `relation.entity` name-matching. */
|
|
2325
|
+
storeNameById = /* @__PURE__ */ new Map();
|
|
2145
2326
|
config;
|
|
2146
2327
|
constructor(config = {}) {
|
|
2147
2328
|
this.config = {
|
|
@@ -2152,17 +2333,17 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2152
2333
|
// input doesn't overwrite the default.
|
|
2153
2334
|
seed: config.seed ?? DEFAULT_MOCK_SEED
|
|
2154
2335
|
};
|
|
2155
|
-
|
|
2336
|
+
seedRandom(this.config.seed);
|
|
2156
2337
|
mockLog.debug("mock:adapter:init", { seed: this.config.seed });
|
|
2157
2338
|
}
|
|
2158
|
-
/** Re-anchor
|
|
2339
|
+
/** Re-anchor the PRNG to the configured seed. Called before every
|
|
2159
2340
|
* re-seed loop so identical reseed sequences produce identical rows
|
|
2160
|
-
* (timestamps +
|
|
2341
|
+
* (timestamps + generated fields). Without this, the first
|
|
2161
2342
|
* reseed produces row set A, the second produces row set B, and
|
|
2162
2343
|
* diff observers see all rows as "changed" between frames. */
|
|
2163
2344
|
resetFakerSeed() {
|
|
2164
2345
|
if (this.config.seed !== void 0) {
|
|
2165
|
-
|
|
2346
|
+
seedRandom(this.config.seed);
|
|
2166
2347
|
}
|
|
2167
2348
|
}
|
|
2168
2349
|
// ============================================================================
|
|
@@ -2188,11 +2369,14 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2188
2369
|
/**
|
|
2189
2370
|
* Register an entity schema and seed mock data.
|
|
2190
2371
|
* If the schema has seedData, those instances are used directly.
|
|
2191
|
-
* Otherwise, random mock data is generated with
|
|
2372
|
+
* Otherwise, random mock data is generated with the seeded PRNG.
|
|
2192
2373
|
*/
|
|
2193
2374
|
registerEntity(schema, seedCount) {
|
|
2194
2375
|
const normalized = schema.name.toLowerCase();
|
|
2195
2376
|
this.schemas.set(normalized, schema);
|
|
2377
|
+
if (schema.id) {
|
|
2378
|
+
this.storeNameById.set(schema.id, normalized);
|
|
2379
|
+
}
|
|
2196
2380
|
if (schema.seedData && schema.seedData.length > 0) {
|
|
2197
2381
|
this.seedFromInstances(schema.name, schema.seedData);
|
|
2198
2382
|
} else {
|
|
@@ -2224,7 +2408,8 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2224
2408
|
if (relationFields.length === 0) continue;
|
|
2225
2409
|
for (const row of store.values()) {
|
|
2226
2410
|
for (const field of relationFields) {
|
|
2227
|
-
const
|
|
2411
|
+
const targetNormalized = (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? field.relation.entity.toLowerCase();
|
|
2412
|
+
const targetStore = this.stores.get(targetNormalized);
|
|
2228
2413
|
if (!targetStore || targetStore.size === 0) continue;
|
|
2229
2414
|
const selfId = row["id"];
|
|
2230
2415
|
const sameStore = targetStore === store;
|
|
@@ -2236,10 +2421,10 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2236
2421
|
if (eligible.length === 0) continue;
|
|
2237
2422
|
const cardinality = field.relation.cardinality ?? "many";
|
|
2238
2423
|
if (cardinality === "one" || cardinality === "many-to-one") {
|
|
2239
|
-
row[field.name] =
|
|
2424
|
+
row[field.name] = randomArrayElement(eligible);
|
|
2240
2425
|
} else {
|
|
2241
|
-
const pickCount = Math.min(eligible.length,
|
|
2242
|
-
row[field.name] =
|
|
2426
|
+
const pickCount = Math.min(eligible.length, randomInt({ min: 2, max: 4 }));
|
|
2427
|
+
row[field.name] = shuffleArray(eligible.slice()).slice(0, pickCount);
|
|
2243
2428
|
}
|
|
2244
2429
|
}
|
|
2245
2430
|
}
|
|
@@ -2291,7 +2476,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2291
2476
|
const id = this.nextId(entityName);
|
|
2292
2477
|
const item = {
|
|
2293
2478
|
id,
|
|
2294
|
-
createdAt:
|
|
2479
|
+
createdAt: randomPastDate({ years: 1 }).toISOString(),
|
|
2295
2480
|
updatedAt: SEED_REFERENCE_TIMESTAMP
|
|
2296
2481
|
};
|
|
2297
2482
|
for (const field of fields) {
|
|
@@ -2333,16 +2518,16 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2333
2518
|
case "string":
|
|
2334
2519
|
return this.generateStringValue(entityName, field, index);
|
|
2335
2520
|
case "number":
|
|
2336
|
-
return
|
|
2521
|
+
return randomInt({ min: 0, max: 100 });
|
|
2337
2522
|
case "boolean":
|
|
2338
|
-
return
|
|
2523
|
+
return randomBoolean();
|
|
2339
2524
|
case "date":
|
|
2340
2525
|
case "timestamp":
|
|
2341
2526
|
case "datetime":
|
|
2342
2527
|
return this.generateDateValue(field);
|
|
2343
2528
|
case "enum":
|
|
2344
2529
|
if (field.values && field.values.length > 0) {
|
|
2345
|
-
return
|
|
2530
|
+
return randomArrayElement(field.values);
|
|
2346
2531
|
}
|
|
2347
2532
|
return null;
|
|
2348
2533
|
case "relation":
|
|
@@ -2367,7 +2552,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2367
2552
|
generateArrayValue(entityName, field, index, depth = 0) {
|
|
2368
2553
|
if (field.type !== "array" || !field.items) return [];
|
|
2369
2554
|
if (depth >= _MockPersistenceAdapter.MAX_NESTED_DEPTH) return [];
|
|
2370
|
-
const count =
|
|
2555
|
+
const count = randomInt({ min: 3, max: 5 });
|
|
2371
2556
|
const out = [];
|
|
2372
2557
|
const elementName = field.name ?? "item";
|
|
2373
2558
|
for (let i = 0; i < count; i++) {
|
|
@@ -2381,7 +2566,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2381
2566
|
}
|
|
2382
2567
|
/**
|
|
2383
2568
|
* Generate a single object value with each declared property populated
|
|
2384
|
-
* by
|
|
2569
|
+
* by the seeded PRNG. Walks `properties` and recursively delegates to
|
|
2385
2570
|
* `generateFieldValue` per property so nested objects-of-arrays-of-objects
|
|
2386
2571
|
* compose correctly.
|
|
2387
2572
|
*/
|
|
@@ -2398,29 +2583,29 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2398
2583
|
/**
|
|
2399
2584
|
* Generate a string value based on the field's declared schema metadata.
|
|
2400
2585
|
* Reads `values` (enum) first, then `format` (email/url/phone/uuid/date/
|
|
2401
|
-
* datetime), then falls back to
|
|
2586
|
+
* datetime), then falls back to randomWords. No field-name heuristics
|
|
2402
2587
|
* — the schema is the source of truth. If a caller needs a real email, they
|
|
2403
2588
|
* declare `format: "email"`; if they need an enum, they declare `values: [...]`.
|
|
2404
2589
|
*/
|
|
2405
2590
|
generateStringValue(entityName, field, _index) {
|
|
2406
2591
|
const values = "values" in field ? field.values : void 0;
|
|
2407
2592
|
if (values && values.length > 0) {
|
|
2408
|
-
return
|
|
2593
|
+
return randomArrayElement(values);
|
|
2409
2594
|
}
|
|
2410
2595
|
const fieldName = field.name ?? "field";
|
|
2411
2596
|
switch (field.format) {
|
|
2412
2597
|
case "email":
|
|
2413
|
-
return
|
|
2598
|
+
return randomEmail();
|
|
2414
2599
|
case "url":
|
|
2415
|
-
return
|
|
2600
|
+
return randomUrl();
|
|
2416
2601
|
case "phone":
|
|
2417
|
-
return
|
|
2602
|
+
return randomPhone();
|
|
2418
2603
|
case "uuid":
|
|
2419
|
-
return
|
|
2604
|
+
return randomUuid();
|
|
2420
2605
|
case "date":
|
|
2421
|
-
return
|
|
2606
|
+
return randomRecentDate().toISOString().split("T")[0];
|
|
2422
2607
|
case "datetime":
|
|
2423
|
-
return
|
|
2608
|
+
return randomRecentDate().toISOString();
|
|
2424
2609
|
case "image":
|
|
2425
2610
|
case "avatar":
|
|
2426
2611
|
case "thumbnail":
|
|
@@ -2430,7 +2615,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2430
2615
|
if (lname === "image" || lname === "imageurl" || lname === "image_url" || lname === "photo" || lname === "photourl" || lname === "photo_url" || lname === "avatar" || lname === "avatarurl" || lname === "avatar_url" || lname === "thumbnail" || lname === "thumbnailurl" || lname === "thumbnail_url" || lname === "picture" || lname === "pictureurl" || lname === "cover" || lname === "coverurl" || lname === "banner" || lname === "bannerurl") {
|
|
2431
2616
|
return picsumUrl(entityName, fieldName);
|
|
2432
2617
|
}
|
|
2433
|
-
const value =
|
|
2618
|
+
const value = randomWords(2);
|
|
2434
2619
|
mockLog.debug("field:fallback-lorem", () => ({
|
|
2435
2620
|
entityName,
|
|
2436
2621
|
fieldName: field.name,
|
|
@@ -2446,7 +2631,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2446
2631
|
* field-name heuristics.
|
|
2447
2632
|
*/
|
|
2448
2633
|
generateDateValue(field) {
|
|
2449
|
-
const date =
|
|
2634
|
+
const date = randomRecentDate({ days: 30 });
|
|
2450
2635
|
if (field.format === "date") return date.toISOString().split("T")[0];
|
|
2451
2636
|
return date.toISOString();
|
|
2452
2637
|
}
|
|
@@ -2530,7 +2715,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2530
2715
|
this.stores.delete(normalized);
|
|
2531
2716
|
this.idCounters.delete(normalized);
|
|
2532
2717
|
}
|
|
2533
|
-
/** Clear all data + re-anchor
|
|
2718
|
+
/** Clear all data + re-anchor the PRNG so the next seed loop reproduces
|
|
2534
2719
|
* identical rows. Hermetic-frame mode calls this between every step
|
|
2535
2720
|
* via OrbitalServerRuntime.resetMockPersistence. */
|
|
2536
2721
|
clearAll() {
|
|
@@ -2902,7 +3087,7 @@ async function getExternalLoaderModule() {
|
|
|
2902
3087
|
return null;
|
|
2903
3088
|
}
|
|
2904
3089
|
try {
|
|
2905
|
-
externalLoaderModule = await import('./external-loader-
|
|
3090
|
+
externalLoaderModule = await import('./external-loader-FNK5AU6U.js');
|
|
2906
3091
|
return externalLoaderModule;
|
|
2907
3092
|
} catch {
|
|
2908
3093
|
return null;
|
|
@@ -3158,7 +3343,310 @@ function createUnifiedLoader(options) {
|
|
|
3158
3343
|
return new UnifiedLoader(options);
|
|
3159
3344
|
}
|
|
3160
3345
|
createLogger("almadar:runtime:studio-config");
|
|
3346
|
+
|
|
3347
|
+
// src/ui/splice-lambda-traits.ts
|
|
3348
|
+
var TRAIT_BINDING_PREFIX = "@trait.";
|
|
3349
|
+
var CONFIG_BINDING_PREFIX = "@config.";
|
|
3350
|
+
var LambdaSpliceError = class extends Error {
|
|
3351
|
+
constructor(code, message) {
|
|
3352
|
+
super(message);
|
|
3353
|
+
this.code = code;
|
|
3354
|
+
this.name = "LambdaSpliceError";
|
|
3355
|
+
}
|
|
3356
|
+
code;
|
|
3357
|
+
};
|
|
3358
|
+
function isRenderUiEffect(effect) {
|
|
3359
|
+
return Array.isArray(effect) && effect.length >= 1 && effect[0] === "render-ui";
|
|
3360
|
+
}
|
|
3361
|
+
function renderOnlyTemplate(trait) {
|
|
3362
|
+
if (trait.listens && trait.listens.length > 0) return null;
|
|
3363
|
+
if (trait.ticks && trait.ticks.length > 0) return null;
|
|
3364
|
+
const sm = trait.stateMachine;
|
|
3365
|
+
const states = sm?.states ?? [];
|
|
3366
|
+
if (states.length > 1) return null;
|
|
3367
|
+
let template = null;
|
|
3368
|
+
for (const transition of sm?.transitions ?? []) {
|
|
3369
|
+
const selfLoop = transition.from === transition.to;
|
|
3370
|
+
const guarded = transition.guard !== void 0 && transition.guard !== null;
|
|
3371
|
+
if (!selfLoop || guarded) return null;
|
|
3372
|
+
for (const effect of transition.effects ?? []) {
|
|
3373
|
+
if (!isRenderUiEffect(effect) || !Array.isArray(effect)) return null;
|
|
3374
|
+
const pattern = effect[2];
|
|
3375
|
+
if (pattern === void 0) return null;
|
|
3376
|
+
if (template !== null) return null;
|
|
3377
|
+
template = pattern;
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
return template;
|
|
3381
|
+
}
|
|
3382
|
+
function resolveObjectPath(value, path) {
|
|
3383
|
+
let cur = value;
|
|
3384
|
+
for (const seg of path.split(".")) {
|
|
3385
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
3386
|
+
cur = cur[seg];
|
|
3387
|
+
if (cur === void 0) return void 0;
|
|
3388
|
+
}
|
|
3389
|
+
return cur;
|
|
3390
|
+
}
|
|
3391
|
+
function substituteConfig(expr, subs) {
|
|
3392
|
+
if (typeof expr === "string") {
|
|
3393
|
+
if (!expr.startsWith(CONFIG_BINDING_PREFIX)) return expr;
|
|
3394
|
+
const rest = expr.slice(CONFIG_BINDING_PREFIX.length);
|
|
3395
|
+
const dot = rest.indexOf(".");
|
|
3396
|
+
const param = dot === -1 ? rest : rest.slice(0, dot);
|
|
3397
|
+
const trailing = dot === -1 ? void 0 : rest.slice(dot + 1);
|
|
3398
|
+
if (!(param in subs)) return expr;
|
|
3399
|
+
const value = subs[param];
|
|
3400
|
+
if (trailing === void 0) {
|
|
3401
|
+
if (typeof value === "string" && value === expr) return expr;
|
|
3402
|
+
return substituteConfig(value, subs);
|
|
3403
|
+
}
|
|
3404
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
3405
|
+
const leaf = resolveObjectPath(value, trailing);
|
|
3406
|
+
if (leaf !== void 0) return substituteConfig(leaf, subs);
|
|
3407
|
+
}
|
|
3408
|
+
return expr;
|
|
3409
|
+
}
|
|
3410
|
+
if (Array.isArray(expr)) {
|
|
3411
|
+
return expr.map((item) => substituteConfig(item, subs));
|
|
3412
|
+
}
|
|
3413
|
+
if (expr !== null && typeof expr === "object") {
|
|
3414
|
+
const out = {};
|
|
3415
|
+
for (const [k, v] of Object.entries(expr)) {
|
|
3416
|
+
out[k] = substituteConfig(v, subs);
|
|
3417
|
+
}
|
|
3418
|
+
return out;
|
|
3419
|
+
}
|
|
3420
|
+
return expr;
|
|
3421
|
+
}
|
|
3422
|
+
function buildTemplate(trait) {
|
|
3423
|
+
const raw = renderOnlyTemplate(trait);
|
|
3424
|
+
const emits = trait.emits ?? [];
|
|
3425
|
+
if (raw === null) return { template: null, emits, traitName: trait.name, id: trait.id, embedIds: trait.traitEmbedIds };
|
|
3426
|
+
const defaults = collectDeclaredConfigDefaults(trait);
|
|
3427
|
+
const template = defaults ? substituteConfig(raw, defaults) : raw;
|
|
3428
|
+
return { template, emits, traitName: trait.name, id: trait.id, embedIds: trait.traitEmbedIds };
|
|
3429
|
+
}
|
|
3430
|
+
function isLambdaForm(expr) {
|
|
3431
|
+
return expr.length === 3 && (expr[0] === "fn" || expr[0] === "lambda");
|
|
3432
|
+
}
|
|
3433
|
+
function spliceExpr(expr, inLambda, st) {
|
|
3434
|
+
if (typeof expr === "string") {
|
|
3435
|
+
if (!inLambda || !expr.startsWith(TRAIT_BINDING_PREFIX)) return expr;
|
|
3436
|
+
const name = expr.slice(TRAIT_BINDING_PREFIX.length);
|
|
3437
|
+
if (name.length === 0 || name.includes(".")) return expr;
|
|
3438
|
+
const embedId = st.embedIds?.[name];
|
|
3439
|
+
const entry = (embedId !== void 0 ? st.templatesById.get(embedId) : void 0) ?? st.templates.get(name);
|
|
3440
|
+
if (entry === void 0) return expr;
|
|
3441
|
+
if (entry.template === null) {
|
|
3442
|
+
throw new LambdaSpliceError(
|
|
3443
|
+
"LAMBDA_STATEFUL_TRAIT",
|
|
3444
|
+
`Trait "${name}" keeps its own state machine and cannot be expanded per list item \u2014 use the list pattern's item events (referenced inside a lambda in trait "${st.hostName}")`
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
if (st.visiting.includes(name)) return expr;
|
|
3448
|
+
st.visiting.push(name);
|
|
3449
|
+
const prevEmbedIds = st.embedIds;
|
|
3450
|
+
st.embedIds = entry.embedIds;
|
|
3451
|
+
const expanded = spliceExpr(entry.template, true, st);
|
|
3452
|
+
st.embedIds = prevEmbedIds;
|
|
3453
|
+
st.visiting.pop();
|
|
3454
|
+
for (const emit of entry.emits) st.merged.push([entry.traitName, emit]);
|
|
3455
|
+
st.spliced.add(entry.traitName);
|
|
3456
|
+
st.changed = true;
|
|
3457
|
+
return expanded;
|
|
3458
|
+
}
|
|
3459
|
+
if (Array.isArray(expr)) {
|
|
3460
|
+
if (isLambdaForm(expr)) {
|
|
3461
|
+
return [expr[0], expr[1], spliceExpr(expr[2], true, st)];
|
|
3462
|
+
}
|
|
3463
|
+
return expr.map((item) => spliceExpr(item, inLambda, st));
|
|
3464
|
+
}
|
|
3465
|
+
if (expr !== null && typeof expr === "object") {
|
|
3466
|
+
const out = {};
|
|
3467
|
+
for (const [k, v] of Object.entries(expr)) {
|
|
3468
|
+
out[k] = spliceExpr(v, inLambda, st);
|
|
3469
|
+
}
|
|
3470
|
+
return out;
|
|
3471
|
+
}
|
|
3472
|
+
return expr;
|
|
3473
|
+
}
|
|
3474
|
+
function canonical(value) {
|
|
3475
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
3476
|
+
if (value !== null && typeof value === "object") {
|
|
3477
|
+
const out = {};
|
|
3478
|
+
for (const key of Object.keys(value).sort()) {
|
|
3479
|
+
out[key] = canonical(value[key]);
|
|
3480
|
+
}
|
|
3481
|
+
return out;
|
|
3482
|
+
}
|
|
3483
|
+
return value;
|
|
3484
|
+
}
|
|
3485
|
+
function mergeWrapperEmits(host, incoming) {
|
|
3486
|
+
const emits = [...host.emits ?? []];
|
|
3487
|
+
for (const [wrapper, emit] of incoming) {
|
|
3488
|
+
const existing = emits.find((e) => e.event === emit.event);
|
|
3489
|
+
if (existing) {
|
|
3490
|
+
const same = JSON.stringify(canonical(existing)) === JSON.stringify(canonical(emit));
|
|
3491
|
+
if (!same) {
|
|
3492
|
+
throw new LambdaSpliceError(
|
|
3493
|
+
"EMIT_MERGE_CONFLICT",
|
|
3494
|
+
`Emit "${emit.event}" merged from spliced wrapper "${wrapper}" conflicts with an existing "${emit.event}" on host "${host.name}" (different payload shape)`
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
} else {
|
|
3498
|
+
emits.push(emit);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
return emits;
|
|
3502
|
+
}
|
|
3503
|
+
function removeConsumedWrappers(traits, pages, spliced) {
|
|
3504
|
+
const idToName = /* @__PURE__ */ new Map();
|
|
3505
|
+
for (const { trait } of traits) {
|
|
3506
|
+
if (trait.id) idToName.set(trait.id, trait.name);
|
|
3507
|
+
}
|
|
3508
|
+
for (; ; ) {
|
|
3509
|
+
const stillReferenced = /* @__PURE__ */ new Set();
|
|
3510
|
+
for (const { trait } of traits) {
|
|
3511
|
+
const rawTokens = /* @__PURE__ */ new Set();
|
|
3512
|
+
for (const t of trait.stateMachine?.transitions ?? []) {
|
|
3513
|
+
if (t.guard !== void 0 && t.guard !== null) {
|
|
3514
|
+
collectTraitRefsFromValue(t.guard, rawTokens);
|
|
3515
|
+
}
|
|
3516
|
+
collectTraitRefsFromEffects(t.effects, rawTokens);
|
|
3517
|
+
}
|
|
3518
|
+
for (const tick of trait.ticks ?? []) {
|
|
3519
|
+
if (tick.guard !== void 0 && tick.guard !== null) {
|
|
3520
|
+
collectTraitRefsFromValue(tick.guard, rawTokens);
|
|
3521
|
+
}
|
|
3522
|
+
collectTraitRefsFromEffects(tick.effects, rawTokens);
|
|
3523
|
+
}
|
|
3524
|
+
if (trait.config) {
|
|
3525
|
+
for (const field of Object.values(trait.config)) {
|
|
3526
|
+
if (field && typeof field === "object" && "default" in field && field.default !== void 0) {
|
|
3527
|
+
collectTraitRefsFromValue(field.default, rawTokens);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
for (const token of rawTokens) {
|
|
3532
|
+
const embedId = trait.traitEmbedIds?.[token];
|
|
3533
|
+
const resolvedName = (embedId !== void 0 ? idToName.get(embedId) : void 0) ?? token;
|
|
3534
|
+
stillReferenced.add(resolvedName);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
const removable = /* @__PURE__ */ new Set();
|
|
3538
|
+
for (const { trait } of traits) {
|
|
3539
|
+
if (spliced.has(trait.name) && !stillReferenced.has(trait.name)) {
|
|
3540
|
+
removable.add(trait.name);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
if (removable.size === 0) return;
|
|
3544
|
+
for (const { page } of pages) {
|
|
3545
|
+
const pageTraits = page.traits;
|
|
3546
|
+
if (!pageTraits) continue;
|
|
3547
|
+
const survivors2 = pageTraits.filter((pt) => !removable.has(pt.ref)).length;
|
|
3548
|
+
if (survivors2 === 0) {
|
|
3549
|
+
for (const pt of pageTraits) removable.delete(pt.ref);
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
if (removable.size === 0) return;
|
|
3553
|
+
for (const resolvedPage of pages) {
|
|
3554
|
+
const pageTraits = resolvedPage.page.traits;
|
|
3555
|
+
if (pageTraits) {
|
|
3556
|
+
resolvedPage.page.traits = pageTraits.filter((pt) => !removable.has(pt.ref));
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
const survivors = traits.filter(({ trait }) => !removable.has(trait.name));
|
|
3560
|
+
traits.length = 0;
|
|
3561
|
+
traits.push(...survivors);
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
function spliceLambdaTraitRefs(traits, pages) {
|
|
3565
|
+
if (traits.length === 0) return;
|
|
3566
|
+
const templates = /* @__PURE__ */ new Map();
|
|
3567
|
+
const templatesById = /* @__PURE__ */ new Map();
|
|
3568
|
+
for (const { trait } of traits) {
|
|
3569
|
+
const entry = buildTemplate(trait);
|
|
3570
|
+
templates.set(trait.name, entry);
|
|
3571
|
+
if (trait.id) templatesById.set(trait.id, entry);
|
|
3572
|
+
}
|
|
3573
|
+
const spliced = /* @__PURE__ */ new Set();
|
|
3574
|
+
for (const resolved of traits) {
|
|
3575
|
+
const host = resolved.trait;
|
|
3576
|
+
const st = {
|
|
3577
|
+
templates,
|
|
3578
|
+
templatesById,
|
|
3579
|
+
embedIds: host.traitEmbedIds,
|
|
3580
|
+
// Seed with the host itself so a self-reference never self-splices.
|
|
3581
|
+
visiting: [host.name],
|
|
3582
|
+
merged: [],
|
|
3583
|
+
spliced,
|
|
3584
|
+
hostName: host.name,
|
|
3585
|
+
changed: false
|
|
3586
|
+
};
|
|
3587
|
+
const sm = host.stateMachine;
|
|
3588
|
+
const nextTransitions = sm?.transitions ? sm.transitions.map((t) => ({
|
|
3589
|
+
...t,
|
|
3590
|
+
effects: t.effects ? t.effects.map((e) => spliceExpr(e, false, st)) : t.effects
|
|
3591
|
+
})) : sm?.transitions;
|
|
3592
|
+
const nextTicks = host.ticks ? host.ticks.map((tick) => ({
|
|
3593
|
+
...tick,
|
|
3594
|
+
effects: tick.effects.map((e) => spliceExpr(e, false, st))
|
|
3595
|
+
})) : host.ticks;
|
|
3596
|
+
if (!st.changed) {
|
|
3597
|
+
continue;
|
|
3598
|
+
}
|
|
3599
|
+
const nextEmits = st.merged.length > 0 ? mergeWrapperEmits(host, st.merged) : host.emits;
|
|
3600
|
+
resolved.trait = {
|
|
3601
|
+
...host,
|
|
3602
|
+
...sm ? { stateMachine: { ...sm, transitions: nextTransitions ?? [] } } : {},
|
|
3603
|
+
...nextTicks !== void 0 ? { ticks: nextTicks } : {},
|
|
3604
|
+
...nextEmits !== void 0 ? { emits: nextEmits } : {}
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
if (spliced.size > 0) {
|
|
3608
|
+
removeConsumedWrappers(traits, pages, spliced);
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
// src/resolver/reference-resolver.ts
|
|
3161
3613
|
var refResolverLog = createLogger("almadar:runtime:ref-resolver");
|
|
3614
|
+
function indexOrbitalNodes(orbital, idIndex) {
|
|
3615
|
+
for (const traitRef of orbital.traits ?? []) {
|
|
3616
|
+
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
3617
|
+
const trait = traitRef;
|
|
3618
|
+
if (trait.id) {
|
|
3619
|
+
idIndex.set(trait.id, { kind: "trait", node: trait });
|
|
3620
|
+
}
|
|
3621
|
+
for (const ev of trait.stateMachine?.events ?? []) {
|
|
3622
|
+
if (ev.id) {
|
|
3623
|
+
idIndex.set(ev.id, { kind: "event", node: ev });
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
const entityRef = orbital.entity;
|
|
3629
|
+
if (entityRef && typeof entityRef !== "string" && !("extends" in entityRef) && entityRef.id) {
|
|
3630
|
+
const entity = entityRef;
|
|
3631
|
+
idIndex.set(entity.id, { kind: "entity", node: entity });
|
|
3632
|
+
}
|
|
3633
|
+
for (const pageRef of orbital.pages ?? []) {
|
|
3634
|
+
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
3635
|
+
const page = pageRef;
|
|
3636
|
+
if (page.id) {
|
|
3637
|
+
idIndex.set(page.id, { kind: "page", node: page });
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
function buildIdIndex(orbital, orbitals) {
|
|
3643
|
+
const idIndex = /* @__PURE__ */ new Map();
|
|
3644
|
+
indexOrbitalNodes(orbital, idIndex);
|
|
3645
|
+
for (const imported of orbitals.values()) {
|
|
3646
|
+
indexOrbitalNodes(imported.orbital, idIndex);
|
|
3647
|
+
}
|
|
3648
|
+
return idIndex;
|
|
3649
|
+
}
|
|
3162
3650
|
function renameEventsInRenderUiConfig(node, rename) {
|
|
3163
3651
|
if (node === null || node === void 0) return node;
|
|
3164
3652
|
if (Array.isArray(node)) {
|
|
@@ -3208,25 +3696,30 @@ function renameEventsInEffects(effects, rename) {
|
|
|
3208
3696
|
return effect;
|
|
3209
3697
|
});
|
|
3210
3698
|
}
|
|
3211
|
-
|
|
3699
|
+
var REBIND_ENTITY_PROPS = /* @__PURE__ */ new Set(["entity"]);
|
|
3700
|
+
var ID_ENTITY_PROPS = /* @__PURE__ */ new Set(["entity", "entityType", "source"]);
|
|
3701
|
+
function renameEntityInRenderUiConfig(node, rename, props) {
|
|
3212
3702
|
if (node === null || node === void 0) return node;
|
|
3213
3703
|
if (Array.isArray(node)) {
|
|
3214
|
-
return node.map((item) => renameEntityInRenderUiConfig(item,
|
|
3704
|
+
return node.map((item) => renameEntityInRenderUiConfig(item, rename, props));
|
|
3215
3705
|
}
|
|
3216
3706
|
if (typeof node !== "object") return node;
|
|
3217
3707
|
const obj = node;
|
|
3218
3708
|
const next = { ...obj };
|
|
3219
3709
|
for (const [key, value] of Object.entries(obj)) {
|
|
3220
|
-
if (key
|
|
3221
|
-
|
|
3222
|
-
|
|
3710
|
+
if (props.has(key) && typeof value === "string") {
|
|
3711
|
+
const replaced = rename(value);
|
|
3712
|
+
if (replaced !== void 0) {
|
|
3713
|
+
next[key] = replaced;
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3223
3716
|
}
|
|
3224
|
-
next[key] = renameEntityInRenderUiConfig(value,
|
|
3717
|
+
next[key] = renameEntityInRenderUiConfig(value, rename, props);
|
|
3225
3718
|
}
|
|
3226
3719
|
return next;
|
|
3227
3720
|
}
|
|
3228
|
-
function renameEntityInEffects(effects,
|
|
3229
|
-
return effects.map((effect) => renameEntityInEffect(effect,
|
|
3721
|
+
function renameEntityInEffects(effects, rename, props) {
|
|
3722
|
+
return effects.map((effect) => renameEntityInEffect(effect, rename, props));
|
|
3230
3723
|
}
|
|
3231
3724
|
var ENTITY_AT_POS_1 = /* @__PURE__ */ new Set(["fetch", "ref", "deref", "spawn"]);
|
|
3232
3725
|
var ALL_ARGS_ARE_EFFECTS = /* @__PURE__ */ new Set([
|
|
@@ -3245,20 +3738,22 @@ var ARGS_FROM_POS_2_ARE_EFFECTS = /* @__PURE__ */ new Set([
|
|
|
3245
3738
|
"async/throttle",
|
|
3246
3739
|
"async/interval"
|
|
3247
3740
|
]);
|
|
3248
|
-
function renameEntityInEffect(effect,
|
|
3741
|
+
function renameEntityInEffect(effect, rename, props) {
|
|
3249
3742
|
if (!Array.isArray(effect) || effect.length === 0) return effect;
|
|
3250
3743
|
const op = effect[0];
|
|
3251
3744
|
if (typeof op !== "string") return effect;
|
|
3252
3745
|
if (op === "render-ui" && effect.length >= 3) {
|
|
3253
3746
|
const [, slot, config, ...rest] = effect;
|
|
3254
|
-
const nextConfig = renameEntityInRenderUiConfig(config,
|
|
3747
|
+
const nextConfig = renameEntityInRenderUiConfig(config, rename, props);
|
|
3255
3748
|
return [op, slot, nextConfig, ...rest];
|
|
3256
3749
|
}
|
|
3257
|
-
if (op === "persist" && effect.length >= 3 && effect[2] ===
|
|
3258
|
-
|
|
3750
|
+
if (op === "persist" && effect.length >= 3 && typeof effect[2] === "string") {
|
|
3751
|
+
const replaced = rename(effect[2]);
|
|
3752
|
+
if (replaced !== void 0) return [op, effect[1], replaced, ...effect.slice(3)];
|
|
3259
3753
|
}
|
|
3260
|
-
if (ENTITY_AT_POS_1.has(op) && effect[1] ===
|
|
3261
|
-
|
|
3754
|
+
if (ENTITY_AT_POS_1.has(op) && typeof effect[1] === "string") {
|
|
3755
|
+
const replaced = rename(effect[1]);
|
|
3756
|
+
if (replaced !== void 0) return [op, replaced, ...effect.slice(2)];
|
|
3262
3757
|
}
|
|
3263
3758
|
const skipFirstNonEffectArg = ARGS_FROM_POS_2_ARE_EFFECTS.has(op);
|
|
3264
3759
|
const recurseAll = ALL_ARGS_ARE_EFFECTS.has(op);
|
|
@@ -3267,7 +3762,7 @@ function renameEntityInEffect(effect, oldName, newName) {
|
|
|
3267
3762
|
return effect.map((arg, i) => {
|
|
3268
3763
|
if (i < startIndex) return arg;
|
|
3269
3764
|
if (Array.isArray(arg)) {
|
|
3270
|
-
return renameEntityInEffect(arg,
|
|
3765
|
+
return renameEntityInEffect(arg, rename, props);
|
|
3271
3766
|
}
|
|
3272
3767
|
return arg;
|
|
3273
3768
|
});
|
|
@@ -3279,11 +3774,12 @@ function applyLinkedEntityRename(trait, linkedEntity) {
|
|
|
3279
3774
|
if (!linkedEntity || !atomLinked || linkedEntity === atomLinked) return trait;
|
|
3280
3775
|
const sm = trait.stateMachine;
|
|
3281
3776
|
if (!sm) return { ...trait, linkedEntity };
|
|
3777
|
+
const rename = (name) => name === atomLinked ? linkedEntity : void 0;
|
|
3282
3778
|
const nextTransitions = (sm.transitions ?? []).map((t) => {
|
|
3283
3779
|
const nextEffects = t.effects ? renameEntityInEffects(
|
|
3284
3780
|
t.effects,
|
|
3285
|
-
|
|
3286
|
-
|
|
3781
|
+
rename,
|
|
3782
|
+
REBIND_ENTITY_PROPS
|
|
3287
3783
|
) : t.effects;
|
|
3288
3784
|
return { ...t, effects: nextEffects };
|
|
3289
3785
|
});
|
|
@@ -3299,6 +3795,85 @@ function applyLinkedEntityRename(trait, linkedEntity) {
|
|
|
3299
3795
|
stateMachine: { ...sm, transitions: nextTransitions }
|
|
3300
3796
|
};
|
|
3301
3797
|
}
|
|
3798
|
+
function resolveEntityTokensById(trait, idIndex) {
|
|
3799
|
+
const map = trait.entityRefIds;
|
|
3800
|
+
if (!map) return trait;
|
|
3801
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
3802
|
+
for (const [tokenName, entityId] of Object.entries(map)) {
|
|
3803
|
+
const entry = idIndex.get(entityId);
|
|
3804
|
+
if (entry && entry.kind === "entity") {
|
|
3805
|
+
const currentName = entry.node.name;
|
|
3806
|
+
if (currentName && currentName !== tokenName) {
|
|
3807
|
+
rewrites.set(tokenName, currentName);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
if (rewrites.size === 0) return trait;
|
|
3812
|
+
const rename = (name) => rewrites.get(name);
|
|
3813
|
+
const sm = trait.stateMachine;
|
|
3814
|
+
const nextTransitions = sm?.transitions ? sm.transitions.map((t) => ({
|
|
3815
|
+
...t,
|
|
3816
|
+
effects: t.effects ? renameEntityInEffects(
|
|
3817
|
+
t.effects,
|
|
3818
|
+
rename,
|
|
3819
|
+
ID_ENTITY_PROPS
|
|
3820
|
+
) : t.effects
|
|
3821
|
+
})) : sm?.transitions;
|
|
3822
|
+
const nextTicks = trait.ticks ? trait.ticks.map((tick) => ({
|
|
3823
|
+
...tick,
|
|
3824
|
+
effects: renameEntityInEffects(
|
|
3825
|
+
tick.effects,
|
|
3826
|
+
rename,
|
|
3827
|
+
ID_ENTITY_PROPS
|
|
3828
|
+
)
|
|
3829
|
+
})) : trait.ticks;
|
|
3830
|
+
const nextInitial = trait.initialEffects ? renameEntityInEffects(
|
|
3831
|
+
trait.initialEffects,
|
|
3832
|
+
rename,
|
|
3833
|
+
ID_ENTITY_PROPS
|
|
3834
|
+
) : trait.initialEffects;
|
|
3835
|
+
const nextLinked = trait.linkedEntity !== void 0 ? rewrites.get(trait.linkedEntity) ?? trait.linkedEntity : trait.linkedEntity;
|
|
3836
|
+
refResolverLog.info("entity-ref:id-resolve", {
|
|
3837
|
+
trait: trait.name,
|
|
3838
|
+
rewrites: Object.fromEntries(rewrites)
|
|
3839
|
+
});
|
|
3840
|
+
return {
|
|
3841
|
+
...trait,
|
|
3842
|
+
linkedEntity: nextLinked,
|
|
3843
|
+
...sm ? { stateMachine: { ...sm, transitions: nextTransitions ?? [] } } : {},
|
|
3844
|
+
...nextTicks !== void 0 ? { ticks: nextTicks } : {},
|
|
3845
|
+
...nextInitial !== void 0 ? { initialEffects: nextInitial } : {}
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
var REFERENCE_CONFIG_TYPE_TO_ID_KIND = {
|
|
3849
|
+
entity: "entity",
|
|
3850
|
+
trait: "trait",
|
|
3851
|
+
event: "event"
|
|
3852
|
+
};
|
|
3853
|
+
function resolveConfigRefsById(trait, idIndex) {
|
|
3854
|
+
const schema = trait.config;
|
|
3855
|
+
if (!schema) return trait;
|
|
3856
|
+
let nextSchema;
|
|
3857
|
+
const rewrites = [];
|
|
3858
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
3859
|
+
if (!field.refId || !isReferenceConfigType(field.type)) continue;
|
|
3860
|
+
const expectedKind = REFERENCE_CONFIG_TYPE_TO_ID_KIND[field.type];
|
|
3861
|
+
if (!expectedKind) continue;
|
|
3862
|
+
const entry = idIndex.get(field.refId);
|
|
3863
|
+
if (!entry || entry.kind !== expectedKind) continue;
|
|
3864
|
+
const currentName = entry.kind === "entity" ? entry.node.name : entry.kind === "event" ? entry.node.key : entry.node.name;
|
|
3865
|
+
if (!currentName || currentName === field.default) continue;
|
|
3866
|
+
nextSchema ??= { ...schema };
|
|
3867
|
+
nextSchema[key] = { ...field, default: currentName };
|
|
3868
|
+
rewrites.push({ key, from: field.default, to: currentName });
|
|
3869
|
+
}
|
|
3870
|
+
if (!nextSchema) return trait;
|
|
3871
|
+
refResolverLog.info("config-ref:id-resolve", {
|
|
3872
|
+
trait: trait.name,
|
|
3873
|
+
rewrites
|
|
3874
|
+
});
|
|
3875
|
+
return { ...trait, config: nextSchema };
|
|
3876
|
+
}
|
|
3302
3877
|
function applyEventRenames(trait, renames) {
|
|
3303
3878
|
if (!renames || Object.keys(renames).length === 0) return trait;
|
|
3304
3879
|
const rename = (k) => k !== void 0 && k in renames ? renames[k] : k;
|
|
@@ -3329,21 +3904,55 @@ function applyEventRenames(trait, renames) {
|
|
|
3329
3904
|
emits: nextEmits
|
|
3330
3905
|
};
|
|
3331
3906
|
}
|
|
3907
|
+
function resolveConfigRefEmitNames(trait, callSiteConfig) {
|
|
3908
|
+
const emits = trait.emits ?? [];
|
|
3909
|
+
const hasRef = emits.some((em) => configRefEventKnob(em.event) !== void 0);
|
|
3910
|
+
if (!hasRef) return { trait, errors: [] };
|
|
3911
|
+
const effectiveConfig = {
|
|
3912
|
+
...normalizeCallSiteConfigToValues(trait.config) ?? {},
|
|
3913
|
+
...normalizeCallSiteConfigToValues(callSiteConfig) ?? {}
|
|
3914
|
+
};
|
|
3915
|
+
const errors = [];
|
|
3916
|
+
const nextEmits = emits.map((em) => {
|
|
3917
|
+
if (configRefEventKnob(em.event) === void 0) return em;
|
|
3918
|
+
const result = resolveConfigRefEventName(em.event, trait.config, effectiveConfig);
|
|
3919
|
+
if (!result.ok) {
|
|
3920
|
+
errors.push(
|
|
3921
|
+
`Trait "${trait.name}" emits \`${em.event}\` but the reference is invalid (${result.error}): the knob must be a declared string-typed config field with a default.`
|
|
3922
|
+
);
|
|
3923
|
+
return em;
|
|
3924
|
+
}
|
|
3925
|
+
refResolverLog.debug("emit-config-ref:resolved", {
|
|
3926
|
+
trait: trait.name,
|
|
3927
|
+
ref: em.event,
|
|
3928
|
+
resolved: result.value
|
|
3929
|
+
});
|
|
3930
|
+
return { ...em, event: result.value };
|
|
3931
|
+
});
|
|
3932
|
+
return { trait: { ...trait, emits: nextEmits }, errors };
|
|
3933
|
+
}
|
|
3332
3934
|
var ReferenceResolver = class {
|
|
3333
3935
|
loader;
|
|
3334
3936
|
options;
|
|
3335
3937
|
localTraits;
|
|
3938
|
+
/** id-keyed mirror of `localTraits`, populated wherever the trait carries an `id`. */
|
|
3939
|
+
localTraitsById = /* @__PURE__ */ new Map();
|
|
3336
3940
|
loaderInitialized = false;
|
|
3337
3941
|
constructor(options) {
|
|
3338
3942
|
this.options = options;
|
|
3339
3943
|
this.loader = options.loader;
|
|
3340
3944
|
this.localTraits = options.localTraits ?? /* @__PURE__ */ new Map();
|
|
3945
|
+
for (const trait of this.localTraits.values()) {
|
|
3946
|
+
if (trait.id) {
|
|
3947
|
+
this.localTraitsById.set(trait.id, trait);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3341
3950
|
}
|
|
3342
3951
|
async ensureLoader() {
|
|
3343
3952
|
if (this.loader || this.loaderInitialized) return;
|
|
3344
3953
|
this.loaderInitialized = true;
|
|
3345
3954
|
try {
|
|
3346
|
-
const { ExternalOrbitalLoader } = await import('./external-loader-
|
|
3955
|
+
const { ExternalOrbitalLoader } = await import('./external-loader-FNK5AU6U.js');
|
|
3347
3956
|
this.loader = new ExternalOrbitalLoader(this.options);
|
|
3348
3957
|
} catch {
|
|
3349
3958
|
}
|
|
@@ -3360,11 +3969,12 @@ var ReferenceResolver = class {
|
|
|
3360
3969
|
} };
|
|
3361
3970
|
const traitsList = orbital.traits ?? [];
|
|
3362
3971
|
const alreadyResolved = traitsList.length > 0 && traitsList.every((t) => isInlineTrait(t));
|
|
3363
|
-
const importsResult = alreadyResolved ? { success: true, data: { orbitals: /* @__PURE__ */ new Map() }} : await this.resolveImports(orbital.uses ?? [], sourcePath, importChain);
|
|
3972
|
+
const importsResult = alreadyResolved ? { success: true, data: { orbitals: /* @__PURE__ */ new Map(), idIndex: /* @__PURE__ */ new Map() }} : await this.resolveImports(orbital.uses ?? [], sourcePath, importChain);
|
|
3364
3973
|
if (!importsResult.success) {
|
|
3365
3974
|
return { success: false, errors: importsResult.errors };
|
|
3366
3975
|
}
|
|
3367
3976
|
const imports = importsResult.data;
|
|
3977
|
+
imports.idIndex = buildIdIndex(orbital, imports.orbitals);
|
|
3368
3978
|
const entityResult = this.resolveEntity(orbital.entity, imports);
|
|
3369
3979
|
if (!entityResult.success) {
|
|
3370
3980
|
errors.push(...entityResult.errors);
|
|
@@ -3383,6 +3993,18 @@ var ReferenceResolver = class {
|
|
|
3383
3993
|
if (!entityResult.success || !traitsResult.success || !pagesResult.success) {
|
|
3384
3994
|
return { success: false, errors: ["Internal error: unexpected failure state"] };
|
|
3385
3995
|
}
|
|
3996
|
+
for (const resolvedTrait of traitsResult.data) {
|
|
3997
|
+
resolvedTrait.trait = resolveEntityTokensById(resolvedTrait.trait, imports.idIndex);
|
|
3998
|
+
resolvedTrait.trait = resolveConfigRefsById(resolvedTrait.trait, imports.idIndex);
|
|
3999
|
+
}
|
|
4000
|
+
try {
|
|
4001
|
+
spliceLambdaTraitRefs(traitsResult.data, pagesResult.data);
|
|
4002
|
+
} catch (e) {
|
|
4003
|
+
if (e instanceof LambdaSpliceError) {
|
|
4004
|
+
return { success: false, errors: [e.message] };
|
|
4005
|
+
}
|
|
4006
|
+
throw e;
|
|
4007
|
+
}
|
|
3386
4008
|
return {
|
|
3387
4009
|
success: true,
|
|
3388
4010
|
data: {
|
|
@@ -3406,7 +4028,7 @@ var ReferenceResolver = class {
|
|
|
3406
4028
|
if (this.options.skipExternalLoading) {
|
|
3407
4029
|
return {
|
|
3408
4030
|
success: true,
|
|
3409
|
-
data: { orbitals },
|
|
4031
|
+
data: { orbitals, idIndex: /* @__PURE__ */ new Map() },
|
|
3410
4032
|
warnings: ["External loading skipped"]
|
|
3411
4033
|
};
|
|
3412
4034
|
}
|
|
@@ -3440,7 +4062,7 @@ var ReferenceResolver = class {
|
|
|
3440
4062
|
if (errors.length > 0) {
|
|
3441
4063
|
return { success: false, errors };
|
|
3442
4064
|
}
|
|
3443
|
-
return { success: true, data: { orbitals }, warnings: [] };
|
|
4065
|
+
return { success: true, data: { orbitals, idIndex: /* @__PURE__ */ new Map() }, warnings: [] };
|
|
3444
4066
|
}
|
|
3445
4067
|
/**
|
|
3446
4068
|
* Resolve entity reference.
|
|
@@ -3549,10 +4171,14 @@ var ReferenceResolver = class {
|
|
|
3549
4171
|
*/
|
|
3550
4172
|
resolveTraitRef(traitRef, imports) {
|
|
3551
4173
|
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
4174
|
+
const { trait: resolvedInline, errors } = resolveConfigRefEmitNames(traitRef);
|
|
4175
|
+
if (errors.length > 0) {
|
|
4176
|
+
return { success: false, errors };
|
|
4177
|
+
}
|
|
3552
4178
|
return {
|
|
3553
4179
|
success: true,
|
|
3554
4180
|
data: {
|
|
3555
|
-
trait:
|
|
4181
|
+
trait: resolvedInline,
|
|
3556
4182
|
source: { type: "inline" }
|
|
3557
4183
|
},
|
|
3558
4184
|
warnings: []
|
|
@@ -3567,7 +4193,8 @@ var ReferenceResolver = class {
|
|
|
3567
4193
|
refObj.linkedEntity,
|
|
3568
4194
|
refObj.name,
|
|
3569
4195
|
refObj.events,
|
|
3570
|
-
refObj.listens
|
|
4196
|
+
refObj.listens,
|
|
4197
|
+
refObj.refId
|
|
3571
4198
|
);
|
|
3572
4199
|
}
|
|
3573
4200
|
if (typeof traitRef === "string") {
|
|
@@ -3581,7 +4208,7 @@ var ReferenceResolver = class {
|
|
|
3581
4208
|
/**
|
|
3582
4209
|
* Resolve a trait reference string.
|
|
3583
4210
|
*/
|
|
3584
|
-
resolveTraitRefString(ref, imports, config, linkedEntity, overrideName, eventRenames, listensOverride) {
|
|
4211
|
+
resolveTraitRefString(ref, imports, config, linkedEntity, overrideName, eventRenames, listensOverride, refId) {
|
|
3585
4212
|
const parsed = parseImportedTraitRef(ref);
|
|
3586
4213
|
if (parsed) {
|
|
3587
4214
|
const imported = imports.orbitals.get(parsed.alias);
|
|
@@ -3593,7 +4220,7 @@ var ReferenceResolver = class {
|
|
|
3593
4220
|
]
|
|
3594
4221
|
};
|
|
3595
4222
|
}
|
|
3596
|
-
const trait = this.findTraitInOrbital(imported.orbital, parsed.traitName);
|
|
4223
|
+
const trait = this.findTraitInOrbital(imported.orbital, parsed.traitName, refId, imports.idIndex);
|
|
3597
4224
|
if (!trait) {
|
|
3598
4225
|
return {
|
|
3599
4226
|
success: false,
|
|
@@ -3603,7 +4230,11 @@ var ReferenceResolver = class {
|
|
|
3603
4230
|
};
|
|
3604
4231
|
}
|
|
3605
4232
|
const baseTrait = overrideName ? { ...trait, name: overrideName } : trait;
|
|
3606
|
-
const
|
|
4233
|
+
const { trait: configResolvedTrait, errors: configRefErrors } = resolveConfigRefEmitNames(baseTrait, config);
|
|
4234
|
+
if (configRefErrors.length > 0) {
|
|
4235
|
+
return { success: false, errors: configRefErrors };
|
|
4236
|
+
}
|
|
4237
|
+
const reboundTrait = applyLinkedEntityRename(configResolvedTrait, linkedEntity);
|
|
3607
4238
|
const renamedTrait = applyEventRenames(reboundTrait, eventRenames);
|
|
3608
4239
|
const finalTrait = listensOverride !== void 0 ? { ...renamedTrait, listens: listensOverride } : renamedTrait;
|
|
3609
4240
|
if (listensOverride !== void 0) {
|
|
@@ -3625,10 +4256,14 @@ var ReferenceResolver = class {
|
|
|
3625
4256
|
warnings: []
|
|
3626
4257
|
};
|
|
3627
4258
|
}
|
|
3628
|
-
const localTrait = this.localTraits.get(ref);
|
|
4259
|
+
const localTrait = (refId && this.localTraitsById.get(refId)) ?? this.localTraits.get(ref);
|
|
3629
4260
|
if (localTrait) {
|
|
3630
4261
|
const baseLocal = overrideName ? { ...localTrait, name: overrideName } : localTrait;
|
|
3631
|
-
const
|
|
4262
|
+
const { trait: configResolvedLocal, errors: localConfigRefErrors } = resolveConfigRefEmitNames(baseLocal, config);
|
|
4263
|
+
if (localConfigRefErrors.length > 0) {
|
|
4264
|
+
return { success: false, errors: localConfigRefErrors };
|
|
4265
|
+
}
|
|
4266
|
+
const reboundLocal = applyLinkedEntityRename(configResolvedLocal, linkedEntity);
|
|
3632
4267
|
const renamedLocalTrait = applyEventRenames(reboundLocal, eventRenames);
|
|
3633
4268
|
const finalLocalTrait = listensOverride !== void 0 ? { ...renamedLocalTrait, listens: listensOverride } : renamedLocalTrait;
|
|
3634
4269
|
if (listensOverride !== void 0) {
|
|
@@ -3658,9 +4293,17 @@ var ReferenceResolver = class {
|
|
|
3658
4293
|
};
|
|
3659
4294
|
}
|
|
3660
4295
|
/**
|
|
3661
|
-
* Find a trait in an orbital by name.
|
|
4296
|
+
* Find a trait in an orbital by name. Id-primary: when the calling ref
|
|
4297
|
+
* carries a `refId` and the id index has a matching trait entry, return it
|
|
4298
|
+
* directly — else fall back to the existing name match unchanged.
|
|
3662
4299
|
*/
|
|
3663
|
-
findTraitInOrbital(orbital, traitName) {
|
|
4300
|
+
findTraitInOrbital(orbital, traitName, refId, idIndex) {
|
|
4301
|
+
if (refId && idIndex) {
|
|
4302
|
+
const entry = idIndex.get(refId);
|
|
4303
|
+
if (entry && entry.kind === "trait") {
|
|
4304
|
+
return entry.node;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
3664
4307
|
for (const traitRef of orbital.traits) {
|
|
3665
4308
|
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
3666
4309
|
if (traitRef.name === traitName) {
|
|
@@ -3734,7 +4377,7 @@ var ReferenceResolver = class {
|
|
|
3734
4377
|
/**
|
|
3735
4378
|
* Resolve a page reference string.
|
|
3736
4379
|
*/
|
|
3737
|
-
resolvePageRefString(ref, imports) {
|
|
4380
|
+
resolvePageRefString(ref, imports, refId) {
|
|
3738
4381
|
const parsed = parsePageRef(ref);
|
|
3739
4382
|
if (!parsed) {
|
|
3740
4383
|
return {
|
|
@@ -3751,7 +4394,7 @@ var ReferenceResolver = class {
|
|
|
3751
4394
|
]
|
|
3752
4395
|
};
|
|
3753
4396
|
}
|
|
3754
|
-
const page = this.findPageInOrbital(imported.orbital, parsed.pageName);
|
|
4397
|
+
const page = this.findPageInOrbital(imported.orbital, parsed.pageName, refId, imports.idIndex);
|
|
3755
4398
|
if (!page) {
|
|
3756
4399
|
return {
|
|
3757
4400
|
success: false,
|
|
@@ -3774,7 +4417,7 @@ var ReferenceResolver = class {
|
|
|
3774
4417
|
* Resolve a page reference object with optional path override.
|
|
3775
4418
|
*/
|
|
3776
4419
|
resolvePageRefObject(refObj, imports) {
|
|
3777
|
-
const baseResult = this.resolvePageRefString(refObj.ref, imports);
|
|
4420
|
+
const baseResult = this.resolvePageRefString(refObj.ref, imports, refObj.refId);
|
|
3778
4421
|
if (!baseResult.success) {
|
|
3779
4422
|
return baseResult;
|
|
3780
4423
|
}
|
|
@@ -3795,9 +4438,17 @@ var ReferenceResolver = class {
|
|
|
3795
4438
|
};
|
|
3796
4439
|
}
|
|
3797
4440
|
/**
|
|
3798
|
-
* Find a page in an orbital by name.
|
|
4441
|
+
* Find a page in an orbital by name. Id-primary: when the calling ref
|
|
4442
|
+
* carries a `refId` and the id index has a matching page entry, return it
|
|
4443
|
+
* directly — else fall back to the existing name match unchanged.
|
|
3799
4444
|
*/
|
|
3800
|
-
findPageInOrbital(orbital, pageName) {
|
|
4445
|
+
findPageInOrbital(orbital, pageName, refId, idIndex) {
|
|
4446
|
+
if (refId && idIndex) {
|
|
4447
|
+
const entry = idIndex.get(refId);
|
|
4448
|
+
if (entry && entry.kind === "page") {
|
|
4449
|
+
return { ...entry.node };
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
3801
4452
|
const pages = orbital.pages;
|
|
3802
4453
|
if (!pages) return null;
|
|
3803
4454
|
for (const pageRef of pages) {
|
|
@@ -3830,6 +4481,9 @@ var ReferenceResolver = class {
|
|
|
3830
4481
|
addLocalTraits(traits) {
|
|
3831
4482
|
for (const trait of traits) {
|
|
3832
4483
|
this.localTraits.set(trait.name, trait);
|
|
4484
|
+
if (trait.id) {
|
|
4485
|
+
this.localTraitsById.set(trait.id, trait);
|
|
4486
|
+
}
|
|
3833
4487
|
}
|
|
3834
4488
|
}
|
|
3835
4489
|
/**
|
|
@@ -4052,4 +4706,4 @@ var InMemoryPersistence = class {
|
|
|
4052
4706
|
}
|
|
4053
4707
|
};
|
|
4054
4708
|
|
|
4055
|
-
export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString,
|
|
4709
|
+
export { CALLSITE_PAYLOAD_PREFIX, EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes };
|