@almadar/runtime 5.0.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
  /**
@@ -175,9 +175,8 @@ function createLogger(namespace) {
175
175
  error: (msg, data) => log("ERROR", msg, data)
176
176
  };
177
177
  }
178
-
179
- // src/BindingResolver.ts
180
178
  var bindLog = createLogger("almadar:runtime:bindings");
179
+ var renderLog = createLogger("almadar:runtime:render-ui");
181
180
  var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
182
181
  function isClientOnlyBinding(value) {
183
182
  if (!value.startsWith("@")) return false;
@@ -188,10 +187,27 @@ function isClientOnlyBinding(value) {
188
187
  }
189
188
  function interpolateProps(props, ctx) {
190
189
  const result = {};
190
+ let anyChanged = false;
191
191
  for (const [key, value] of Object.entries(props)) {
192
- 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
+ });
193
209
  }
194
- return result;
210
+ return anyChanged ? result : props;
195
211
  }
196
212
  function interpolateValue(value, ctx) {
197
213
  if (value === null || value === void 0) {
@@ -237,12 +253,20 @@ function interpolateEmbeddedBindings(value, ctx) {
237
253
  }
238
254
  function interpolateArray(value, ctx) {
239
255
  if (value.length === 0) {
240
- return [];
256
+ return value;
241
257
  }
242
258
  if (isSExpression(value)) {
243
259
  return evaluate(value, ctx);
244
260
  }
245
- 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;
246
270
  }
247
271
  function isSExpression(value) {
248
272
  if (value.length === 0) return false;
@@ -343,6 +367,7 @@ function processEvent(options) {
343
367
  eventKey,
344
368
  payload,
345
369
  entityData,
370
+ config,
346
371
  guardMode = "permissive",
347
372
  strictBindings = false,
348
373
  contextExtensions
@@ -377,7 +402,13 @@ function processEvent(options) {
377
402
  const ctx = createContextFromBindings({
378
403
  entity: entityData,
379
404
  payload,
380
- 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
381
412
  }, strictBindings, contextExtensions);
382
413
  try {
383
414
  const guardPasses = evaluateGuard(
@@ -457,6 +488,15 @@ function compositeKey(traitName, scope) {
457
488
  }
458
489
  var StateMachineManager = class {
459
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();
460
500
  /**
461
501
  * State map keyed by `${traitName}::${entityId | __singleton__}`.
462
502
  *
@@ -499,6 +539,19 @@ var StateMachineManager = class {
499
539
  addTrait(trait) {
500
540
  this.traits.set(trait.name, trait);
501
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
+ }
502
555
  /**
503
556
  * Remove a trait from the manager.
504
557
  */
@@ -614,6 +667,7 @@ var StateMachineManager = class {
614
667
  eventKey,
615
668
  payload,
616
669
  entityData,
670
+ config: this.traitConfigs.get(traitName),
617
671
  guardMode: this.config.guardMode,
618
672
  strictBindings: this.config.strictBindings,
619
673
  contextExtensions: this.config.contextExtensions
@@ -683,6 +737,7 @@ var StateMachineManager = class {
683
737
  eventKey: entry.eventKey,
684
738
  payload: entry.payload,
685
739
  entityData: entry.entityData,
740
+ config: this.traitConfigs.get(traitName),
686
741
  guardMode: this.config.guardMode,
687
742
  strictBindings: this.config.strictBindings,
688
743
  contextExtensions: this.config.contextExtensions
@@ -1108,18 +1163,51 @@ var EffectExecutor = class {
1108
1163
  const action = args[0];
1109
1164
  const last = args[args.length - 1];
1110
1165
  const emitCfg = last && typeof last === "object" && !Array.isArray(last) && "emit" in last ? this.extractEmitConfig(last) : void 0;
1166
+ effectLog.debug("persist:dispatch", {
1167
+ action,
1168
+ argCount: args.length,
1169
+ argTypes: args.map((a) => Array.isArray(a) ? "array" : a === null ? "null" : typeof a).join(","),
1170
+ traitName: this.context.traitName,
1171
+ transition: this.context.transition
1172
+ });
1173
+ effectLog.debug("persist:emit-config", {
1174
+ action,
1175
+ hasEmitCfg: emitCfg !== void 0,
1176
+ success: emitCfg?.success,
1177
+ failure: emitCfg?.failure
1178
+ });
1111
1179
  try {
1112
1180
  if (action === "batch") {
1113
1181
  const operations = args[1];
1114
1182
  await this.handlers.persist("batch", "", { operations });
1183
+ effectLog.debug("persist:success", {
1184
+ action,
1185
+ entityType: "batch",
1186
+ opCount: operations.length,
1187
+ willEmit: emitCfg?.success
1188
+ });
1115
1189
  this.emitSuccess(emitCfg, "success", operations);
1190
+ effectLog.debug("persist:emit-fired", { action, eventName: emitCfg?.success });
1116
1191
  } else {
1117
1192
  const entityType = args[1];
1118
1193
  const data = args[2];
1119
1194
  await this.handlers.persist(action, entityType, data);
1195
+ const dataId = typeof data === "string" ? data : data && typeof data === "object" ? data.id : void 0;
1196
+ effectLog.debug("persist:success", {
1197
+ action,
1198
+ entityType,
1199
+ dataId,
1200
+ willEmit: emitCfg?.success
1201
+ });
1120
1202
  this.emitSuccess(emitCfg, "success", data);
1203
+ effectLog.debug("persist:emit-fired", { action, eventName: emitCfg?.success });
1121
1204
  }
1122
1205
  } catch (err) {
1206
+ effectLog.error("persist:error", {
1207
+ action,
1208
+ entityType: action === "batch" ? "batch" : args[1],
1209
+ error: err instanceof Error ? err.message : String(err)
1210
+ });
1123
1211
  this.emitFailure(emitCfg, err);
1124
1212
  throw err;
1125
1213
  }
@@ -1482,6 +1570,106 @@ function createTestExecutor(overrides = {}) {
1482
1570
  debug: true
1483
1571
  });
1484
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
+ }
1485
1673
  var MockPersistenceAdapter = class {
1486
1674
  stores = /* @__PURE__ */ new Map();
1487
1675
  schemas = /* @__PURE__ */ new Map();
@@ -3193,6 +3381,6 @@ var InMemoryPersistence = class {
3193
3381
  }
3194
3382
  };
3195
3383
 
3196
- export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent };
3197
- //# sourceMappingURL=chunk-S5OLFFHT.js.map
3198
- //# sourceMappingURL=chunk-S5OLFFHT.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