@almadar/runtime 5.1.0 → 5.3.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,41 @@ 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
+ const patternType = typeof typeBindingRaw === "string" ? typeBindingRaw : void 0;
199
+ if (typeof entityBindingRaw === "string") {
200
+ const resolvedEntity = result["entity"];
201
+ const resolvedRow = resolvedEntity !== null && typeof resolvedEntity === "object" && !Array.isArray(resolvedEntity) ? resolvedEntity : null;
202
+ const ctxRow = ctx.payload["row"];
203
+ renderLog.debug("interpolateProps:entity", {
204
+ patternType,
205
+ entityBinding: entityBindingRaw,
206
+ resolvedIsObject: resolvedRow !== null,
207
+ resolvedEqualsCtxRow: ctxRow !== void 0 && resolvedRow !== null && resolvedRow === ctxRow,
208
+ resolvedRowId: resolvedRow?.id
209
+ });
191
210
  }
192
- return result;
211
+ if (patternType === "form-section" || patternType === "form") {
212
+ const modeRaw = result["mode"];
213
+ const submitRaw = result["submitEvent"];
214
+ const cancelRaw = result["cancelEvent"];
215
+ bindLog.debug("form-binding", {
216
+ patternType,
217
+ mode: typeof modeRaw === "string" ? modeRaw : void 0,
218
+ submitEvent: typeof submitRaw === "string" ? submitRaw : void 0,
219
+ cancelEvent: typeof cancelRaw === "string" ? cancelRaw : void 0,
220
+ entity: JSON.stringify(result["entity"] ?? null),
221
+ fields: JSON.stringify(result["fields"] ?? null)
222
+ });
223
+ }
224
+ return anyChanged ? result : props;
193
225
  }
194
226
  function interpolateValue(value, ctx) {
195
227
  if (value === null || value === void 0) {
@@ -235,12 +267,20 @@ function interpolateEmbeddedBindings(value, ctx) {
235
267
  }
236
268
  function interpolateArray(value, ctx) {
237
269
  if (value.length === 0) {
238
- return [];
270
+ return value;
239
271
  }
240
272
  if (isSExpression(value)) {
241
273
  return evaluate(value, ctx);
242
274
  }
243
- return value.map((item) => interpolateValue(item, ctx));
275
+ const mapped = [];
276
+ let anyChanged = false;
277
+ for (let i = 0; i < value.length; i++) {
278
+ const item = value[i];
279
+ const interpolated = interpolateValue(item, ctx);
280
+ mapped.push(interpolated);
281
+ if (interpolated !== item) anyChanged = true;
282
+ }
283
+ return anyChanged ? mapped : value;
244
284
  }
245
285
  function isSExpression(value) {
246
286
  if (value.length === 0) return false;
@@ -341,6 +381,7 @@ function processEvent(options) {
341
381
  eventKey,
342
382
  payload,
343
383
  entityData,
384
+ config,
344
385
  guardMode = "permissive",
345
386
  strictBindings = false,
346
387
  contextExtensions
@@ -375,7 +416,13 @@ function processEvent(options) {
375
416
  const ctx = createContextFromBindings({
376
417
  entity: entityData,
377
418
  payload,
378
- state: traitState.currentState
419
+ state: traitState.currentState,
420
+ // Surface call-site config so `@config.X` resolves inside
421
+ // guard expressions (matches the validator's allowed sigil
422
+ // list as of v3.12.0). createContextFromBindings already
423
+ // propagates `bindings.config` to ctx.config when present —
424
+ // we just need to pass it through here.
425
+ config
379
426
  }, strictBindings, contextExtensions);
380
427
  try {
381
428
  const guardPasses = evaluateGuard(
@@ -455,6 +502,15 @@ function compositeKey(traitName, scope) {
455
502
  }
456
503
  var StateMachineManager = class {
457
504
  traits = /* @__PURE__ */ new Map();
505
+ /**
506
+ * Per-trait call-site config, surfaced to guard expressions so
507
+ * `@config.X` resolves at runtime. Populated by the orbital's
508
+ * registration step (see `OrbitalServerRuntime.registerOrbital`'s
509
+ * `configByTrait` projection). Empty for atom-scope traits whose
510
+ * call-site config wasn't supplied — guards that read `@config.X`
511
+ * in that case will see `undefined` and short-circuit accordingly.
512
+ */
513
+ traitConfigs = /* @__PURE__ */ new Map();
458
514
  /**
459
515
  * State map keyed by `${traitName}::${entityId | __singleton__}`.
460
516
  *
@@ -497,6 +553,19 @@ var StateMachineManager = class {
497
553
  addTrait(trait) {
498
554
  this.traits.set(trait.name, trait);
499
555
  }
556
+ /**
557
+ * Bind the call-site config for a trait so guard `@config.X`
558
+ * resolves at runtime. Typically called by the orbital
559
+ * registration step right after `addTrait`. Idempotent; passing
560
+ * `undefined` clears the binding.
561
+ */
562
+ setTraitConfig(traitName, config) {
563
+ if (config === void 0) {
564
+ this.traitConfigs.delete(traitName);
565
+ } else {
566
+ this.traitConfigs.set(traitName, config);
567
+ }
568
+ }
500
569
  /**
501
570
  * Remove a trait from the manager.
502
571
  */
@@ -612,6 +681,7 @@ var StateMachineManager = class {
612
681
  eventKey,
613
682
  payload,
614
683
  entityData,
684
+ config: this.traitConfigs.get(traitName),
615
685
  guardMode: this.config.guardMode,
616
686
  strictBindings: this.config.strictBindings,
617
687
  contextExtensions: this.config.contextExtensions
@@ -681,6 +751,7 @@ var StateMachineManager = class {
681
751
  eventKey: entry.eventKey,
682
752
  payload: entry.payload,
683
753
  entityData: entry.entityData,
754
+ config: this.traitConfigs.get(traitName),
684
755
  guardMode: this.config.guardMode,
685
756
  strictBindings: this.config.strictBindings,
686
757
  contextExtensions: this.config.contextExtensions
@@ -1513,6 +1584,106 @@ function createTestExecutor(overrides = {}) {
1513
1584
  debug: true
1514
1585
  });
1515
1586
  }
1587
+
1588
+ // src/PayloadValidator.ts
1589
+ function validateEventPayload(eventKey, payload, schema) {
1590
+ if (!schema || schema.length === 0) return [];
1591
+ const failures = [];
1592
+ for (const field of schema) {
1593
+ if (!field.required) continue;
1594
+ const value = payload?.[field.name];
1595
+ if (value === void 0) {
1596
+ failures.push({ event: eventKey, field: field.name, reason: "missing", expectedType: field.type });
1597
+ continue;
1598
+ }
1599
+ if (value === null) {
1600
+ failures.push({ event: eventKey, field: field.name, reason: "null", expectedType: field.type });
1601
+ }
1602
+ }
1603
+ return failures;
1604
+ }
1605
+ function formatPayloadValidationError(failures) {
1606
+ if (failures.length === 0) return "";
1607
+ const parts = failures.map(
1608
+ (f) => `${f.field} (${f.reason}${f.expectedType ? `, expected ${f.expectedType}` : ""})`
1609
+ );
1610
+ return `Payload validation failed for event '${failures[0].event}': ${parts.join("; ")}`;
1611
+ }
1612
+ function validatePayloadShapes(traits, emits) {
1613
+ const mismatches = [];
1614
+ const emitIndex = /* @__PURE__ */ new Map();
1615
+ for (const [traitName, declarations] of emits) {
1616
+ for (const decl of declarations) {
1617
+ const fields = decl.payloadSchema?.map((p) => p.name) ?? [];
1618
+ emitIndex.set(decl.event, { traitName, fields });
1619
+ }
1620
+ }
1621
+ for (const trait of traits) {
1622
+ if (!trait.listens) continue;
1623
+ for (const listener of trait.listens) {
1624
+ const emitter = emitIndex.get(listener.event);
1625
+ if (!emitter) continue;
1626
+ if (!listener.payloadMapping) continue;
1627
+ const payloadRefs = extractPayloadReferences(listener.payloadMapping);
1628
+ for (const ref of payloadRefs) {
1629
+ if (!emitter.fields.includes(ref)) {
1630
+ mismatches.push({
1631
+ listenerTrait: trait.name,
1632
+ emitterTrait: emitter.traitName,
1633
+ event: listener.event,
1634
+ referencedField: ref,
1635
+ availableFields: emitter.fields
1636
+ });
1637
+ }
1638
+ }
1639
+ }
1640
+ }
1641
+ return mismatches;
1642
+ }
1643
+ function extractPayloadReferences(mapping) {
1644
+ const refs = [];
1645
+ function collect(value) {
1646
+ if (typeof value === "string") {
1647
+ const match = value.match(/^@payload\.(\w+)$/);
1648
+ if (match) {
1649
+ refs.push(match[1]);
1650
+ }
1651
+ } else if (typeof value === "object" && value !== null) {
1652
+ if (Array.isArray(value)) {
1653
+ value.forEach(collect);
1654
+ } else {
1655
+ Object.values(value).forEach(collect);
1656
+ }
1657
+ }
1658
+ }
1659
+ Object.values(mapping).forEach(collect);
1660
+ return [...new Set(refs)];
1661
+ }
1662
+ function buildEmitsFromTraits(traits, explicitEmits) {
1663
+ const result = new Map(explicitEmits ?? []);
1664
+ for (const trait of traits) {
1665
+ if (result.has(trait.name)) continue;
1666
+ const emitDecls = [];
1667
+ for (const transition of trait.transitions) {
1668
+ if (!transition.effects) continue;
1669
+ for (const effect of transition.effects) {
1670
+ if (!Array.isArray(effect)) continue;
1671
+ if (effect[0] === "emit" && typeof effect[1] === "string") {
1672
+ const event = effect[1];
1673
+ const payloadObj = effect[2];
1674
+ const payloadSchema = payloadObj ? Object.keys(payloadObj).map((name) => ({ name })) : void 0;
1675
+ if (!emitDecls.some((d) => d.event === event)) {
1676
+ emitDecls.push({ event, payloadSchema });
1677
+ }
1678
+ }
1679
+ }
1680
+ }
1681
+ if (emitDecls.length > 0) {
1682
+ result.set(trait.name, emitDecls);
1683
+ }
1684
+ }
1685
+ return result;
1686
+ }
1516
1687
  var MockPersistenceAdapter = class {
1517
1688
  stores = /* @__PURE__ */ new Map();
1518
1689
  schemas = /* @__PURE__ */ new Map();
@@ -3224,6 +3395,6 @@ var InMemoryPersistence = class {
3224
3395
  }
3225
3396
  };
3226
3397
 
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
3398
+ 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 };
3399
+ //# sourceMappingURL=chunk-FAWWBERN.js.map
3400
+ //# sourceMappingURL=chunk-FAWWBERN.js.map