@almadar/runtime 6.71.0 → 6.72.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,14 +1,14 @@
1
- import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-PAEMYGKV.js';
2
- export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-PAEMYGKV.js';
1
+ import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, EffectExecutor, LIFECYCLE_EVENTS } from './chunk-A474B57C.js';
2
+ export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-A474B57C.js';
3
3
  import { isValidCronExpression } from './chunk-OU3ITB5S.js';
4
- import { createContextFromBindings, checkMutationAccess, resolveCallSitePayloadCaptures, applyRowAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
4
+ import { createContextFromBindings, checkMutationAccess, applyRowAccess, accessDeniedMessage } from './chunk-ZWAOJT6R.js';
5
5
  import './chunk-T4VDAB4C.js';
6
6
  import './chunk-SCRAHWOC.js';
7
7
  import './chunk-MLKGABMK.js';
8
8
  import { createLogger } from '@almadar/logger';
9
9
  import * as nodeModule from 'module';
10
10
  import { evaluateListenPayloadExpr, evaluateGuard, evaluate } from '@almadar/evaluator';
11
- import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, personaFromIdentityRow, normalizeUserContext, isRuntimeEntity, isPageReference } from '@almadar/core';
11
+ import { DEFAULT_VIEWER, buildResolvedTraitConfigs, collectCallsiteCaptureChildren, isInlineTrait, isEntityCall, applyListenPayloadMapping, personaFromIdentityRow, normalizeUserContext, isRuntimeEntity, isPageReference } from '@almadar/core';
12
12
  import { ownerFieldsFromSchema, identityEntityName, entityAccessPoliciesByStoreKey, entityAccessPolicies } from '@almadar/core/mock';
13
13
  import { getPatternFieldsContract } from '@almadar/core/patterns';
14
14
 
@@ -115,6 +115,10 @@ var OrbitalServerRuntime = class {
115
115
  orbitals = /* @__PURE__ */ new Map();
116
116
  eventBus;
117
117
  config;
118
+ /** The bound persistence adapter (mock/in-memory/consumer-supplied). Public
119
+ * so a test can inspect committed rows honestly — `(runtime as any)
120
+ * .persistence` was the alternative, and that cast is what this field
121
+ * visibility replaces. */
118
122
  persistence;
119
123
  listenerCleanups = [];
120
124
  tickBindings = [];
@@ -156,6 +160,15 @@ var OrbitalServerRuntime = class {
156
160
  * `declaredDefaults` and ahead of `callSiteOverride`.
157
161
  */
158
162
  resolvedTraitConfigs = {};
163
+ /**
164
+ * Referrer trait name → the DIRECT children (via `@trait.X`) that need
165
+ * their lifecycle transition re-run under the referrer's `callsitePayload`
166
+ * whenever the referrer's own transition fires — computed once per
167
+ * `register()` via `@almadar/core`'s `collectCallsiteCaptureChildren`
168
+ * (merged across every orbital in the schema, same flattening
169
+ * `resolvedTraitConfigs` uses). See `rerenderCallsiteCaptureChildren`.
170
+ */
171
+ callsiteCaptureChildrenByTrait = /* @__PURE__ */ new Map();
159
172
  constructor(config = {}) {
160
173
  this.config = {
161
174
  mode: "mock",
@@ -356,6 +369,7 @@ var OrbitalServerRuntime = class {
356
369
  this.setupTicks();
357
370
  this.resolvedSchema = schema;
358
371
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
372
+ this.callsiteCaptureChildrenByTrait = this.buildCallsiteCaptureChildrenByTrait(schema);
359
373
  this.installOwnerGate();
360
374
  }
361
375
  /**
@@ -375,6 +389,22 @@ var OrbitalServerRuntime = class {
375
389
  columns: derived
376
390
  });
377
391
  }
392
+ /**
393
+ * Merge `collectCallsiteCaptureChildren` across every orbital in the
394
+ * schema into ONE flat referrer-trait-name → children map — same
395
+ * flattening `resolvedTraitConfigs` uses, safe because trait names are
396
+ * unique within one running schema (the compose/resolve pipeline already
397
+ * relies on that for `configByTrait` and `resolvedTraitConfigs`).
398
+ */
399
+ buildCallsiteCaptureChildrenByTrait(schema) {
400
+ const merged = /* @__PURE__ */ new Map();
401
+ for (const orbital of schema.orbitals) {
402
+ for (const [referrer, children] of collectCallsiteCaptureChildren(orbital)) {
403
+ merged.set(referrer, children);
404
+ }
405
+ }
406
+ return merged;
407
+ }
378
408
  /**
379
409
  * Register an OrbitalSchema synchronously (for backward compatibility).
380
410
  * Note: This version doesn't wait for instance seeding to complete.
@@ -393,6 +423,7 @@ var OrbitalServerRuntime = class {
393
423
  this.setupTicks();
394
424
  this.resolvedSchema = schema;
395
425
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
426
+ this.callsiteCaptureChildrenByTrait = this.buildCallsiteCaptureChildrenByTrait(schema);
396
427
  this.installOwnerGate();
397
428
  }
398
429
  /**
@@ -1372,6 +1403,21 @@ var OrbitalServerRuntime = class {
1372
1403
  onPush,
1373
1404
  clientId
1374
1405
  );
1406
+ await this.rerenderCallsiteCaptureChildren(
1407
+ registered,
1408
+ traitName,
1409
+ cleanPayload ?? {},
1410
+ entityData,
1411
+ entityId,
1412
+ emittedEvents,
1413
+ fetchedData,
1414
+ clientEffects,
1415
+ effectResults,
1416
+ viewer,
1417
+ clientEffectsByTrait,
1418
+ onPush,
1419
+ clientId
1420
+ );
1375
1421
  }
1376
1422
  }
1377
1423
  const states = {};
@@ -1417,7 +1463,7 @@ var OrbitalServerRuntime = class {
1417
1463
  /**
1418
1464
  * Execute effects from a transition
1419
1465
  */
1420
- async executeEffects(registered, traitName, effects, payload, entityData, entityId, emittedEvents, fetchedData, clientEffects, effectResults, user, clientEffectsByTrait, onPush, originClientId) {
1466
+ async executeEffects(registered, traitName, effects, payload, entityData, entityId, emittedEvents, fetchedData, clientEffects, effectResults, user, clientEffectsByTrait, onPush, originClientId, callsitePayload) {
1421
1467
  const entityType = registered.entity.name;
1422
1468
  for (const eff of effects) {
1423
1469
  if (Array.isArray(eff) && eff[0] === "fetch") {
@@ -1533,7 +1579,7 @@ var OrbitalServerRuntime = class {
1533
1579
  case "create": {
1534
1580
  const createData = opRest[0] || {};
1535
1581
  const { id: newId } = await this.persistence.create(opEntityType, createData);
1536
- batchResults.push({ action: "create", entityType: opEntityType, id: newId, ...createData });
1582
+ batchResults.push({ ...createData, action: "create", entityType: opEntityType, id: newId });
1537
1583
  completed.push({ action: "create", entityType: opEntityType, id: newId });
1538
1584
  break;
1539
1585
  }
@@ -1542,7 +1588,7 @@ var OrbitalServerRuntime = class {
1542
1588
  const updateData = opRest[1] || {};
1543
1589
  await this.persistence.update(opEntityType, updateId, updateData);
1544
1590
  const updated = await this.persistence.getById(opEntityType, updateId);
1545
- batchResults.push({ action: "update", entityType: opEntityType, id: updateId, ...updated || updateData });
1591
+ batchResults.push({ ...updated || updateData, action: "update", entityType: opEntityType, id: updateId });
1546
1592
  completed.push({ action: "update", entityType: opEntityType, id: updateId });
1547
1593
  break;
1548
1594
  }
@@ -1581,6 +1627,7 @@ var OrbitalServerRuntime = class {
1581
1627
  const type = targetEntityType || entityType;
1582
1628
  let resultData;
1583
1629
  const sizeBefore = (await this.persistence.list(type)).length;
1630
+ let deniedReason;
1584
1631
  try {
1585
1632
  if (action === "create" || action === "update") {
1586
1633
  this.validateRelationCardinality(type, data || {});
@@ -1590,10 +1637,11 @@ var OrbitalServerRuntime = class {
1590
1637
  switch (action) {
1591
1638
  case "create": {
1592
1639
  if (!checkMutationAccess(data || {}, mutationPolicy, accessBindings)) {
1640
+ deniedReason = "access-denied";
1593
1641
  throw new Error(accessDeniedMessage("create", type));
1594
1642
  }
1595
1643
  const { id } = await this.persistence.create(type, data || {});
1596
- resultData = { id, ...data || {} };
1644
+ resultData = { ...data || {}, id };
1597
1645
  break;
1598
1646
  }
1599
1647
  case "update":
@@ -1602,15 +1650,17 @@ var OrbitalServerRuntime = class {
1602
1650
  if (mutationPolicy !== void 0) {
1603
1651
  const existing = await this.persistence.getById(type, updateId);
1604
1652
  if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings)) {
1653
+ deniedReason = "access-denied";
1605
1654
  throw new Error(accessDeniedMessage("update", type));
1606
1655
  }
1607
1656
  }
1608
1657
  await this.persistence.update(type, updateId, data || {});
1609
1658
  const updated = await this.persistence.getById(type, updateId);
1610
- resultData = updated || { id: updateId, ...data || {} };
1659
+ resultData = updated || { ...data || {}, id: updateId };
1611
1660
  } else {
1612
1661
  effectLog.error("persist:no-row-key", { action, entityType: type });
1613
1662
  if (NO_ROW_KEY_IS_FATAL) {
1663
+ deniedReason = "no-row-key";
1614
1664
  throw new Error(
1615
1665
  `persist ${action} ${type} resolved no row key \u2014 the id was neither on the row being written nor on the request. Bind it before the write, e.g. (set @entity.id ?row.id) on the transition that selects it.`
1616
1666
  );
@@ -1625,6 +1675,7 @@ var OrbitalServerRuntime = class {
1625
1675
  if (mutationPolicy !== void 0) {
1626
1676
  const existing = await this.persistence.getById(type, deleteId);
1627
1677
  if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings)) {
1678
+ deniedReason = "access-denied";
1628
1679
  throw new Error(accessDeniedMessage("delete", type));
1629
1680
  }
1630
1681
  }
@@ -1634,6 +1685,7 @@ var OrbitalServerRuntime = class {
1634
1685
  } else {
1635
1686
  effectLog.error("persist:no-row-key", { action, entityType: type });
1636
1687
  if (NO_ROW_KEY_IS_FATAL) {
1688
+ deniedReason = "no-row-key";
1637
1689
  throw new Error(
1638
1690
  `persist ${action} ${type} resolved no row key \u2014 the id was neither on the row being written nor on the request. Bind it before the write, e.g. (set @entity.id ?row.id) on the transition that selects it.`
1639
1691
  );
@@ -1642,6 +1694,17 @@ var OrbitalServerRuntime = class {
1642
1694
  break;
1643
1695
  }
1644
1696
  }
1697
+ if (resultData === void 0 && (action === "update" || action === "delete")) {
1698
+ effectResults.push({
1699
+ effect: "persist",
1700
+ action,
1701
+ entityType: type,
1702
+ success: false,
1703
+ denied: true,
1704
+ error: `persist ${action} ${type} resolved no row key`
1705
+ });
1706
+ return void 0;
1707
+ }
1645
1708
  const sizeAfter = (await this.persistence.list(type)).length;
1646
1709
  effectLog.debug("persist:store-mutate", {
1647
1710
  action,
@@ -1670,6 +1733,7 @@ var OrbitalServerRuntime = class {
1670
1733
  action,
1671
1734
  entityType: type,
1672
1735
  success: false,
1736
+ ...deniedReason !== void 0 ? { denied: true } : {},
1673
1737
  error: err instanceof Error ? err.message : String(err)
1674
1738
  });
1675
1739
  }
@@ -1964,17 +2028,17 @@ var OrbitalServerRuntime = class {
1964
2028
  state: state?.currentState || "unknown",
1965
2029
  user
1966
2030
  };
2031
+ if (callsitePayload) {
2032
+ bindings.callsitePayload = callsitePayload;
2033
+ }
1967
2034
  const traitDef = registered.traits.find((t) => t.name === traitName);
1968
2035
  const declaredDefaults = collectDeclaredConfigDefaults(traitDef);
1969
2036
  const resolvedDefaults = this.resolvedTraitConfigs[traitName];
1970
2037
  const callSiteOverrideRaw = registered.configByTrait.get(traitName);
1971
- const callSiteOverride = callSiteOverrideRaw ? resolveCallSitePayloadCaptures(
1972
- Object.fromEntries(
1973
- Object.entries(callSiteOverrideRaw).filter(
1974
- ([, v]) => !(typeof v === "string" && v.startsWith("@config."))
1975
- )
1976
- ),
1977
- payload
2038
+ const callSiteOverride = callSiteOverrideRaw ? Object.fromEntries(
2039
+ Object.entries(callSiteOverrideRaw).filter(
2040
+ ([, v]) => !(typeof v === "string" && v.startsWith("@config."))
2041
+ )
1978
2042
  ) : void 0;
1979
2043
  if (declaredDefaults || resolvedDefaults || callSiteOverride) {
1980
2044
  bindings.config = {
@@ -2057,6 +2121,85 @@ var OrbitalServerRuntime = class {
2057
2121
  });
2058
2122
  await executor.executeAll(effects);
2059
2123
  }
2124
+ /**
2125
+ * Re-run a JSX-hoisted inline child trait's (`@trait.X`) lifecycle
2126
+ * transition under `callsitePayload` — the payload of the transition that
2127
+ * just composed it — so its `@callsitePayload.<field>` captures reflect
2128
+ * the composing event instead of staying frozen at whatever the child
2129
+ * captured at its own mount-time INIT (a child renders once at mount and
2130
+ * never again on its own).
2131
+ *
2132
+ * `this.callsiteCaptureChildrenByTrait` (built at `register()` via
2133
+ * `@almadar/core`'s `collectCallsiteCaptureChildren`) gives `traitName`'s
2134
+ * DIRECT children that need this — either because the child itself
2135
+ * captures, or because it is a pass-through to a capturing descendant.
2136
+ * The child's lifecycle event (INIT/LOAD/$MOUNT) is re-dispatched
2137
+ * TARGETED at just that trait, from its CURRENT state (the same
2138
+ * guard-aware `sendEvent`/`canHandleEvent` lookup a mount-time INIT
2139
+ * uses), then its effects run through the SAME `executeEffects` used
2140
+ * everywhere else, with `payload: {}` (a lifecycle event carries none)
2141
+ * and `callsitePayload` set so `@callsitePayload.*` resolves — pushing
2142
+ * into the SAME `clientEffects`/`clientEffectsByTrait`/`effectResults`
2143
+ * so the child's refreshed frame reaches the sidecar under its own trait
2144
+ * name. Recurses into the child's own entry in the same map (still under
2145
+ * the SAME `callsitePayload` — the capture resolves up the embed chain to
2146
+ * the nearest transition that actually has one) for grandchildren;
2147
+ * `visited` guards against a malformed embed graph cycling on itself.
2148
+ * Never goes through `processOrbitalEvent` (that would be re-entrant) —
2149
+ * calls this internal executor directly, exactly like every other
2150
+ * transition's effects.
2151
+ */
2152
+ async rerenderCallsiteCaptureChildren(registered, traitName, callsitePayload, entityData, entityId, emittedEvents, fetchedData, clientEffects, effectResults, user, clientEffectsByTrait, onPush, originClientId, visited = /* @__PURE__ */ new Set()) {
2153
+ const children = this.callsiteCaptureChildrenByTrait.get(traitName);
2154
+ if (!children || children.size === 0) return;
2155
+ for (const childName of children) {
2156
+ if (visited.has(childName)) continue;
2157
+ visited.add(childName);
2158
+ const lifecycleEvent = LIFECYCLE_EVENTS.find((evt) => registered.manager.canHandleEvent(childName, evt));
2159
+ if (lifecycleEvent === void 0) continue;
2160
+ const [entry] = registered.manager.sendEvent(lifecycleEvent, {}, entityData, void 0, void 0, childName, user);
2161
+ if (!entry || !entry.result.executed) continue;
2162
+ xOrbitalLog.debug("callsite-capture-child:rerender", () => ({
2163
+ referrer: traitName,
2164
+ child: childName,
2165
+ lifecycleEvent,
2166
+ callsitePayload: JSON.stringify(callsitePayload)
2167
+ }));
2168
+ await this.executeEffects(
2169
+ registered,
2170
+ childName,
2171
+ entry.result.effects,
2172
+ {},
2173
+ entityData,
2174
+ entityId,
2175
+ emittedEvents,
2176
+ fetchedData,
2177
+ clientEffects,
2178
+ effectResults,
2179
+ user,
2180
+ clientEffectsByTrait,
2181
+ onPush,
2182
+ originClientId,
2183
+ callsitePayload
2184
+ );
2185
+ await this.rerenderCallsiteCaptureChildren(
2186
+ registered,
2187
+ childName,
2188
+ callsitePayload,
2189
+ entityData,
2190
+ entityId,
2191
+ emittedEvents,
2192
+ fetchedData,
2193
+ clientEffects,
2194
+ effectResults,
2195
+ user,
2196
+ clientEffectsByTrait,
2197
+ onPush,
2198
+ originClientId,
2199
+ visited
2200
+ );
2201
+ }
2202
+ }
2060
2203
  // ==========================================================================
2061
2204
  // Relation Population
2062
2205
  // ==========================================================================
@@ -2316,6 +2459,7 @@ var OrbitalServerRuntime = class {
2316
2459
  * Routes:
2317
2460
  * - GET / - List registered orbitals
2318
2461
  * - GET /:orbital - Get orbital info and current states
2462
+ * - GET /:orbital/entities/:entityType - Full mock-store row set (verification/tooling only)
2319
2463
  * - POST /:orbital/events - Send event to orbital (includes data from `fetch` effects)
2320
2464
  */
2321
2465
  router() {
@@ -2361,6 +2505,17 @@ var OrbitalServerRuntime = class {
2361
2505
  }
2362
2506
  });
2363
2507
  });
2508
+ router.get("/:orbital/entities/:entityType", (req, res, next) => {
2509
+ const orbitalName = req.params.orbital;
2510
+ const entityType = req.params.entityType;
2511
+ if (!this.orbitals.has(orbitalName)) {
2512
+ res.status(404).json({ success: false, error: "Orbital not found" });
2513
+ return;
2514
+ }
2515
+ this.persistence.list(entityType).then((rows) => {
2516
+ res.json({ success: true, entityType, rows });
2517
+ }).catch(next);
2518
+ });
2364
2519
  router.post(
2365
2520
  "/:orbital/events",
2366
2521
  async (req, res, next) => {
@@ -1,4 +1,4 @@
1
- import { I as IEventBus } from './types-BaD_ox7e.js';
1
+ import { I as IEventBus } from './types-CWaIuEQn.js';
2
2
  import { EventPayload } from '@almadar/core';
3
3
 
4
4
  /**