@almadar/runtime 6.43.0 → 6.45.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,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { I as IEventBus, R as RuntimeEvent, a as EventListener, U as Unsubscribe, T as TraitDefinition, b as RuntimeConfig, c as TransitionObserver, d as TraitState, C as ConfigContext, e as TransitionResult, f as EvaluationContextExtensions, E as EffectHandlers } from './types-p_L8-Sk4.js';
2
+ import { I as IEventBus, R as RuntimeEvent, a as EventListener, U as Unsubscribe, T as TraitDefinition, b as RuntimeConfig, c as TransitionObserver, d as TraitState, C as ConfigContext, e as TransitionResult, f as EvaluationContextExtensions, E as EffectHandlers } from './types-CL03tjGU.js';
3
3
  import { EventPayload, EventId, EntityRow, UserContext, Orbital, ServiceCallResult, TraitConfig, DeclaredTraitConfig, Entity, OrbitalSchema, Trait, PatternConfig, ResolvedPatternProps, SExpr, BusEventSource, RawUserClaims, OrbitalDefinition, TraitTick } from '@almadar/core';
4
4
 
5
5
  /**
@@ -937,6 +937,13 @@ interface RegisteredOrbital {
937
937
  * `state.fields`. The trait's `@entity` binding is a three-layer merge:
938
938
  * declared entity field defaults < persistence entityData < this map.
939
939
  * Mutated only by the `set` effect handler.
940
+ *
941
+ * For a `[shared]` linked entity the map is keyed `$shared::<entityName>`
942
+ * instead of the trait name — ONE frame across every bound trait, the
943
+ * server half of the client hook's `sharedKeyByTraitName` groups. Without
944
+ * it a writer trait's `(set @entity.X …)` was invisible to a sibling's
945
+ * effect interpolation (the chat thread's refetch filter read "" while the
946
+ * composer held the open channel).
940
947
  */
941
948
  traitFieldStates: Map<string, EntityRow>;
942
949
  }
@@ -1199,6 +1206,14 @@ declare class OrbitalServerRuntime {
1199
1206
  * std registry on disk.
1200
1207
  */
1201
1208
  register(schema: OrbitalSchema): Promise<void>;
1209
+ /**
1210
+ * Teach the mock seeder which columns hold the viewer's id, derived from the
1211
+ * schema's `[identity]` entity. No-op for a program that declares none, so
1212
+ * unmigrated apps seed exactly as before. Mirrors
1213
+ * `orbital-core/src/runtime/seed.rs::owner_fields_from_schema` — a feature
1214
+ * that lives on only one execution path is not a feature.
1215
+ */
1216
+ private applyIdentityOwnerFields;
1202
1217
  /**
1203
1218
  * Register an OrbitalSchema synchronously (for backward compatibility).
1204
1219
  * Note: This version doesn't wait for instance seeding to complete.
@@ -1333,6 +1348,19 @@ declare class OrbitalServerRuntime {
1333
1348
  setDefaultUser(user: UserContext | undefined): void;
1334
1349
  /** The viewer a dev host is currently presenting the app as. */
1335
1350
  getDefaultUser(): UserContext | undefined;
1351
+ /**
1352
+ * `traitFieldStates` key for one trait: its own name, or — when its linked
1353
+ * entity is `[shared]` — `$shared::<entityName>` so every trait bound to
1354
+ * that entity reads/writes ONE frame. The server twin of the client hook's
1355
+ * `sharedKeyByTraitName` (useTraitStateMachine): without it, a sibling
1356
+ * trait's effect interpolation (a fetch filter reading
1357
+ * `@entity.activeChannel`) never saw the writer's `(set …)`.
1358
+ */
1359
+ private sharedFieldKey;
1360
+ /** Resolve an entity name to its declared `shared` flag: the orbital's own
1361
+ * entity, its auxiliary entities, then other registered orbitals' primary
1362
+ * entities (cross-orbital binds like a membership rail in the chat page). */
1363
+ private isSharedEntity;
1336
1364
  /**
1337
1365
  * Process an event for an orbital
1338
1366
  *
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- export { F as ClientEffectTuple, G as ClientNavigateTuple, H as ClientNotifyTuple, J as ClientRenderUITuple, K as EffectResult, e as InMemoryPersistence, M as LiveBroadcastItem, N as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, Q as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, T as RuntimeTraitTick, p as collectDeclaredConfigDefaults, V as createOrbitalServerRuntime } from './OrbitalServerRuntime-f0EZSw4f.js';
3
- import './types-p_L8-Sk4.js';
2
+ export { F as ClientEffectTuple, G as ClientNavigateTuple, H as ClientNotifyTuple, J as ClientRenderUITuple, K as EffectResult, e as InMemoryPersistence, M as LiveBroadcastItem, N as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, Q as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, T as RuntimeTraitTick, p as collectDeclaredConfigDefaults, V as createOrbitalServerRuntime } from './OrbitalServerRuntime-A2h0EikK.js';
3
+ import './types-CL03tjGU.js';
4
4
  import '@almadar/core';
@@ -1,5 +1,5 @@
1
- import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, createContextFromBindings, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-2SM626PK.js';
2
- export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-2SM626PK.js';
1
+ import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, createContextFromBindings, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-FIBEYLME.js';
2
+ export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-FIBEYLME.js';
3
3
  import { isValidCronExpression } from './chunk-OU3ITB5S.js';
4
4
  import './chunk-T4VDAB4C.js';
5
5
  import './chunk-SCRAHWOC.js';
@@ -7,7 +7,8 @@ import './chunk-MLKGABMK.js';
7
7
  import { createLogger } from '@almadar/logger';
8
8
  import * as nodeModule from 'module';
9
9
  import { evaluateListenPayloadExpr, evaluateGuard, evaluate } from '@almadar/evaluator';
10
- import { buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, normalizeUserContext } from '@almadar/core';
10
+ import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, normalizeUserContext, isRuntimeEntity } from '@almadar/core';
11
+ import { ownerFieldsFromSchema, identityEntityName } from '@almadar/core/mock';
11
12
 
12
13
  // src/identity/routing.ts
13
14
  function eventRouteKey(eventName, eventId) {
@@ -115,7 +116,12 @@ var OrbitalServerRuntime = class {
115
116
  // Default to mock mode for preview
116
117
  autoPreprocess: false,
117
118
  namespaceEvents: true,
118
- ...config
119
+ ...config,
120
+ // `@user` is never Null. A host that named no viewer (no ALMADAR_PERSONA,
121
+ // no auth yet) otherwise renders every `viewerName: @user.name` blank, so
122
+ // the app cannot say who you are. DEFAULT_VIEWER carries an empty `role`,
123
+ // so no `@user.role` guard changes outcome — see its doc in @almadar/core.
124
+ defaultUser: config.defaultUser ?? DEFAULT_VIEWER
119
125
  };
120
126
  this.eventBus = new EventBus();
121
127
  if (config.loaderConfig?.loader) {
@@ -135,7 +141,9 @@ var OrbitalServerRuntime = class {
135
141
  // Let the dev viewer own some seeded rows, so ownership-scoped views
136
142
  // ("only mine") have data instead of being indistinguishable from a
137
143
  // broken filter. Columns are declared, never inferred by name.
138
- ownerId: config.defaultUser?.id,
144
+ // `this.config`, not `config` — the DEFAULT_VIEWER fallback is applied
145
+ // there, and reading the raw param leaves every seeded row unowned.
146
+ ownerId: this.config.defaultUser?.id,
139
147
  ownerFields: config.mockOwnerFields
140
148
  });
141
149
  if (config.debug) {
@@ -293,6 +301,7 @@ var OrbitalServerRuntime = class {
293
301
  registerLog.warn("register:no-loader", { name: schema.name });
294
302
  }
295
303
  }
304
+ this.applyIdentityOwnerFields(schema);
296
305
  for (const orbital of schema.orbitals) {
297
306
  await this.registerOrbitalAsync(orbital);
298
307
  }
@@ -301,6 +310,23 @@ var OrbitalServerRuntime = class {
301
310
  this.resolvedSchema = schema;
302
311
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
303
312
  }
313
+ /**
314
+ * Teach the mock seeder which columns hold the viewer's id, derived from the
315
+ * schema's `[identity]` entity. No-op for a program that declares none, so
316
+ * unmigrated apps seed exactly as before. Mirrors
317
+ * `orbital-core/src/runtime/seed.rs::owner_fields_from_schema` — a feature
318
+ * that lives on only one execution path is not a feature.
319
+ */
320
+ applyIdentityOwnerFields(schema) {
321
+ if (!(this.persistence instanceof MockPersistenceAdapter)) return;
322
+ const derived = ownerFieldsFromSchema(schema);
323
+ if (derived.length === 0) return;
324
+ this.persistence.addOwnerFields(derived);
325
+ persistLog.debug("mock:identity-owner-fields", {
326
+ identity: identityEntityName(schema),
327
+ columns: derived
328
+ });
329
+ }
304
330
  /**
305
331
  * Register an OrbitalSchema synchronously (for backward compatibility).
306
332
  * Note: This version doesn't wait for instance seeding to complete.
@@ -310,6 +336,7 @@ var OrbitalServerRuntime = class {
310
336
  if (this.config.debug) {
311
337
  registerLog.debug("register:schema-sync", { name: schema.name });
312
338
  }
339
+ this.applyIdentityOwnerFields(schema);
313
340
  for (const orbital of schema.orbitals) {
314
341
  this.registerOrbital(orbital);
315
342
  }
@@ -317,6 +344,7 @@ var OrbitalServerRuntime = class {
317
344
  this.setupTicks();
318
345
  this.resolvedSchema = schema;
319
346
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
347
+ this.applyIdentityOwnerFields(schema);
320
348
  }
321
349
  /**
322
350
  * Returns the schema that this runtime is currently executing, post-
@@ -622,6 +650,16 @@ var OrbitalServerRuntime = class {
622
650
  const routeKey = eventRouteKey(bareEvent, listener.eventId);
623
651
  const cleanup = this.eventBus.on(routeKey, async (event) => {
624
652
  if (!matcher(event.source)) return;
653
+ if (event.source?.originClientId !== void 0) {
654
+ if (this.config.debug) {
655
+ xOrbitalLog.debug("listen:skip-client-origin", {
656
+ receiverTrait: trait.name,
657
+ event: listener.event,
658
+ originClientId: event.source.originClientId
659
+ });
660
+ }
661
+ return;
662
+ }
625
663
  if (this.config.debug) {
626
664
  xOrbitalLog.debug("listen:received", () => ({
627
665
  receiverOrbital: orbitalName,
@@ -887,6 +925,34 @@ var OrbitalServerRuntime = class {
887
925
  // ==========================================================================
888
926
  // Event Processing
889
927
  // ==========================================================================
928
+ /**
929
+ * `traitFieldStates` key for one trait: its own name, or — when its linked
930
+ * entity is `[shared]` — `$shared::<entityName>` so every trait bound to
931
+ * that entity reads/writes ONE frame. The server twin of the client hook's
932
+ * `sharedKeyByTraitName` (useTraitStateMachine): without it, a sibling
933
+ * trait's effect interpolation (a fetch filter reading
934
+ * `@entity.activeChannel`) never saw the writer's `(set …)`.
935
+ */
936
+ sharedFieldKey(registered, traitName) {
937
+ const trait = registered.traits.find((t) => t.name === traitName);
938
+ const linked = trait?.linkedEntity ?? registered.entity.name;
939
+ return this.isSharedEntity(registered, linked) ? `$shared::${linked}` : traitName;
940
+ }
941
+ /** Resolve an entity name to its declared `shared` flag: the orbital's own
942
+ * entity, its auxiliary entities, then other registered orbitals' primary
943
+ * entities (cross-orbital binds like a membership rail in the chat page). */
944
+ isSharedEntity(registered, entityName) {
945
+ if (registered.entity.name === entityName) return registered.entity.shared === true;
946
+ for (const aux of registered.schema.auxiliaryEntities ?? []) {
947
+ if (typeof aux === "object" && !isEntityCall(aux) && aux.name === entityName) {
948
+ return aux.shared === true;
949
+ }
950
+ }
951
+ for (const other of this.orbitals.values()) {
952
+ if (other.entity.name === entityName) return other.entity.shared === true;
953
+ }
954
+ return false;
955
+ }
890
956
  /**
891
957
  * Process an event for an orbital
892
958
  *
@@ -982,9 +1048,10 @@ var OrbitalServerRuntime = class {
982
1048
  }
983
1049
  }
984
1050
  const entityByTrait = {};
985
- for (const [name, fields] of registered.traitFieldStates) {
1051
+ for (const trait of registered.traits) {
1052
+ const fields = registered.traitFieldStates.get(this.sharedFieldKey(registered, trait.name));
986
1053
  if (fields && Object.keys(fields).length > 0) {
987
- entityByTrait[name] = fields;
1054
+ entityByTrait[trait.name] = fields;
988
1055
  }
989
1056
  }
990
1057
  const results = registered.manager.sendEvent(
@@ -1099,6 +1166,9 @@ var OrbitalServerRuntime = class {
1099
1166
  if (stamp.eventId === void 0 && emitContract?.eventId !== void 0) {
1100
1167
  stamp.eventId = emitContract.eventId;
1101
1168
  }
1169
+ if (originClientId !== void 0 && stamp.originClientId === void 0) {
1170
+ stamp.originClientId = originClientId;
1171
+ }
1102
1172
  this.eventBus.emit(event, eventPayload, stamp, eventRouteKey(event, stamp.eventId));
1103
1173
  const emittedItem = { event, payload: eventPayload, source: stamp };
1104
1174
  emittedEvents.push(emittedItem);
@@ -1127,10 +1197,11 @@ var OrbitalServerRuntime = class {
1127
1197
  }
1128
1198
  },
1129
1199
  set: async (targetId, field, value) => {
1130
- let fieldState = registered.traitFieldStates.get(traitName);
1200
+ const fieldKey = this.sharedFieldKey(registered, traitName);
1201
+ let fieldState = registered.traitFieldStates.get(fieldKey);
1131
1202
  if (!fieldState) {
1132
1203
  fieldState = {};
1133
- registered.traitFieldStates.set(traitName, fieldState);
1204
+ registered.traitFieldStates.set(fieldKey, fieldState);
1134
1205
  }
1135
1206
  fieldState[field] = value;
1136
1207
  effectResults.push({
@@ -1503,7 +1574,10 @@ var OrbitalServerRuntime = class {
1503
1574
  bindings: bindingsRef ?? {},
1504
1575
  context: contextRef ?? { traitName, orbitalName: registered.schema.name, state: "unknown", transition: "unknown" },
1505
1576
  debug: this.config.debug,
1506
- contextExtensions: this.config.contextExtensions
1577
+ contextExtensions: this.config.contextExtensions,
1578
+ // Same render-time marker boundary as the outer executor
1579
+ // (including the [shared]-defers-regardless-of-persistence rule).
1580
+ deferRenderBindings: registered.entity === void 0 || isRuntimeEntity(registered.entity) || registered.entity.shared === true
1507
1581
  });
1508
1582
  for (const innerEffect of atomicEffects) {
1509
1583
  if (atomicFailed) break;
@@ -1591,7 +1665,7 @@ var OrbitalServerRuntime = class {
1591
1665
  };
1592
1666
  }
1593
1667
  const entityFieldDefaults = collectDeclaredEntityDefaults(registered.entity);
1594
- const traitFieldState = registered.traitFieldStates.get(traitName);
1668
+ const traitFieldState = registered.traitFieldStates.get(this.sharedFieldKey(registered, traitName));
1595
1669
  if (entityFieldDefaults || traitFieldState) {
1596
1670
  bindings.entity = {
1597
1671
  ...entityFieldDefaults ?? {},
@@ -1616,7 +1690,19 @@ var OrbitalServerRuntime = class {
1616
1690
  bindings,
1617
1691
  context,
1618
1692
  debug: this.config.debug,
1619
- contextExtensions: this.config.contextExtensions
1693
+ contextExtensions: this.config.contextExtensions,
1694
+ // Carry `@entity`-dependent render-ui leaves as render-time markers
1695
+ // for runtime-only (and unlinked) entities: the client's local state
1696
+ // machines execute the same `(set)` writes, so the renderer resolves
1697
+ // markers against its live store — otherwise these SSE-pushed eager
1698
+ // props clobber the client's live marker frames on every event.
1699
+ // Persistent NON-shared entities stay eager: their `@entity` merges
1700
+ // persistence rows the client does not hold. A `[shared]` entity is
1701
+ // deferred REGARDLESS of persistence — its trait frame's scalars are
1702
+ // client-held by contract (one live frame across bound traits), and
1703
+ // the eager clobber pinned the chat composer's controlled input to
1704
+ // the server's flush-time "" on every keystroke round-trip.
1705
+ deferRenderBindings: registered.entity === void 0 || isRuntimeEntity(registered.entity) || registered.entity.shared === true
1620
1706
  });
1621
1707
  await executor.executeAll(effects);
1622
1708
  }
@@ -1,4 +1,4 @@
1
- import { I as IEventBus } from './types-p_L8-Sk4.js';
1
+ import { I as IEventBus } from './types-CL03tjGU.js';
2
2
  import { EventPayload } from '@almadar/core';
3
3
 
4
4
  /**
@@ -5,7 +5,7 @@ import { createLogger, setNamespaceLevel } from '@almadar/logger';
5
5
  import { createMinimalContext, resolveBinding, evaluate, evaluateGuard, SExpressionEvaluator } from '@almadar/evaluator';
6
6
  export { createMinimalContext } from '@almadar/evaluator';
7
7
  import { isKnownStdOperator } from '@almadar/std/registry';
8
- import { OrbitalSchemaSchema, isInlineTrait, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef, isReferenceConfigType, configRefEventKnob, normalizeCallSiteConfigToValues, resolveConfigRefEventName } from '@almadar/core';
8
+ import { containsEntityBinding, containsPayloadBinding, RENDER_BINDING_MARKER, OrbitalSchemaSchema, isInlineTrait, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef, isReferenceConfigType, configRefEventKnob, normalizeCallSiteConfigToValues, resolveConfigRefEventName } from '@almadar/core';
9
9
  export { normalizeCallSiteConfigToValues } from '@almadar/core';
10
10
  import { sampleRowCount, sampleRow } from '@almadar/core/mock';
11
11
 
@@ -291,6 +291,7 @@ function isValidDurationString(interval) {
291
291
  }
292
292
  var bindLog = createLogger("almadar:runtime:bindings");
293
293
  setNamespaceLevel("almadar:runtime:bindings", "WARN");
294
+ var deferLog = createLogger("almadar:runtime:defer");
294
295
  var renderLog = createLogger("almadar:runtime:render-ui");
295
296
  var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
296
297
  var CALLSITE_PAYLOAD_PREFIX = "@callsitePayload.";
@@ -396,6 +397,45 @@ function interpolateValue(value, ctx) {
396
397
  }
397
398
  return value;
398
399
  }
400
+ function deferEntityBindings(value, ctx, configHops = 0) {
401
+ if (typeof value === "string") {
402
+ if (containsEntityBinding(value) && !containsPayloadBinding(value)) {
403
+ return { [RENDER_BINDING_MARKER]: true, expression: value };
404
+ }
405
+ if (value.startsWith("@config.") && !value.includes(" ") && configHops < 8) {
406
+ const hop = ctx.config?.[value.slice("@config.".length)];
407
+ deferLog.debug("defer:config-hop", () => ({
408
+ forward: value,
409
+ hopType: typeof hop,
410
+ hopPreview: typeof hop === "string" ? hop : Array.isArray(hop) ? "array" : hop === void 0 ? "undefined" : "object"
411
+ }));
412
+ if (hop !== void 0 && hop !== value) {
413
+ return deferEntityBindings(hop, ctx, configHops + 1);
414
+ }
415
+ }
416
+ return interpolateValue(value, ctx);
417
+ }
418
+ if (Array.isArray(value)) {
419
+ if (value.length === 3 && value[0] === "fn" && typeof value[1] === "string") {
420
+ return value;
421
+ }
422
+ if (isSExpression(value)) {
423
+ if (containsEntityBinding(value) && !containsPayloadBinding(value)) {
424
+ return { [RENDER_BINDING_MARKER]: true, expression: value };
425
+ }
426
+ return interpolateValue(value, ctx);
427
+ }
428
+ return value.map((item) => deferEntityBindings(item, ctx));
429
+ }
430
+ if (value !== null && typeof value === "object") {
431
+ const out = {};
432
+ for (const [key, item] of Object.entries(value)) {
433
+ out[key] = deferEntityBindings(item, ctx);
434
+ }
435
+ return out;
436
+ }
437
+ return value;
438
+ }
399
439
  var inFlightConfigRecursions = /* @__PURE__ */ new Set();
400
440
  function interpolateString(value, ctx) {
401
441
  if (value.startsWith("@") && isPureBinding(value)) {
@@ -1178,6 +1218,18 @@ function resolveArgs(args, bindings, strictBindings, contextExtensions) {
1178
1218
  const ctx = createContextFromBindings(bindings, strictBindings, contextExtensions);
1179
1219
  return args.map((arg) => interpolateValue(arg, ctx));
1180
1220
  }
1221
+ function interpolateFilterTraitRefs(value, ctx) {
1222
+ if (typeof value === "string") {
1223
+ return value === "@entity" || value.startsWith("@entity.") ? interpolateValue(value, ctx) : value;
1224
+ }
1225
+ if (Array.isArray(value)) {
1226
+ if (value.length >= 2 && value[0] === "object/get" && value[1] === "@entity") {
1227
+ return value;
1228
+ }
1229
+ return value.map((v) => interpolateFilterTraitRefs(v, ctx));
1230
+ }
1231
+ return value;
1232
+ }
1181
1233
  var EffectExecutor = class _EffectExecutor {
1182
1234
  handlers;
1183
1235
  bindings;
@@ -1186,6 +1238,7 @@ var EffectExecutor = class _EffectExecutor {
1186
1238
  strictBindings;
1187
1239
  contextExtensions;
1188
1240
  evaluator;
1241
+ deferRenderBindings;
1189
1242
  constructor(options) {
1190
1243
  this.handlers = options.handlers;
1191
1244
  this.bindings = options.bindings;
@@ -1194,6 +1247,7 @@ var EffectExecutor = class _EffectExecutor {
1194
1247
  this.strictBindings = options.strictBindings ?? false;
1195
1248
  this.contextExtensions = options.contextExtensions;
1196
1249
  this.evaluator = options.evaluator;
1250
+ this.deferRenderBindings = options.deferRenderBindings ?? false;
1197
1251
  }
1198
1252
  _evaluator;
1199
1253
  getEvaluator() {
@@ -1293,9 +1347,15 @@ var EffectExecutor = class _EffectExecutor {
1293
1347
  const isCompound = operator === "do" || operator === "when" || operator === "let" || operator === "if";
1294
1348
  const isSetPathForm = operator === "set" && args.length >= 2 && typeof args[0] === "string" && args[0].startsWith("@entity.");
1295
1349
  const isFetchLike = (operator === "fetch" || operator === "ref" || operator === "deref" || operator === "os/watch-collection" || operator === "os/watch") && args.length >= 2 && args[1] !== null && typeof args[1] === "object" && !Array.isArray(args[1]);
1350
+ const isDeferringRenderUI = (operator === "render-ui" || operator === "render") && this.deferRenderBindings;
1296
1351
  let resolvedArgs;
1297
1352
  if (isCompound) {
1298
1353
  resolvedArgs = args;
1354
+ } else if (isDeferringRenderUI) {
1355
+ const ctx = createContextFromBindings(this.bindings, this.strictBindings, this.contextExtensions);
1356
+ resolvedArgs = args.map(
1357
+ (arg, index) => index === 0 ? interpolateValue(arg, ctx) : deferEntityBindings(arg, ctx)
1358
+ );
1299
1359
  } else if (isSetPathForm) {
1300
1360
  const ctx = createContextFromBindings(this.bindings, this.strictBindings, this.contextExtensions);
1301
1361
  const resolvedRest = [];
@@ -1316,7 +1376,7 @@ var EffectExecutor = class _EffectExecutor {
1316
1376
  const opts = args[1];
1317
1377
  const resolvedOpts = {
1318
1378
  ...opts.id !== void 0 && { id: interpolateValue(opts.id, ctx) },
1319
- ...opts.filter !== void 0 && { filter: opts.filter },
1379
+ ...opts.filter !== void 0 && { filter: interpolateFilterTraitRefs(opts.filter, ctx) },
1320
1380
  ...opts.limit !== void 0 && { limit: interpolateValue(opts.limit, ctx) },
1321
1381
  ...opts.offset !== void 0 && { offset: interpolateValue(opts.offset, ctx) },
1322
1382
  ...opts.include !== void 0 && { include: interpolateValue(opts.include, ctx) },
@@ -1868,7 +1928,8 @@ var EffectExecutor = class _EffectExecutor {
1868
1928
  context: this.context,
1869
1929
  debug: this.debug,
1870
1930
  strictBindings: this.strictBindings,
1871
- contextExtensions: this.contextExtensions
1931
+ contextExtensions: this.contextExtensions,
1932
+ deferRenderBindings: this.deferRenderBindings
1872
1933
  });
1873
1934
  await bodyExecutor.execute(body);
1874
1935
  break;
@@ -2346,6 +2407,22 @@ var MockPersistenceAdapter = class {
2346
2407
  seedRandom(this.config.seed);
2347
2408
  mockLog.debug("mock:adapter:init", { seed: this.config.seed });
2348
2409
  }
2410
+ /**
2411
+ * Add owner columns discovered after construction.
2412
+ *
2413
+ * The adapter is built before any schema is registered, so schema-DERIVED
2414
+ * owner columns (relation fields pointing at the `[identity]` entity) can
2415
+ * only arrive later.
2416
+ *
2417
+ * ⚠️ Seeding is EAGER — `registerEntity()` seeds immediately — so callers must
2418
+ * supply these columns BEFORE registering any orbital. Calling this afterwards
2419
+ * silently stamps nothing.
2420
+ */
2421
+ addOwnerFields(fields) {
2422
+ if (fields.length === 0) return;
2423
+ const merged = /* @__PURE__ */ new Set([...this.config.ownerFields ?? [], ...fields]);
2424
+ this.config.ownerFields = [...merged];
2425
+ }
2349
2426
  /** Re-anchor the PRNG to the configured seed. Called before every
2350
2427
  * re-seed loop so identical reseed sequences produce identical rows
2351
2428
  * (timestamps + generated fields). Without this, the first
@@ -2422,6 +2499,9 @@ var MockPersistenceAdapter = class {
2422
2499
  if (relationFields.length === 0) continue;
2423
2500
  for (const row of store.values()) {
2424
2501
  for (const field of relationFields) {
2502
+ if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId) {
2503
+ continue;
2504
+ }
2425
2505
  const targetNormalized = (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? field.relation.entity.toLowerCase();
2426
2506
  const targetStore = this.stores.get(targetNormalized);
2427
2507
  if (!targetStore || targetStore.size === 0) continue;
@@ -3642,6 +3722,96 @@ function renameEntityInEffect(effect, rename, props) {
3642
3722
  }
3643
3723
  return effect;
3644
3724
  }
3725
+ var TRAIT_EMBED_PREFIX = "@trait.";
3726
+ function renameTraitEmbedsInValue(node, subs) {
3727
+ if (node === null || node === void 0) return node;
3728
+ if (typeof node === "string") {
3729
+ if (!node.startsWith(TRAIT_EMBED_PREFIX)) return node;
3730
+ const rest = node.slice(TRAIT_EMBED_PREFIX.length);
3731
+ const dot = rest.search(/[.[]/);
3732
+ const name = dot === -1 ? rest : rest.slice(0, dot);
3733
+ const suffix = dot === -1 ? "" : rest.slice(dot);
3734
+ const to = subs.get(name);
3735
+ return to === void 0 ? node : `${TRAIT_EMBED_PREFIX}${to}${suffix}`;
3736
+ }
3737
+ if (Array.isArray(node)) return node.map((item) => renameTraitEmbedsInValue(item, subs));
3738
+ if (typeof node !== "object") return node;
3739
+ const next = {};
3740
+ for (const [key, value] of Object.entries(node)) {
3741
+ next[key] = renameTraitEmbedsInValue(value, subs);
3742
+ }
3743
+ return next;
3744
+ }
3745
+ function renameTraitEmbeds(trait, subs) {
3746
+ if (subs.size === 0) return trait;
3747
+ const sm = trait.stateMachine;
3748
+ const next = { ...trait };
3749
+ if (sm) {
3750
+ next.stateMachine = {
3751
+ ...sm,
3752
+ transitions: (sm.transitions ?? []).map(
3753
+ (t) => t.effects ? { ...t, effects: renameTraitEmbedsInValue(t.effects, subs) } : t
3754
+ )
3755
+ };
3756
+ }
3757
+ if (trait.ticks) {
3758
+ next.ticks = trait.ticks.map(
3759
+ (tick) => tick.effects ? { ...tick, effects: renameTraitEmbedsInValue(tick.effects, subs) } : tick
3760
+ );
3761
+ }
3762
+ if (trait.config) {
3763
+ const nextConfig = {};
3764
+ for (const [key, field] of Object.entries(trait.config)) {
3765
+ nextConfig[key] = field.default === void 0 ? field : {
3766
+ ...field,
3767
+ default: renameTraitEmbedsInValue(
3768
+ field.default,
3769
+ subs
3770
+ )
3771
+ };
3772
+ }
3773
+ next.config = nextConfig;
3774
+ }
3775
+ return next;
3776
+ }
3777
+ function findTraitEntryInOrbital(orbital, localName) {
3778
+ for (const traitRef of orbital.traits ?? []) {
3779
+ if (typeof traitRef === "string") continue;
3780
+ if ("stateMachine" in traitRef) {
3781
+ if (traitRef.name === localName) return traitRef;
3782
+ continue;
3783
+ }
3784
+ if (!("ref" in traitRef)) continue;
3785
+ const refObj = traitRef;
3786
+ const declared = refObj.name ?? parseImportedTraitRef(refObj.ref)?.traitName;
3787
+ if (declared === localName) return traitRef;
3788
+ }
3789
+ return null;
3790
+ }
3791
+ function traitEmbedNamesOf(trait) {
3792
+ const found = /* @__PURE__ */ new Set();
3793
+ const walk = (node) => {
3794
+ if (node === null || node === void 0) return;
3795
+ if (typeof node === "string") {
3796
+ if (!node.startsWith(TRAIT_EMBED_PREFIX)) return;
3797
+ const rest = node.slice(TRAIT_EMBED_PREFIX.length);
3798
+ const dot = rest.search(/[.[]/);
3799
+ const name = dot === -1 ? rest : rest.slice(0, dot);
3800
+ if (name.length > 0) found.add(name);
3801
+ return;
3802
+ }
3803
+ if (Array.isArray(node)) {
3804
+ for (const item of node) walk(item);
3805
+ return;
3806
+ }
3807
+ if (typeof node !== "object") return;
3808
+ for (const value of Object.values(node)) walk(value);
3809
+ };
3810
+ for (const t of trait.stateMachine?.transitions ?? []) walk(t.effects);
3811
+ for (const tick of trait.ticks ?? []) walk(tick.effects);
3812
+ for (const field of Object.values(trait.config ?? {})) walk(field.default);
3813
+ return [...found];
3814
+ }
3645
3815
  function applyLinkedEntityRename(trait, linkedEntity) {
3646
3816
  const atomLinked = trait.linkedEntity;
3647
3817
  if (!linkedEntity || !atomLinked || linkedEntity === atomLinked) return trait;
@@ -3813,6 +3983,8 @@ var ReferenceResolver = class {
3813
3983
  localTraits;
3814
3984
  /** id-keyed mirror of `localTraits`, populated wherever the trait carries an `id`. */
3815
3985
  localTraitsById = /* @__PURE__ */ new Map();
3986
+ /** Import scope of each loaded source orbital, keyed by its source path. */
3987
+ sourceImportsCache = /* @__PURE__ */ new Map();
3816
3988
  loaderInitialized = false;
3817
3989
  constructor(options) {
3818
3990
  this.options = options;
@@ -3869,6 +4041,10 @@ var ReferenceResolver = class {
3869
4041
  if (!entityResult.success || !traitsResult.success || !pagesResult.success) {
3870
4042
  return { success: false, errors: ["Internal error: unexpected failure state"] };
3871
4043
  }
4044
+ const pullErrors = await this.pullSiblingTraits(traitsResult.data, imports, importChain);
4045
+ if (pullErrors.length > 0) {
4046
+ return { success: false, errors: pullErrors };
4047
+ }
3872
4048
  for (const resolvedTrait of traitsResult.data) {
3873
4049
  resolvedTrait.trait = resolveEntityTokensById(resolvedTrait.trait, imports.idIndex);
3874
4050
  resolvedTrait.trait = resolveConfigRefsById(resolvedTrait.trait, imports.idIndex);
@@ -4042,6 +4218,191 @@ var ReferenceResolver = class {
4042
4218
  }
4043
4219
  return { success: true, data: resolved, warnings: [] };
4044
4220
  }
4221
+ /**
4222
+ * The import scope of an already-loaded orbital, memoised per source path.
4223
+ * A sibling that is itself a ref names its target through the SOURCE atom's
4224
+ * aliases, so it can only be resolved against those.
4225
+ */
4226
+ async importsOfSource(imported, chain) {
4227
+ const key = imported.sourcePath ?? `${imported.alias}:${imported.from}`;
4228
+ const cached = this.sourceImportsCache.get(key);
4229
+ if (cached) return cached;
4230
+ const result = await this.resolveImports(
4231
+ imported.orbital.uses ?? [],
4232
+ imported.sourcePath,
4233
+ chain
4234
+ );
4235
+ if (!result.success) return null;
4236
+ result.data.idIndex = buildIdIndex(imported.orbital, result.data.orbitals);
4237
+ this.sourceImportsCache.set(key, result.data);
4238
+ return result.data;
4239
+ }
4240
+ /**
4241
+ * Sibling-trait auto-pull — the JS twin of the compiler's
4242
+ * `phases/inline/trait.rs` pass, appending to `resolved` in place.
4243
+ *
4244
+ * An atom's main trait embeds its own sub-views as `@trait.<Sibling>` string
4245
+ * literals (`std-browse`'s `bodyContent: {children: [@trait.DataGrid1]}`).
4246
+ * A consumer that imports only the main trait never instantiates those
4247
+ * siblings, so the token dangles: no state machine, no fetch, an empty list
4248
+ * where the grid should be. The compiled path materialises them; without this
4249
+ * pass the interpreted path did not, so a raw `.orb` handed straight to
4250
+ * `OrbitalServerRuntime` rendered a different app than the same `.orb` run
4251
+ * through `orbital resolve`.
4252
+ *
4253
+ * Pulls are keyed per EMBEDDER (`owner` = the top-level consumer trait that
4254
+ * started the chain), not per atom. Two rebinds of one atom in one orbital
4255
+ * (`ChannelRail -> Channel` and `ChatThread -> ChatMessage`, both std-browse)
4256
+ * carry different entity rebinds and different hosts to listen to, so one
4257
+ * shared copy can only ever serve one of them. The first owner keeps the
4258
+ * source name; later owners pull under `<Owner><Sibling>` — unique by
4259
+ * construction, since owner names are unique within the orbital.
4260
+ */
4261
+ async pullSiblingTraits(resolved, imports, chain) {
4262
+ const work = [];
4263
+ const ownerSubs = /* @__PURE__ */ new Map();
4264
+ const subsFor = (owner) => {
4265
+ let m = ownerSubs.get(owner);
4266
+ if (!m) {
4267
+ m = /* @__PURE__ */ new Map();
4268
+ ownerSubs.set(owner, m);
4269
+ }
4270
+ return m;
4271
+ };
4272
+ for (const rt of resolved) {
4273
+ if (rt.source.type !== "imported" || !rt.trait.name) continue;
4274
+ const owner = rt.trait.name;
4275
+ subsFor(owner).set(rt.source.traitName, owner);
4276
+ for (const sibling of traitEmbedNamesOf(rt.trait)) {
4277
+ work.push({ alias: rt.source.alias, sibling, linkedEntity: rt.linkedEntity, parent: owner, owner });
4278
+ }
4279
+ }
4280
+ if (work.length === 0) return [];
4281
+ const errors = [];
4282
+ const consumerDeclared = new Set(resolved.map((r) => r.trait.name).filter(Boolean));
4283
+ const seen = new Set(consumerDeclared);
4284
+ const visited = /* @__PURE__ */ new Set();
4285
+ const pulledAs = /* @__PURE__ */ new Map();
4286
+ const ownerOf = /* @__PURE__ */ new Map();
4287
+ const parentRewrites = /* @__PURE__ */ new Map();
4288
+ const pulled = [];
4289
+ const noteRewrite = (parent, from, to) => {
4290
+ let m = parentRewrites.get(parent);
4291
+ if (!m) {
4292
+ m = /* @__PURE__ */ new Map();
4293
+ parentRewrites.set(parent, m);
4294
+ }
4295
+ m.set(from, to);
4296
+ };
4297
+ for (let item = work.pop(); item !== void 0; item = work.pop()) {
4298
+ const { alias, sibling, linkedEntity, parent, owner } = item;
4299
+ const pullKey = `${alias}\0${sibling}\0${owner}`;
4300
+ const existing = pulledAs.get(pullKey);
4301
+ if (existing !== void 0) {
4302
+ if (existing !== sibling) noteRewrite(parent, sibling, existing);
4303
+ continue;
4304
+ }
4305
+ let finalName = sibling;
4306
+ if (seen.has(sibling)) {
4307
+ if (consumerDeclared.has(sibling)) continue;
4308
+ finalName = `${owner}${sibling}`;
4309
+ if (seen.has(finalName)) continue;
4310
+ }
4311
+ if (visited.has(pullKey)) continue;
4312
+ visited.add(pullKey);
4313
+ const imported = imports.orbitals.get(alias);
4314
+ if (!imported) continue;
4315
+ const atomEntry = findTraitEntryInOrbital(imported.orbital, sibling);
4316
+ if (!atomEntry) continue;
4317
+ let atomTrait;
4318
+ if ("stateMachine" in atomEntry) {
4319
+ const { trait: cfgResolved, errors: cfgErrors } = resolveConfigRefEmitNames(
4320
+ atomEntry
4321
+ );
4322
+ if (cfgErrors.length > 0) {
4323
+ errors.push(...cfgErrors);
4324
+ continue;
4325
+ }
4326
+ atomTrait = cfgResolved;
4327
+ } else {
4328
+ const srcImports = await this.importsOfSource(imported, chain);
4329
+ if (!srcImports) continue;
4330
+ const refObj = atomEntry;
4331
+ const nested = this.resolveTraitRefString(
4332
+ refObj.ref,
4333
+ srcImports,
4334
+ refObj.config,
4335
+ refObj.linkedEntity,
4336
+ refObj.name ?? sibling,
4337
+ refObj.events,
4338
+ refObj.listens,
4339
+ refObj.refId
4340
+ );
4341
+ if (!nested.success) {
4342
+ errors.push(...nested.errors);
4343
+ continue;
4344
+ }
4345
+ atomTrait = nested.data.trait;
4346
+ }
4347
+ let copy = applyLinkedEntityRename(atomTrait, linkedEntity);
4348
+ if (finalName !== sibling) {
4349
+ copy = { ...copy, name: finalName };
4350
+ noteRewrite(parent, sibling, finalName);
4351
+ }
4352
+ for (const next of traitEmbedNamesOf(copy)) {
4353
+ work.push({ alias, sibling: next, linkedEntity, parent: finalName, owner });
4354
+ }
4355
+ pulledAs.set(pullKey, finalName);
4356
+ subsFor(owner).set(sibling, finalName);
4357
+ ownerOf.set(finalName, owner);
4358
+ seen.add(finalName);
4359
+ pulled.push({
4360
+ trait: copy,
4361
+ source: { type: "imported", alias, traitName: sibling },
4362
+ ...linkedEntity !== void 0 ? { linkedEntity } : {}
4363
+ });
4364
+ }
4365
+ if (pulled.length === 0 && parentRewrites.size === 0) return errors;
4366
+ const applyRewrites = (rt) => {
4367
+ const subs = rt.trait.name ? parentRewrites.get(rt.trait.name) : void 0;
4368
+ if (subs) rt.trait = renameTraitEmbeds(rt.trait, subs);
4369
+ };
4370
+ for (const rt of resolved) applyRewrites(rt);
4371
+ for (const rt of pulled) applyRewrites(rt);
4372
+ const idByName = /* @__PURE__ */ new Map();
4373
+ for (const rt of [...resolved, ...pulled]) {
4374
+ if (rt.trait.name) idByName.set(rt.trait.name, rt.trait.id);
4375
+ }
4376
+ for (const rt of pulled) {
4377
+ const listens = rt.trait.listens;
4378
+ const owner = rt.trait.name ? ownerOf.get(rt.trait.name) : void 0;
4379
+ const subs = owner ? ownerSubs.get(owner) : void 0;
4380
+ if (!listens || listens.length === 0 || !subs) continue;
4381
+ let changed = false;
4382
+ const nextListens = listens.map((listen) => {
4383
+ const source = listen.source;
4384
+ if (!source || source.kind !== "trait") return listen;
4385
+ const target = subs.get(source.trait);
4386
+ if (target === void 0 || target === source.trait) return listen;
4387
+ changed = true;
4388
+ const traitId = idByName.get(target);
4389
+ return {
4390
+ ...listen,
4391
+ source: { kind: "trait", trait: target, ...traitId ? { traitId } : {} }
4392
+ };
4393
+ });
4394
+ if (changed) rt.trait = { ...rt.trait, listens: nextListens };
4395
+ }
4396
+ refResolverLog.info("sibling-pull", {
4397
+ pulled: pulled.map((p) => ({
4398
+ trait: p.trait.name,
4399
+ owner: p.trait.name ? ownerOf.get(p.trait.name) : void 0,
4400
+ linkedEntity: p.linkedEntity ?? p.trait.linkedEntity
4401
+ }))
4402
+ });
4403
+ resolved.push(...pulled);
4404
+ return errors;
4405
+ }
4045
4406
  /**
4046
4407
  * Resolve a single trait reference.
4047
4408
  */
@@ -4582,4 +4943,4 @@ var InMemoryPersistence = class {
4582
4943
  }
4583
4944
  };
4584
4945
 
4585
- 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 };
4946
+ export { CALLSITE_PAYLOAD_PREFIX, EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, deferEntityBindings, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes };
@@ -1,4 +1,4 @@
1
- import { E as EffectHandlers } from './types-p_L8-Sk4.js';
1
+ import { E as EffectHandlers } from './types-CL03tjGU.js';
2
2
  import { EventPayload } from '@almadar/core';
3
3
 
4
4
  /**
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { B as BindingContext, f as EvaluationContextExtensions, P as PatternProps, E as EffectHandlers, g as EffectContext, h as ExecutionEnvironment, i as EffectResult, T as TraitDefinition } from './types-p_L8-Sk4.js';
2
- export { j as BrowserFileMeta, k as BrowserFilePickerOptions, l as BrowserGeolocationOptions, m as BrowserGeolocationPosition, C as ConfigContext, n as Effect, a as EventListener, H as HANDLER_MANIFEST, I as IEventBus, b as RuntimeConfig, R as RuntimeEvent, d as TraitState, c as TransitionObserver, e as TransitionResult, U as Unsubscribe } from './types-p_L8-Sk4.js';
3
- import { U as UnifiedLoaderOptions, S as SchemaLoader, I as ImportChainLike, L as LoadResult, a as LoadedSchema, b as LoadedOrbital, P as PersistenceAdapter } from './OrbitalServerRuntime-f0EZSw4f.js';
4
- export { E as EntitySharingMap, c as EventBus, d as EventNamespaceMap, e as InMemoryPersistence, O as OrbitalEventRequest, f as OrbitalEventResponse, g as OrbitalServerRuntimeConfig, h as PreprocessOptions, i as PreprocessResult, j as PreprocessedSchema, k as ProcessEventOptions, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, o as StateMachineManager, p as collectDeclaredConfigDefaults, q as collectDeclaredEntityDefaults, r as createInitialTraitState, s as findInitialState, t as findTransition, u as getIsolatedCollectionName, v as getNamespacedEvent, w as isBrowser, x as isElectron, y as isNamespacedEvent, z as isNode, A as normalizeEventKey, B as parseNamespacedEvent, C as preprocessSchema, D as processEvent } from './OrbitalServerRuntime-f0EZSw4f.js';
1
+ import { g as RuntimePatternValue, B as BindingContext, f as EvaluationContextExtensions, P as PatternProps, E as EffectHandlers, h as EffectContext, i as ExecutionEnvironment, j as EffectResult, T as TraitDefinition } from './types-CL03tjGU.js';
2
+ export { k as BrowserFileMeta, l as BrowserFilePickerOptions, m as BrowserGeolocationOptions, n as BrowserGeolocationPosition, C as ConfigContext, o as Effect, a as EventListener, H as HANDLER_MANIFEST, I as IEventBus, b as RuntimeConfig, R as RuntimeEvent, d as TraitState, c as TransitionObserver, e as TransitionResult, U as Unsubscribe } from './types-CL03tjGU.js';
3
+ import { U as UnifiedLoaderOptions, S as SchemaLoader, I as ImportChainLike, L as LoadResult, a as LoadedSchema, b as LoadedOrbital, P as PersistenceAdapter } from './OrbitalServerRuntime-A2h0EikK.js';
4
+ export { E as EntitySharingMap, c as EventBus, d as EventNamespaceMap, e as InMemoryPersistence, O as OrbitalEventRequest, f as OrbitalEventResponse, g as OrbitalServerRuntimeConfig, h as PreprocessOptions, i as PreprocessResult, j as PreprocessedSchema, k as ProcessEventOptions, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, o as StateMachineManager, p as collectDeclaredConfigDefaults, q as collectDeclaredEntityDefaults, r as createInitialTraitState, s as findInitialState, t as findTransition, u as getIsolatedCollectionName, v as getNamespacedEvent, w as isBrowser, x as isElectron, y as isNamespacedEvent, z as isNode, A as normalizeEventKey, B as parseNamespacedEvent, C as preprocessSchema, D as processEvent } from './OrbitalServerRuntime-A2h0EikK.js';
5
5
  import { EvaluationContext, SExpressionEvaluator } from '@almadar/evaluator';
6
6
  export { EvaluationContext, createMinimalContext } from '@almadar/evaluator';
7
- import { TraitConfigObject, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, ServiceParams, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
7
+ import { RenderBindingMarker, SExpr, TraitConfigObject, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, ServiceParams, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
8
8
  export { EntityField, normalizeCallSiteConfigToValues } from '@almadar/core';
9
9
  export { ServerBridgeConfig, ServerBridgeState } from './ServerBridge.js';
10
10
  export { OsHandlerContext, OsHandlerResult } from './createOsHandlers.js';
@@ -284,6 +284,28 @@ declare function interpolateProps(props: PatternProps, ctx: EvaluationContext):
284
284
  * Interpolate a single value.
285
285
  */
286
286
  declare function interpolateValue(value: unknown, ctx: EvaluationContext): unknown;
287
+ /**
288
+ * Value a deferred render-ui prop tree can carry: interpolated runtime
289
+ * values, raw pass-through S-expressions (`fn` lambdas), render-time
290
+ * binding markers, and nested arrays/records of the same. No `unknown`
291
+ * escape hatch.
292
+ */
293
+ type DeferredPatternValue = RuntimePatternValue | RenderBindingMarker | SExpr | Date | readonly DeferredPatternValue[] | {
294
+ readonly [prop: string]: DeferredPatternValue;
295
+ };
296
+ /**
297
+ * Carry `@entity`-dependent prop leaves into slot content as
298
+ * `RenderBindingMarker`s instead of resolving them at flush time — the
299
+ * renderer (`@almadar/ui`'s SlotContentRenderer) evaluates each marker
300
+ * against the live entity store on every React render, the same model the
301
+ * compiled shell uses. Everything else resolves eagerly through the same
302
+ * `interpolateValue` path the non-deferring executor uses:
303
+ * - payload-referencing leaves stay eager (`@payload` is event-scoped and
304
+ * does not exist at render time);
305
+ * - `fn` render lambdas pass through raw (they are already render-time);
306
+ * - literal arrays (children lists) and plain objects recurse per item.
307
+ */
308
+ declare function deferEntityBindings(value: SExpr, ctx: EvaluationContext, configHops?: number): DeferredPatternValue;
287
309
  /**
288
310
  * Check if a value contains any binding references.
289
311
  */
@@ -328,6 +350,14 @@ interface EffectExecutorOptions {
328
350
  contextExtensions?: EvaluationContextExtensions;
329
351
  /** Evaluator for resolving nested S-expression values in `set` (e.g., substrate operators) */
330
352
  evaluator?: SExpressionEvaluator;
353
+ /**
354
+ * When true, render-ui args keep `@entity`-dependent prop leaves as
355
+ * `RenderBindingMarker`s for render-time evaluation instead of resolving
356
+ * them eagerly at flush time. Set by the client-side interpreter
357
+ * (`useTraitStateMachine`); server-side executors resolve eagerly so
358
+ * SSE-pushed props stay concrete.
359
+ */
360
+ deferRenderBindings?: boolean;
331
361
  }
332
362
  /**
333
363
  * EffectExecutor - Routes effects to handlers.
@@ -363,6 +393,7 @@ declare class EffectExecutor {
363
393
  private strictBindings;
364
394
  private contextExtensions?;
365
395
  private evaluator?;
396
+ private deferRenderBindings;
366
397
  constructor(options: EffectExecutorOptions);
367
398
  private _evaluator?;
368
399
  private getEvaluator;
@@ -585,6 +616,18 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
585
616
  private storeNameById;
586
617
  private config;
587
618
  constructor(config?: MockPersistenceConfig);
619
+ /**
620
+ * Add owner columns discovered after construction.
621
+ *
622
+ * The adapter is built before any schema is registered, so schema-DERIVED
623
+ * owner columns (relation fields pointing at the `[identity]` entity) can
624
+ * only arrive later.
625
+ *
626
+ * ⚠️ Seeding is EAGER — `registerEntity()` seeds immediately — so callers must
627
+ * supply these columns BEFORE registering any orbital. Calling this afterwards
628
+ * silently stamps nothing.
629
+ */
630
+ addOwnerFields(fields: readonly string[]): void;
588
631
  /** Re-anchor the PRNG to the configured seed. Called before every
589
632
  * re-seed loop so identical reseed sequences produce identical rows
590
633
  * (timestamps + generated fields). Without this, the first
@@ -1033,4 +1076,4 @@ declare namespace index {
1033
1076
  export { type index_ComposeBehaviorsInput as ComposeBehaviorsInput, type index_ComposeBehaviorsResult as ComposeBehaviorsResult, type index_EventWiringEntry as EventWiringEntry, type index_LayoutStrategy as LayoutStrategy, type index_PipeStep as PipeStep, index_applyEventWiring as applyEventWiring, index_composeBehaviors as composeBehaviors, index_detectLayoutStrategy as detectLayoutStrategy, index_pipeBehaviors as pipeBehaviors };
1034
1077
  }
1035
1078
 
1036
- export { BindingContext, CALLSITE_PAYLOAD_PREFIX, type ClientEventBus, type ComposeBehaviorsInput, type ComposeBehaviorsResult, type CreateClientEffectHandlersOptions, type CreateServerEffectHandlersOptions, type CronFields, EffectContext, EffectExecutor, type EffectExecutorOptions, EffectHandlers, EffectResult, type EntitySchema, type EventWiringEntry, ExecutionEnvironment, ImportChainLike, type LayoutStrategy, LoadResult, LoadedOrbital, LoadedSchema, MockPersistenceAdapter, type MockPersistenceConfig, type PayloadMismatch, type PayloadValidationFailure, PersistenceAdapter, type PipeStep, SchemaLoader, type ServerEffectResult, type SlotSetter, type TickHandle, TickScheduler, TraitDefinition, UnifiedLoaderOptions, applyEventWiring, buildEmitsFromTraits, composeBehaviors, index as composition, containsBindings, createClientEffectHandlers, createContextFromBindings, createMockPersistence, createServerEffectHandlers, createTestExecutor, createTickScheduler, createUnifiedLoader, cronMatches, cronMinuteKey, detectLayoutStrategy, extractBindings, formatPayloadValidationError, interpolateProps, interpolateValue, isValidCronExpression, isValidDurationString, parseCron, parseCronField, parseDurationString, pipeBehaviors, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes };
1079
+ export { BindingContext, CALLSITE_PAYLOAD_PREFIX, type ClientEventBus, type ComposeBehaviorsInput, type ComposeBehaviorsResult, type CreateClientEffectHandlersOptions, type CreateServerEffectHandlersOptions, type CronFields, type DeferredPatternValue, EffectContext, EffectExecutor, type EffectExecutorOptions, EffectHandlers, EffectResult, type EntitySchema, type EventWiringEntry, ExecutionEnvironment, ImportChainLike, type LayoutStrategy, LoadResult, LoadedOrbital, LoadedSchema, MockPersistenceAdapter, type MockPersistenceConfig, type PayloadMismatch, type PayloadValidationFailure, PersistenceAdapter, type PipeStep, SchemaLoader, type ServerEffectResult, type SlotSetter, type TickHandle, TickScheduler, TraitDefinition, UnifiedLoaderOptions, applyEventWiring, buildEmitsFromTraits, composeBehaviors, index as composition, containsBindings, createClientEffectHandlers, createContextFromBindings, createMockPersistence, createServerEffectHandlers, createTestExecutor, createTickScheduler, createUnifiedLoader, cronMatches, cronMinuteKey, deferEntityBindings, detectLayoutStrategy, extractBindings, formatPayloadValidationError, interpolateProps, interpolateValue, isValidCronExpression, isValidDurationString, parseCron, parseCronField, parseDurationString, pipeBehaviors, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { EffectExecutor, createContextFromBindings } from './chunk-2SM626PK.js';
2
- export { CALLSITE_PAYLOAD_PREFIX, EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMinimalContext, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeCallSiteConfigToValues, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes } from './chunk-2SM626PK.js';
1
+ import { EffectExecutor, createContextFromBindings } from './chunk-FIBEYLME.js';
2
+ export { CALLSITE_PAYLOAD_PREFIX, EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMinimalContext, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, deferEntityBindings, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeCallSiteConfigToValues, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes } from './chunk-FIBEYLME.js';
3
3
  export { cronMatches, cronMinuteKey, isValidCronExpression, parseCron, parseCronField } from './chunk-OU3ITB5S.js';
4
4
  import './chunk-T4VDAB4C.js';
5
5
  export { PERF_NAMESPACE, RendererContractViolationError, adjustSchemaForMockData, aggregateSlotContent, assertIsMultiSourceSlotManager, assertIsSlotManager, bindEventBus, bindTraitStateGetter, buildMockData, buildOrbitalsByTrait, clearPerf, createSlotSetter, ensureVerificationApi, getOrbitalVerification, perfEnd, perfStart, perfStore, perfTime, prepareSchemaForPreview, pushPerfEntry, validateSlotContent, wrapCallbackForEvent } from './chunk-FOFLEZRJ.js';
@@ -3,6 +3,25 @@ import { TraitId, EventId, SExpr, TraitEventListener, AgentContext, LlmContext,
3
3
 
4
4
  /** Alias for ResolvedPatternProps to avoid breaking internal consumers */
5
5
  type PatternProps = ResolvedPatternProps;
6
+ /**
7
+ * Value a render-ui pattern prop can carry AFTER interpolation. Mirrors
8
+ * `FieldValue` but extends with `RuntimeRenderPattern` so nested pattern
9
+ * configs (children, slot props that are themselves render-ui nodes)
10
+ * round-trip without an `unknown` escape hatch.
11
+ */
12
+ type RuntimePatternValue = string | number | boolean | null | undefined | EntityRow | EntityRow[] | readonly RuntimePatternValue[] | RuntimeRenderPattern;
13
+ /**
14
+ * Shape of a render-ui pattern AFTER interpolation, as the runtime hands
15
+ * it to `EffectHandlers.renderUI`. Different from `ResolvedPatternProps`
16
+ * (`@almadar/core`) because the post-interpolation pattern carries
17
+ * resolved EntityRow values (e.g. `{ entity: <row> }` from `@payload.row`)
18
+ * not just primitives. Used by render-ui observability helpers to narrow
19
+ * safely without `unknown` casts.
20
+ */
21
+ interface RuntimeRenderPattern {
22
+ readonly [prop: string]: RuntimePatternValue;
23
+ }
24
+
6
25
  type ConfigContext = TraitConfig;
7
26
  /**
8
27
  * Event structure for cross-trait communication.
@@ -432,7 +451,7 @@ interface TransitionObserver {
432
451
  guardResult?: boolean;
433
452
  effects: Array<{
434
453
  type: string;
435
- args: unknown[];
454
+ args: SExpr[];
436
455
  status: "executed" | "failed" | "skipped";
437
456
  error?: string;
438
457
  durationMs?: number;
@@ -444,4 +463,4 @@ interface TransitionObserver {
444
463
  */
445
464
  declare const HANDLER_MANIFEST: Record<ExecutionEnvironment, string[]>;
446
465
 
447
- export { type BindingContext as B, type ConfigContext as C, type EffectHandlers as E, HANDLER_MANIFEST as H, type IEventBus as I, type PatternProps as P, type RuntimeEvent as R, type TraitDefinition as T, type Unsubscribe as U, type EventListener as a, type RuntimeConfig as b, type TransitionObserver as c, type TraitState as d, type TransitionResult as e, type EvaluationContextExtensions as f, type EffectContext as g, type ExecutionEnvironment as h, type EffectResult as i, type BrowserFileMeta as j, type BrowserFilePickerOptions as k, type BrowserGeolocationOptions as l, type BrowserGeolocationPosition as m, type Effect as n };
466
+ export { type BindingContext as B, type ConfigContext as C, type EffectHandlers as E, HANDLER_MANIFEST as H, type IEventBus as I, type PatternProps as P, type RuntimeEvent as R, type TraitDefinition as T, type Unsubscribe as U, type EventListener as a, type RuntimeConfig as b, type TransitionObserver as c, type TraitState as d, type TransitionResult as e, type EvaluationContextExtensions as f, type RuntimePatternValue as g, type EffectContext as h, type ExecutionEnvironment as i, type EffectResult as j, type BrowserFileMeta as k, type BrowserFilePickerOptions as l, type BrowserGeolocationOptions as m, type BrowserGeolocationPosition as n, type Effect as o };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "6.43.0",
3
+ "version": "6.45.0",
4
4
  "description": "Interpreted runtime for Almadar orbital applications (OrbitalServerRuntime)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,11 +52,11 @@
52
52
  "access": "public"
53
53
  },
54
54
  "dependencies": {
55
- "@almadar/core": "^10.40.0",
56
- "@almadar/evaluator": "^2.37.0",
55
+ "@almadar/core": "^10.43.0",
56
+ "@almadar/evaluator": "^2.38.0",
57
57
  "@almadar/logger": "^1.10.0",
58
58
  "@almadar/server": "^2.29.0",
59
- "@almadar/std": "^16.151.0"
59
+ "@almadar/std": "^16.153.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "express": "^5.0.0"