@almadar/runtime 5.1.0 → 5.2.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.
@@ -1,4 +1,4 @@
1
- import { I as IEventBus } from './types-DwDhc9Jt.js';
1
+ import { I as IEventBus } from './types-SmmabGZk.js';
2
2
  import { EventPayload } from '@almadar/core';
3
3
 
4
4
  /**
@@ -176,6 +176,7 @@ function createLogger(namespace) {
176
176
  };
177
177
  }
178
178
  var bindLog = createLogger("almadar:runtime:bindings");
179
+ var renderLog = createLogger("almadar:runtime:render-ui");
179
180
  var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
180
181
  function isClientOnlyBinding(value) {
181
182
  if (!value.startsWith("@")) return false;
@@ -186,10 +187,27 @@ function isClientOnlyBinding(value) {
186
187
  }
187
188
  function interpolateProps(props, ctx) {
188
189
  const result = {};
190
+ let anyChanged = false;
189
191
  for (const [key, value] of Object.entries(props)) {
190
- result[key] = interpolateValue(value, ctx);
192
+ const interpolated = interpolateValue(value, ctx);
193
+ result[key] = interpolated;
194
+ if (interpolated !== value) anyChanged = true;
195
+ }
196
+ const entityBindingRaw = props["entity"];
197
+ const typeBindingRaw = props["type"];
198
+ if (typeof entityBindingRaw === "string") {
199
+ const resolvedEntity = result["entity"];
200
+ const resolvedRow = resolvedEntity !== null && typeof resolvedEntity === "object" && !Array.isArray(resolvedEntity) ? resolvedEntity : null;
201
+ const ctxRow = ctx.payload["row"];
202
+ renderLog.debug("interpolateProps:entity", {
203
+ patternType: typeof typeBindingRaw === "string" ? typeBindingRaw : void 0,
204
+ entityBinding: entityBindingRaw,
205
+ resolvedIsObject: resolvedRow !== null,
206
+ resolvedEqualsCtxRow: ctxRow !== void 0 && resolvedRow !== null && resolvedRow === ctxRow,
207
+ resolvedRowId: resolvedRow?.id
208
+ });
191
209
  }
192
- return result;
210
+ return anyChanged ? result : props;
193
211
  }
194
212
  function interpolateValue(value, ctx) {
195
213
  if (value === null || value === void 0) {
@@ -235,12 +253,20 @@ function interpolateEmbeddedBindings(value, ctx) {
235
253
  }
236
254
  function interpolateArray(value, ctx) {
237
255
  if (value.length === 0) {
238
- return [];
256
+ return value;
239
257
  }
240
258
  if (isSExpression(value)) {
241
259
  return evaluate(value, ctx);
242
260
  }
243
- return value.map((item) => interpolateValue(item, ctx));
261
+ const mapped = [];
262
+ let anyChanged = false;
263
+ for (let i = 0; i < value.length; i++) {
264
+ const item = value[i];
265
+ const interpolated = interpolateValue(item, ctx);
266
+ mapped.push(interpolated);
267
+ if (interpolated !== item) anyChanged = true;
268
+ }
269
+ return anyChanged ? mapped : value;
244
270
  }
245
271
  function isSExpression(value) {
246
272
  if (value.length === 0) return false;
@@ -341,6 +367,7 @@ function processEvent(options) {
341
367
  eventKey,
342
368
  payload,
343
369
  entityData,
370
+ config,
344
371
  guardMode = "permissive",
345
372
  strictBindings = false,
346
373
  contextExtensions
@@ -375,7 +402,13 @@ function processEvent(options) {
375
402
  const ctx = createContextFromBindings({
376
403
  entity: entityData,
377
404
  payload,
378
- state: traitState.currentState
405
+ state: traitState.currentState,
406
+ // Surface call-site config so `@config.X` resolves inside
407
+ // guard expressions (matches the validator's allowed sigil
408
+ // list as of v3.12.0). createContextFromBindings already
409
+ // propagates `bindings.config` to ctx.config when present —
410
+ // we just need to pass it through here.
411
+ config
379
412
  }, strictBindings, contextExtensions);
380
413
  try {
381
414
  const guardPasses = evaluateGuard(
@@ -455,6 +488,15 @@ function compositeKey(traitName, scope) {
455
488
  }
456
489
  var StateMachineManager = class {
457
490
  traits = /* @__PURE__ */ new Map();
491
+ /**
492
+ * Per-trait call-site config, surfaced to guard expressions so
493
+ * `@config.X` resolves at runtime. Populated by the orbital's
494
+ * registration step (see `OrbitalServerRuntime.registerOrbital`'s
495
+ * `configByTrait` projection). Empty for atom-scope traits whose
496
+ * call-site config wasn't supplied — guards that read `@config.X`
497
+ * in that case will see `undefined` and short-circuit accordingly.
498
+ */
499
+ traitConfigs = /* @__PURE__ */ new Map();
458
500
  /**
459
501
  * State map keyed by `${traitName}::${entityId | __singleton__}`.
460
502
  *
@@ -497,6 +539,19 @@ var StateMachineManager = class {
497
539
  addTrait(trait) {
498
540
  this.traits.set(trait.name, trait);
499
541
  }
542
+ /**
543
+ * Bind the call-site config for a trait so guard `@config.X`
544
+ * resolves at runtime. Typically called by the orbital
545
+ * registration step right after `addTrait`. Idempotent; passing
546
+ * `undefined` clears the binding.
547
+ */
548
+ setTraitConfig(traitName, config) {
549
+ if (config === void 0) {
550
+ this.traitConfigs.delete(traitName);
551
+ } else {
552
+ this.traitConfigs.set(traitName, config);
553
+ }
554
+ }
500
555
  /**
501
556
  * Remove a trait from the manager.
502
557
  */
@@ -612,6 +667,7 @@ var StateMachineManager = class {
612
667
  eventKey,
613
668
  payload,
614
669
  entityData,
670
+ config: this.traitConfigs.get(traitName),
615
671
  guardMode: this.config.guardMode,
616
672
  strictBindings: this.config.strictBindings,
617
673
  contextExtensions: this.config.contextExtensions
@@ -681,6 +737,7 @@ var StateMachineManager = class {
681
737
  eventKey: entry.eventKey,
682
738
  payload: entry.payload,
683
739
  entityData: entry.entityData,
740
+ config: this.traitConfigs.get(traitName),
684
741
  guardMode: this.config.guardMode,
685
742
  strictBindings: this.config.strictBindings,
686
743
  contextExtensions: this.config.contextExtensions
@@ -1513,6 +1570,106 @@ function createTestExecutor(overrides = {}) {
1513
1570
  debug: true
1514
1571
  });
1515
1572
  }
1573
+
1574
+ // src/PayloadValidator.ts
1575
+ function validateEventPayload(eventKey, payload, schema) {
1576
+ if (!schema || schema.length === 0) return [];
1577
+ const failures = [];
1578
+ for (const field of schema) {
1579
+ if (!field.required) continue;
1580
+ const value = payload?.[field.name];
1581
+ if (value === void 0) {
1582
+ failures.push({ event: eventKey, field: field.name, reason: "missing", expectedType: field.type });
1583
+ continue;
1584
+ }
1585
+ if (value === null) {
1586
+ failures.push({ event: eventKey, field: field.name, reason: "null", expectedType: field.type });
1587
+ }
1588
+ }
1589
+ return failures;
1590
+ }
1591
+ function formatPayloadValidationError(failures) {
1592
+ if (failures.length === 0) return "";
1593
+ const parts = failures.map(
1594
+ (f) => `${f.field} (${f.reason}${f.expectedType ? `, expected ${f.expectedType}` : ""})`
1595
+ );
1596
+ return `Payload validation failed for event '${failures[0].event}': ${parts.join("; ")}`;
1597
+ }
1598
+ function validatePayloadShapes(traits, emits) {
1599
+ const mismatches = [];
1600
+ const emitIndex = /* @__PURE__ */ new Map();
1601
+ for (const [traitName, declarations] of emits) {
1602
+ for (const decl of declarations) {
1603
+ const fields = decl.payloadSchema?.map((p) => p.name) ?? [];
1604
+ emitIndex.set(decl.event, { traitName, fields });
1605
+ }
1606
+ }
1607
+ for (const trait of traits) {
1608
+ if (!trait.listens) continue;
1609
+ for (const listener of trait.listens) {
1610
+ const emitter = emitIndex.get(listener.event);
1611
+ if (!emitter) continue;
1612
+ if (!listener.payloadMapping) continue;
1613
+ const payloadRefs = extractPayloadReferences(listener.payloadMapping);
1614
+ for (const ref of payloadRefs) {
1615
+ if (!emitter.fields.includes(ref)) {
1616
+ mismatches.push({
1617
+ listenerTrait: trait.name,
1618
+ emitterTrait: emitter.traitName,
1619
+ event: listener.event,
1620
+ referencedField: ref,
1621
+ availableFields: emitter.fields
1622
+ });
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ return mismatches;
1628
+ }
1629
+ function extractPayloadReferences(mapping) {
1630
+ const refs = [];
1631
+ function collect(value) {
1632
+ if (typeof value === "string") {
1633
+ const match = value.match(/^@payload\.(\w+)$/);
1634
+ if (match) {
1635
+ refs.push(match[1]);
1636
+ }
1637
+ } else if (typeof value === "object" && value !== null) {
1638
+ if (Array.isArray(value)) {
1639
+ value.forEach(collect);
1640
+ } else {
1641
+ Object.values(value).forEach(collect);
1642
+ }
1643
+ }
1644
+ }
1645
+ Object.values(mapping).forEach(collect);
1646
+ return [...new Set(refs)];
1647
+ }
1648
+ function buildEmitsFromTraits(traits, explicitEmits) {
1649
+ const result = new Map(explicitEmits ?? []);
1650
+ for (const trait of traits) {
1651
+ if (result.has(trait.name)) continue;
1652
+ const emitDecls = [];
1653
+ for (const transition of trait.transitions) {
1654
+ if (!transition.effects) continue;
1655
+ for (const effect of transition.effects) {
1656
+ if (!Array.isArray(effect)) continue;
1657
+ if (effect[0] === "emit" && typeof effect[1] === "string") {
1658
+ const event = effect[1];
1659
+ const payloadObj = effect[2];
1660
+ const payloadSchema = payloadObj ? Object.keys(payloadObj).map((name) => ({ name })) : void 0;
1661
+ if (!emitDecls.some((d) => d.event === event)) {
1662
+ emitDecls.push({ event, payloadSchema });
1663
+ }
1664
+ }
1665
+ }
1666
+ }
1667
+ if (emitDecls.length > 0) {
1668
+ result.set(trait.name, emitDecls);
1669
+ }
1670
+ }
1671
+ return result;
1672
+ }
1516
1673
  var MockPersistenceAdapter = class {
1517
1674
  stores = /* @__PURE__ */ new Map();
1518
1675
  schemas = /* @__PURE__ */ new Map();
@@ -3224,6 +3381,6 @@ var InMemoryPersistence = class {
3224
3381
  }
3225
3382
  };
3226
3383
 
3227
- export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createLogger, createMockPersistence, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent };
3228
- //# sourceMappingURL=chunk-T343XTYB.js.map
3229
- //# sourceMappingURL=chunk-T343XTYB.js.map
3384
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, buildEmitsFromTraits, containsBindings, createContextFromBindings, createInitialTraitState, createLogger, createMockPersistence, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes };
3385
+ //# sourceMappingURL=chunk-OG2NHXES.js.map
3386
+ //# sourceMappingURL=chunk-OG2NHXES.js.map