@almadar/runtime 6.62.0 → 6.64.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.
@@ -767,6 +767,11 @@ declare function preprocessSchema(schema: OrbitalSchema, options: PreprocessOpti
767
767
  *
768
768
  * - `persistent` entities share the same collection
769
769
  * - `runtime` entities get isolated collections per orbital
770
+ *
771
+ * NOTE: the runtime's own store keying does NOT go through this helper —
772
+ * `MockPersistenceAdapter` resolves entity → store via its declared
773
+ * `collection` directly (collection-keyed stores, 2026-08-29). This stays
774
+ * exported for external consumers computing display/derived names only.
770
775
  */
771
776
  declare function getIsolatedCollectionName(orbitalName: string, entitySharing: EntitySharingMap): string;
772
777
  /**
@@ -1411,6 +1416,29 @@ declare class OrbitalServerRuntime {
1411
1416
  * instead of re-seeding.
1412
1417
  */
1413
1418
  setDefaultUser(user: UserContext | undefined): void;
1419
+ /** setDefaultUser can land before register() finishes resolving the schema
1420
+ * (a dev host pins a persona right after boot). An ungated restamp there
1421
+ * would hand a read-only persona authorship of drafts, so the restamp is
1422
+ * DEFERRED until the schema exists — register()/registerSync() flush it. */
1423
+ private pendingOwnerRestamp;
1424
+ /**
1425
+ * Re-point the mock seeder's owner stamps at `user`. The adapter's
1426
+ * installed owner gate (see installOwnerGate) decides per row whether the
1427
+ * persona could have CREATED it — a reader keeps seeing the store's
1428
+ * published face instead of inheriting draft ownership from the demo
1429
+ * stamper. Deferred when the schema (and so the gate) isn't resolved yet.
1430
+ */
1431
+ private restampOwnerGated;
1432
+ /**
1433
+ * Install the schema's `@create` directives as the mock seeder's owner
1434
+ * gate — the single authority on who may author a row. Consulted by BOTH
1435
+ * `restampOwner` (persona switches) and `seed()` (a client connect
1436
+ * re-registers, and the deterministic reseed re-stamps with the current
1437
+ * ownerId — gating only the switch path would leak right there). Reads the
1438
+ * CURRENT default user at evaluation time so one installation covers every
1439
+ * later switch. Then flushes any switch that arrived before the schema.
1440
+ */
1441
+ private installOwnerGate;
1414
1442
  /** The viewer a dev host is currently presenting the app as. */
1415
1443
  getDefaultUser(): UserContext | undefined;
1416
1444
  /**
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- export { F as ClientEffectTuple, G as ClientNavigateBackTuple, H as ClientNavigateTuple, J as ClientNotifyTuple, K as ClientRenderUITuple, M as EffectResult, e as InMemoryPersistence, N as LiveBroadcastItem, Q as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, T as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, V as RuntimeTraitTick, p as collectDeclaredConfigDefaults, W as createOrbitalServerRuntime } from './OrbitalServerRuntime-CZHHxo_A.js';
2
+ export { F as ClientEffectTuple, G as ClientNavigateBackTuple, H as ClientNavigateTuple, J as ClientNotifyTuple, K as ClientRenderUITuple, M as EffectResult, e as InMemoryPersistence, N as LiveBroadcastItem, Q as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, T as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, V as RuntimeTraitTick, p as collectDeclaredConfigDefaults, W as createOrbitalServerRuntime } from './OrbitalServerRuntime-DOJDoqXu.js';
3
3
  import './types-BaD_ox7e.js';
4
4
  import '@almadar/core';
@@ -1,7 +1,7 @@
1
- import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-MEBKFZC4.js';
2
- export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-MEBKFZC4.js';
1
+ import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-IFSFX43J.js';
2
+ export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-IFSFX43J.js';
3
3
  import { isValidCronExpression } from './chunk-OU3ITB5S.js';
4
- import { createContextFromBindings, resolveCallSitePayloadCaptures, applyRowAccess, checkMutationAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
4
+ import { createContextFromBindings, checkMutationAccess, resolveCallSitePayloadCaptures, applyRowAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
5
5
  import './chunk-T4VDAB4C.js';
6
6
  import './chunk-SCRAHWOC.js';
7
7
  import './chunk-MLKGABMK.js';
@@ -9,7 +9,7 @@ import { createLogger } from '@almadar/logger';
9
9
  import * as nodeModule from 'module';
10
10
  import { evaluateListenPayloadExpr, evaluateGuard, evaluate } from '@almadar/evaluator';
11
11
  import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, personaFromIdentityRow, normalizeUserContext, isRuntimeEntity, isPageReference } from '@almadar/core';
12
- import { ownerFieldsFromSchema, identityEntityName, entityAccessPolicies } from '@almadar/core/mock';
12
+ import { ownerFieldsFromSchema, identityEntityName, entityAccessPoliciesByStoreKey, entityAccessPolicies } from '@almadar/core/mock';
13
13
  import { getPatternFieldsContract } from '@almadar/core/patterns';
14
14
 
15
15
  // src/identity/routing.ts
@@ -355,6 +355,7 @@ var OrbitalServerRuntime = class {
355
355
  this.setupTicks();
356
356
  this.resolvedSchema = schema;
357
357
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
358
+ this.installOwnerGate();
358
359
  }
359
360
  /**
360
361
  * Teach the mock seeder which columns hold the viewer's id, derived from the
@@ -391,7 +392,7 @@ var OrbitalServerRuntime = class {
391
392
  this.setupTicks();
392
393
  this.resolvedSchema = schema;
393
394
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
394
- this.applyIdentityOwnerFields(schema);
395
+ this.installOwnerGate();
395
396
  }
396
397
  /**
397
398
  * Returns the schema that this runtime is currently executing, post-
@@ -707,6 +708,7 @@ var OrbitalServerRuntime = class {
707
708
  this.persistence.registerEntity({
708
709
  name: entity.name,
709
710
  id: entity.id,
711
+ collection: entity.collection,
710
712
  fields,
711
713
  persistence: entity.persistence
712
714
  });
@@ -727,6 +729,7 @@ var OrbitalServerRuntime = class {
727
729
  this.persistence.registerEntity({
728
730
  name: auxEntity.name,
729
731
  id: auxEntity.id,
732
+ collection: auxEntity.collection,
730
733
  fields: auxFields,
731
734
  persistence: auxEntity.persistence
732
735
  });
@@ -1015,10 +1018,25 @@ var OrbitalServerRuntime = class {
1015
1018
  this.persistence.registerEntity({
1016
1019
  name: entity.name,
1017
1020
  id: entity.id,
1021
+ collection: entity.collection,
1018
1022
  fields,
1019
1023
  persistence: entity.persistence
1020
1024
  });
1021
1025
  }
1026
+ for (const auxRef of registered.schema.auxiliaryEntities ?? []) {
1027
+ if (typeof auxRef === "string" || isEntityCall(auxRef)) continue;
1028
+ if (!auxRef.name || !auxRef.fields) continue;
1029
+ const auxFields = auxRef.fields.filter(
1030
+ (f) => typeof f.name === "string" && f.name.length > 0
1031
+ );
1032
+ this.persistence.registerEntity({
1033
+ name: auxRef.name,
1034
+ id: auxRef.id,
1035
+ collection: auxRef.collection,
1036
+ fields: auxFields,
1037
+ persistence: auxRef.persistence
1038
+ });
1039
+ }
1022
1040
  }
1023
1041
  }
1024
1042
  /**
@@ -1048,7 +1066,55 @@ var OrbitalServerRuntime = class {
1048
1066
  const previousId = this.config.defaultUser?.id;
1049
1067
  this.config.defaultUser = user;
1050
1068
  if (this.persistence instanceof MockPersistenceAdapter && user?.id !== void 0 && user.id !== previousId) {
1051
- this.persistence.restampOwner(user.id);
1069
+ this.restampOwnerGated(user);
1070
+ }
1071
+ }
1072
+ /** setDefaultUser can land before register() finishes resolving the schema
1073
+ * (a dev host pins a persona right after boot). An ungated restamp there
1074
+ * would hand a read-only persona authorship of drafts, so the restamp is
1075
+ * DEFERRED until the schema exists — register()/registerSync() flush it. */
1076
+ pendingOwnerRestamp = false;
1077
+ /**
1078
+ * Re-point the mock seeder's owner stamps at `user`. The adapter's
1079
+ * installed owner gate (see installOwnerGate) decides per row whether the
1080
+ * persona could have CREATED it — a reader keeps seeing the store's
1081
+ * published face instead of inheriting draft ownership from the demo
1082
+ * stamper. Deferred when the schema (and so the gate) isn't resolved yet.
1083
+ */
1084
+ restampOwnerGated(user) {
1085
+ if (!(this.persistence instanceof MockPersistenceAdapter)) return;
1086
+ if (!this.resolvedSchema) {
1087
+ this.pendingOwnerRestamp = true;
1088
+ return;
1089
+ }
1090
+ this.persistence.restampOwner(user.id);
1091
+ }
1092
+ /**
1093
+ * Install the schema's `@create` directives as the mock seeder's owner
1094
+ * gate — the single authority on who may author a row. Consulted by BOTH
1095
+ * `restampOwner` (persona switches) and `seed()` (a client connect
1096
+ * re-registers, and the deterministic reseed re-stamps with the current
1097
+ * ownerId — gating only the switch path would leak right there). Reads the
1098
+ * CURRENT default user at evaluation time so one installation covers every
1099
+ * later switch. Then flushes any switch that arrived before the schema.
1100
+ */
1101
+ installOwnerGate() {
1102
+ if (!(this.persistence instanceof MockPersistenceAdapter) || !this.resolvedSchema) return;
1103
+ const policiesByStore = entityAccessPoliciesByStoreKey(this.resolvedSchema);
1104
+ this.persistence.setOwnerGate((storeKey, candidateRow) => {
1105
+ const user = this.config.defaultUser;
1106
+ if (!user) return true;
1107
+ return checkMutationAccess(candidateRow, policiesByStore.get(storeKey)?.create, { user });
1108
+ });
1109
+ this.persistence.setOwnerCandidateGate((storeKey, candidateRow, candidateIdentityRow) => {
1110
+ const persona = personaFromIdentityRow(candidateIdentityRow);
1111
+ if (!persona) return false;
1112
+ return checkMutationAccess(candidateRow, policiesByStore.get(storeKey)?.create, { user: persona });
1113
+ });
1114
+ if (this.pendingOwnerRestamp) {
1115
+ this.pendingOwnerRestamp = false;
1116
+ const user = this.config.defaultUser;
1117
+ if (user?.id !== void 0) this.restampOwnerGated(user);
1052
1118
  }
1053
1119
  }
1054
1120
  /** The viewer a dev host is currently presenting the app as. */
@@ -355,7 +355,7 @@ function processEvent(options) {
355
355
  let lastFailedGuardTransition;
356
356
  for (const transition of candidates) {
357
357
  smLog.debug("processEvent", { trait: trait.name, event: normalizedEvent, currentState: traitState.currentState, to: transition.to });
358
- if (!transition.guard) {
358
+ if (transition.guard === void 0 || transition.guard === null) {
359
359
  return {
360
360
  executed: true,
361
361
  newState: transition.to,
@@ -2128,12 +2128,24 @@ var mockLog = createLogger("almadar:runtime:mock");
2128
2128
  var DEFAULT_MOCK_SEED = 42;
2129
2129
  var SEED_REFERENCE_TIMESTAMP = "2024-01-01T00:00:00.000Z";
2130
2130
  var MS_PER_DAY = 864e5;
2131
+ function isRelationField(field) {
2132
+ return field.type === "relation";
2133
+ }
2134
+ function stampedCellKey(entity, id, column) {
2135
+ return `${entity}\0${id}\0${column}`;
2136
+ }
2131
2137
  var MockPersistenceAdapter = class _MockPersistenceAdapter {
2132
2138
  stores = /* @__PURE__ */ new Map();
2133
2139
  schemas = /* @__PURE__ */ new Map();
2134
2140
  idCounters = /* @__PURE__ */ new Map();
2135
- /** entityId -> normalized store name, so relation lookups can prefer the id sibling over `relation.entity` name-matching. */
2141
+ /** entityId -> store key, so relation lookups can prefer the id sibling over `relation.entity` name-matching. */
2136
2142
  storeNameById = /* @__PURE__ */ new Map();
2143
+ /** normalized entity name -> store key (the declared collection, else the name).
2144
+ * Entities sharing a `persistent:` collection resolve to the same store. */
2145
+ storeKeyByEntity = /* @__PURE__ */ new Map();
2146
+ /** store key -> the first registrant's entity name, used as the minted-id label
2147
+ * so rows in a shared collection carry one consistent id family. */
2148
+ idLabelByStoreKey = /* @__PURE__ */ new Map();
2137
2149
  config;
2138
2150
  /**
2139
2151
  * Every (entity, row id, column) cell `seed()` stamped with `config.ownerId`.
@@ -2185,21 +2197,108 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2185
2197
  * No-op when there is no new id, or it matches the current one — an
2186
2198
  * anonymous switch (`newOwnerId === undefined`) leaves existing stamps as
2187
2199
  * they are rather than clearing a non-nullable owner column.
2200
+ *
2201
+ * Both this re-labeling AND `seed()`'s original stamping consult the
2202
+ * `ownerGate` hook (see `setOwnerGate`) — a re-register reseeds identical
2203
+ * rows, so gating only the restamp path leaks the moment a client
2204
+ * connects and the deterministic reseed stamps with the updated ownerId.
2188
2205
  */
2189
2206
  restampOwner(newOwnerId) {
2190
2207
  const oldOwnerId = this.config.ownerId;
2191
2208
  this.config.ownerId = newOwnerId;
2192
2209
  if (!newOwnerId || newOwnerId === oldOwnerId) return;
2210
+ let skipped = 0;
2211
+ let fallbackStamps = 0;
2193
2212
  for (const cell of this.ownerStampedCells) {
2194
2213
  const row = this.stores.get(cell.entity)?.get(cell.id);
2195
- if (row) row[cell.column] = newOwnerId;
2214
+ if (!row) continue;
2215
+ if (this.ownerGate && !this.ownerGate(cell.entity, { ...row, [cell.column]: newOwnerId })) {
2216
+ const fallbackId = this.firstEligibleOwner(cell.entity, cell.column, row);
2217
+ if (fallbackId === void 0) {
2218
+ skipped++;
2219
+ continue;
2220
+ }
2221
+ row[cell.column] = fallbackId;
2222
+ fallbackStamps++;
2223
+ continue;
2224
+ }
2225
+ row[cell.column] = newOwnerId;
2196
2226
  }
2197
2227
  mockLog.debug("mock:owner-restamped", {
2198
2228
  from: oldOwnerId,
2199
2229
  to: newOwnerId,
2200
- cells: this.ownerStampedCells.length
2230
+ cells: this.ownerStampedCells.length,
2231
+ skipped,
2232
+ fallbackStamps
2201
2233
  });
2202
2234
  }
2235
+ /**
2236
+ * The host's answer to "may the current viewer OWN this row?" — evaluated
2237
+ * against the candidate row with the owner column already re-pointed,
2238
+ * `storeKey` being the collection key the stores are keyed by. The runtime
2239
+ * installs the registered schema's `@create` directive here (the single
2240
+ * authority on who authors; no role heuristics), so a read-only persona
2241
+ * never inherits authorship of drafts from the demo stamper — neither on a
2242
+ * persona switch (`restampOwner`) nor on a deterministic reseed (`seed`).
2243
+ * Absent hook = stamp unconditionally (pre-role-model behavior).
2244
+ */
2245
+ ownerGate;
2246
+ setOwnerGate(gate) {
2247
+ this.ownerGate = gate;
2248
+ }
2249
+ /**
2250
+ * The host's answer to "may THIS identity row own this candidate row?" —
2251
+ * evaluated per candidate rather than against the fixed default viewer, so
2252
+ * `linkRelationFields` and the eligible-owner fallback (see
2253
+ * `firstEligibleOwner`) can pick a policy-valid owner from the whole
2254
+ * identity roster instead of only ever checking the current default user.
2255
+ * Absent hook = no candidate filtering (pre-role-model behavior).
2256
+ */
2257
+ ownerCandidateGate;
2258
+ setOwnerCandidateGate(gate) {
2259
+ this.ownerCandidateGate = gate;
2260
+ }
2261
+ /**
2262
+ * The store an owner column's relation field targets, resolved the same
2263
+ * way `linkRelationFields` resolves any relation target (id sibling wins
2264
+ * over the `entity` name string) — but keyed by STORE, mirroring that
2265
+ * method's first-declarer-per-collection rule, so `seed()` and
2266
+ * `restampOwner()` can find it before a `linkRelationFields` pass has run
2267
+ * this registration. Undefined when no registered schema on `storeKey`
2268
+ * declares `column` as a relation field.
2269
+ */
2270
+ resolveOwnerColumnTargetStore(storeKey, column) {
2271
+ for (const [normalizedName, schema] of this.schemas) {
2272
+ if ((this.storeKeyByEntity.get(normalizedName) ?? normalizedName) !== storeKey) continue;
2273
+ const field = schema.fields.find((f) => f.name === column);
2274
+ if (field && isRelationField(field)) {
2275
+ return (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? this.resolveStoreKey(field.relation.entity);
2276
+ }
2277
+ }
2278
+ return void 0;
2279
+ }
2280
+ /**
2281
+ * The first identity-store row (seed order) whose persona is eligible, per
2282
+ * the installed `ownerCandidateGate`, to own `row`'s `column` in `storeKey`
2283
+ * — the deterministic replacement for "skip the stamp" when the CURRENT
2284
+ * viewer fails the `@create` policy (e.g. a reader-default persona against
2285
+ * an author|reviewer-only policy). Undefined when no gate is installed, the
2286
+ * column isn't a resolvable relation, or no identity row is eligible — the
2287
+ * caller keeps today's skip behavior in that case.
2288
+ */
2289
+ firstEligibleOwner(storeKey, column, row) {
2290
+ if (!this.ownerCandidateGate) return void 0;
2291
+ const identityStoreKey = this.resolveOwnerColumnTargetStore(storeKey, column);
2292
+ const identityStore = identityStoreKey ? this.stores.get(identityStoreKey) : void 0;
2293
+ if (!identityStore) return void 0;
2294
+ for (const identityRow of identityStore.values()) {
2295
+ const candidateId = identityRow["id"];
2296
+ if (this.ownerCandidateGate(storeKey, { ...row, [column]: candidateId }, identityRow)) {
2297
+ return candidateId;
2298
+ }
2299
+ }
2300
+ return void 0;
2301
+ }
2203
2302
  /** Re-anchor the PRNG to the configured seed. Called before every
2204
2303
  * re-seed loop so identical reseed sequences produce identical rows
2205
2304
  * (timestamps + generated fields). Without this, the first
@@ -2213,19 +2312,27 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2213
2312
  // ============================================================================
2214
2313
  // Store Management
2215
2314
  // ============================================================================
2216
- getStore(entityName) {
2315
+ /** Resolve an entity name to its store key: the declared collection when the
2316
+ * entity is registered, the lowercased name otherwise (unregistered ad-hoc
2317
+ * creates keep working). */
2318
+ resolveStoreKey(entityName) {
2217
2319
  const normalized = entityName.toLowerCase();
2218
- if (!this.stores.has(normalized)) {
2219
- this.stores.set(normalized, /* @__PURE__ */ new Map());
2220
- this.idCounters.set(normalized, 0);
2320
+ return this.storeKeyByEntity.get(normalized) ?? normalized;
2321
+ }
2322
+ getStore(entityName) {
2323
+ const key = this.resolveStoreKey(entityName);
2324
+ if (!this.stores.has(key)) {
2325
+ this.stores.set(key, /* @__PURE__ */ new Map());
2326
+ this.idCounters.set(key, 0);
2221
2327
  }
2222
- return this.stores.get(normalized);
2328
+ return this.stores.get(key);
2223
2329
  }
2224
2330
  nextId(entityName) {
2225
- const normalized = entityName.toLowerCase();
2226
- const counter = (this.idCounters.get(normalized) ?? 0) + 1;
2227
- this.idCounters.set(normalized, counter);
2228
- return `${this.capitalizeFirst(entityName)} Id ${counter}`;
2331
+ const key = this.resolveStoreKey(entityName);
2332
+ const counter = (this.idCounters.get(key) ?? 0) + 1;
2333
+ this.idCounters.set(key, counter);
2334
+ const label = this.idLabelByStoreKey.get(key) ?? entityName;
2335
+ return `${this.capitalizeFirst(label)} Id ${counter}`;
2229
2336
  }
2230
2337
  // ============================================================================
2231
2338
  // Schema & Seeding
@@ -2237,12 +2344,20 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2237
2344
  */
2238
2345
  registerEntity(schema, seedCount) {
2239
2346
  const normalized = schema.name.toLowerCase();
2347
+ const storeKey = (schema.collection ?? schema.name).toLowerCase();
2348
+ this.storeKeyByEntity.set(normalized, storeKey);
2349
+ if (!this.idLabelByStoreKey.has(storeKey)) {
2350
+ this.idLabelByStoreKey.set(storeKey, schema.name);
2351
+ }
2240
2352
  this.schemas.set(normalized, schema);
2241
2353
  if (schema.id) {
2242
- this.storeNameById.set(schema.id, normalized);
2354
+ this.storeNameById.set(schema.id, storeKey);
2243
2355
  }
2356
+ const alreadySeeded = (this.stores.get(storeKey)?.size ?? 0) > 0;
2244
2357
  if (schema.seedData && schema.seedData.length > 0) {
2245
2358
  this.seedFromInstances(schema.name, schema.seedData);
2359
+ } else if (alreadySeeded) {
2360
+ this.backfillFields(storeKey, schema);
2246
2361
  } else {
2247
2362
  const requested = seedCount ?? this.config.defaultSeedCount ?? 6;
2248
2363
  const count = sampleRowCount(
@@ -2261,36 +2376,81 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2261
2376
  * of nested-tree atoms (e.g. std-thread-comments-linear with ThreadPost.
2262
2377
  * replies → [ThreadPost]) render empty reply cards.
2263
2378
  *
2264
- * For self-referential relations, each row gets 2–4 sibling IDs (excluding
2265
- * self). For cross-entity relations, IDs are picked from the target store.
2266
- * The runtime caps recursion at depth=2 in `populateRelations`, so
2267
- * grandparent-of-self cycles render two levels deep then stop.
2379
+ * Cross-entity relations pick random IDs from the target store. A
2380
+ * SELF-referential `one`-cardinality relation (a parent column like
2381
+ * `Tag.parentId : Tag`) is linked deterministically instead: row 0 stays a
2382
+ * root (`""`) and row *i* parents to row ⌊(i−1)/2⌋ — a proper forest with
2383
+ * real roots and no cycles, so tree views and `parentId = ""` root fetches
2384
+ * render sensibly. Self-referential `many` relations keep the 2–4 random
2385
+ * sibling IDs (excluding self). The runtime caps recursion at depth=2 in
2386
+ * `populateRelations`, so deep chains render two levels then stop.
2387
+ *
2388
+ * Entities sharing a collection are linked once per STORE, over the union
2389
+ * of their declared relation fields (first declarer of a field name wins).
2268
2390
  */
2269
2391
  linkRelationFields() {
2392
+ const relationFieldsByStore = /* @__PURE__ */ new Map();
2270
2393
  for (const [normalizedName, schema] of this.schemas) {
2271
- const store = this.stores.get(normalizedName);
2272
- if (!store) continue;
2273
- const relationFields = schema.fields.filter(
2274
- (f) => f.type === "relation"
2275
- );
2276
- if (relationFields.length === 0) continue;
2277
- for (const row of store.values()) {
2278
- for (const field of relationFields) {
2279
- if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId) {
2394
+ const storeKey = this.storeKeyByEntity.get(normalizedName) ?? normalizedName;
2395
+ let fieldMap = relationFieldsByStore.get(storeKey);
2396
+ if (!fieldMap) {
2397
+ fieldMap = /* @__PURE__ */ new Map();
2398
+ relationFieldsByStore.set(storeKey, fieldMap);
2399
+ }
2400
+ for (const f of schema.fields) {
2401
+ if (isRelationField(f) && !fieldMap.has(f.name)) {
2402
+ fieldMap.set(f.name, f);
2403
+ }
2404
+ }
2405
+ }
2406
+ const ownerColumnsByStore = /* @__PURE__ */ new Map();
2407
+ for (const { entity, field } of this.parsedOwnerFields()) {
2408
+ const storeKey = this.resolveStoreKey(entity);
2409
+ const set = ownerColumnsByStore.get(storeKey) ?? /* @__PURE__ */ new Set();
2410
+ set.add(field);
2411
+ ownerColumnsByStore.set(storeKey, set);
2412
+ }
2413
+ const stampedCellKeys = new Set(
2414
+ this.ownerStampedCells.map((cell) => stampedCellKey(cell.entity, cell.id, cell.column))
2415
+ );
2416
+ for (const [storeKey, fieldMap] of relationFieldsByStore) {
2417
+ const store = this.stores.get(storeKey);
2418
+ if (!store || fieldMap.size === 0) continue;
2419
+ const rows = [...store.values()];
2420
+ const ownerColumns = ownerColumnsByStore.get(storeKey);
2421
+ for (const field of fieldMap.values()) {
2422
+ const isOwnerColumn = ownerColumns?.has(field.name) ?? false;
2423
+ const targetKey = (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? this.resolveStoreKey(field.relation.entity);
2424
+ const targetStore = this.stores.get(targetKey);
2425
+ if (!targetStore || targetStore.size === 0) continue;
2426
+ const sameStore = targetStore === store;
2427
+ const cardinality = field.relation.cardinality ?? "many";
2428
+ if (sameStore && (cardinality === "one" || cardinality === "many-to-one")) {
2429
+ rows.forEach((row, i) => {
2430
+ if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId || stampedCellKeys.has(stampedCellKey(storeKey, row["id"], field.name))) {
2431
+ return;
2432
+ }
2433
+ row[field.name] = i === 0 ? "" : rows[Math.floor((i - 1) / 2)]["id"];
2434
+ });
2435
+ continue;
2436
+ }
2437
+ for (const row of rows) {
2438
+ const selfId = row["id"];
2439
+ if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId || stampedCellKeys.has(stampedCellKey(storeKey, selfId, field.name))) {
2280
2440
  continue;
2281
2441
  }
2282
- const targetNormalized = (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? field.relation.entity.toLowerCase();
2283
- const targetStore = this.stores.get(targetNormalized);
2284
- if (!targetStore || targetStore.size === 0) continue;
2285
- const selfId = row["id"];
2286
- const sameStore = targetStore === store;
2287
- const eligible = [];
2442
+ let eligible = [];
2288
2443
  for (const id of targetStore.keys()) {
2289
2444
  if (sameStore && id === selfId) continue;
2290
2445
  eligible.push(id);
2291
2446
  }
2447
+ if (isOwnerColumn && this.ownerCandidateGate) {
2448
+ const gate = this.ownerCandidateGate;
2449
+ eligible = eligible.filter(
2450
+ (id) => gate(storeKey, { ...row, [field.name]: id }, targetStore.get(id))
2451
+ );
2452
+ }
2292
2453
  if (eligible.length === 0) continue;
2293
- const cardinality = field.relation.cardinality ?? "many";
2294
2454
  if (cardinality === "one" || cardinality === "many-to-one") {
2295
2455
  row[field.name] = randomArrayElement(eligible);
2296
2456
  } else {
@@ -2301,6 +2461,43 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2301
2461
  }
2302
2462
  }
2303
2463
  }
2464
+ /**
2465
+ * Fill fields a late-registering sibling schema declares that the shared
2466
+ * store's existing rows lack. The first registrant on a collection seeds
2467
+ * with ITS field list; a sibling declaring extra columns (e.g. `Tag.parentId`
2468
+ * arriving after `WikiTagRef {id,name}`) would otherwise read `undefined`
2469
+ * where its filters expect a value. Values come from the same canonical
2470
+ * sample policy as seeding; relation fields are then linked by the global
2471
+ * `linkRelationFields` pass that follows registration.
2472
+ */
2473
+ backfillFields(storeKey, schema) {
2474
+ const store = this.stores.get(storeKey);
2475
+ if (!store) return;
2476
+ const candidates = schema.fields.filter(
2477
+ (f) => f.name !== "id" && f.name !== "createdAt" && f.name !== "updatedAt"
2478
+ );
2479
+ if (candidates.length === 0) return;
2480
+ let index = 0;
2481
+ for (const row of store.values()) {
2482
+ index += 1;
2483
+ const absent = candidates.filter((f) => row[f.name] === void 0);
2484
+ if (absent.length === 0) continue;
2485
+ const sample = sampleRow(
2486
+ { name: schema.name, persistence: schema.persistence, fields: absent },
2487
+ { index, strategy: "seeded", persistence: schema.persistence }
2488
+ );
2489
+ for (const f of absent) {
2490
+ if (sample[f.name] !== void 0) {
2491
+ row[f.name] = sample[f.name];
2492
+ }
2493
+ }
2494
+ }
2495
+ mockLog.debug("mock:backfill", {
2496
+ storeKey,
2497
+ entity: schema.name,
2498
+ fields: candidates.map((f) => f.name)
2499
+ });
2500
+ }
2304
2501
  /**
2305
2502
  * Seed an entity with pre-authored instance data.
2306
2503
  */
@@ -2326,18 +2523,28 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2326
2523
  seed(entityName, fields, count, persistence) {
2327
2524
  const store = this.getStore(entityName);
2328
2525
  const normalized = entityName.toLowerCase();
2526
+ const storeKey = this.resolveStoreKey(entityName);
2329
2527
  if (this.config.debug) {
2330
2528
  mockLog.debug("seeding", { count, entity: entityName });
2331
2529
  }
2332
- const ownerCols = (this.config.ownerFields ?? []).map((pair) => pair.split(".")).filter(([ent]) => ent?.toLowerCase() === normalized).map(([, field]) => field).filter((field) => Boolean(field));
2530
+ const ownerCols = this.parsedOwnerFields().filter(({ entity }) => entity.toLowerCase() === normalized).map(({ field }) => field);
2333
2531
  const ownerId = this.config.ownerId;
2334
2532
  const generated = [];
2533
+ let fallbackStamps = 0;
2335
2534
  for (let i = 0; i < count; i++) {
2336
2535
  const item = this.generateMockItem(entityName, fields, i + 1, persistence);
2337
2536
  if (ownerId && ownerCols.length > 0 && i % 2 === 0) {
2338
2537
  for (const col of ownerCols) {
2538
+ if (this.ownerGate && !this.ownerGate(storeKey, { ...item, [col]: ownerId })) {
2539
+ const fallbackId = this.firstEligibleOwner(storeKey, col, item);
2540
+ if (fallbackId === void 0) continue;
2541
+ item[col] = fallbackId;
2542
+ this.ownerStampedCells.push({ entity: storeKey, id: item.id, column: col });
2543
+ fallbackStamps++;
2544
+ continue;
2545
+ }
2339
2546
  item[col] = ownerId;
2340
- this.ownerStampedCells.push({ entity: normalized, id: item.id, column: col });
2547
+ this.ownerStampedCells.push({ entity: storeKey, id: item.id, column: col });
2341
2548
  }
2342
2549
  }
2343
2550
  store.set(item.id, item);
@@ -2346,7 +2553,23 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2346
2553
  updatedAt: typeof item.updatedAt === "string" ? item.updatedAt : ""
2347
2554
  });
2348
2555
  }
2349
- mockLog.debug("mock:seed", () => ({ entityName, count, idsAndTimestamps: JSON.stringify(generated) }));
2556
+ mockLog.debug("mock:seed", () => ({
2557
+ entityName,
2558
+ count,
2559
+ idsAndTimestamps: JSON.stringify(generated),
2560
+ fallbackStamps
2561
+ }));
2562
+ }
2563
+ /** Parsed `config.ownerFields` pairs — every declared "Entity.field" owner
2564
+ * column, split once so `seed()` and `linkRelationFields()` don't each
2565
+ * re-parse the same strings. */
2566
+ parsedOwnerFields() {
2567
+ const out = [];
2568
+ for (const pair of this.config.ownerFields ?? []) {
2569
+ const [entity, field] = pair.split(".");
2570
+ if (entity && field) out.push({ entity, field });
2571
+ }
2572
+ return out;
2350
2573
  }
2351
2574
  /**
2352
2575
  * Generate a single mock item. Field values come from the canonical policy in
@@ -2467,9 +2690,9 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2467
2690
  * Clear all data for an entity.
2468
2691
  */
2469
2692
  clear(entityName) {
2470
- const normalized = entityName.toLowerCase();
2471
- this.stores.delete(normalized);
2472
- this.idCounters.delete(normalized);
2693
+ const key = this.resolveStoreKey(entityName);
2694
+ this.stores.delete(key);
2695
+ this.idCounters.delete(key);
2473
2696
  }
2474
2697
  /** Clear all data + re-anchor the PRNG so the next seed loop reproduces
2475
2698
  * identical rows. Hermetic-frame mode calls this between every step
@@ -2478,6 +2701,9 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2478
2701
  this.stores.clear();
2479
2702
  this.idCounters.clear();
2480
2703
  this.ownerStampedCells = [];
2704
+ this.storeKeyByEntity.clear();
2705
+ this.idLabelByStoreKey.clear();
2706
+ this.storeNameById.clear();
2481
2707
  this.resetFakerSeed();
2482
2708
  mockLog.debug("mock:adapter:clearAll", { reanchored: this.config.seed });
2483
2709
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
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, S as ServiceCallContext, T as TraitDefinition } from './types-BaD_ox7e.js';
2
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-BaD_ox7e.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-CZHHxo_A.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-CZHHxo_A.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-DOJDoqXu.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-DOJDoqXu.js';
5
5
  import { EvaluationContext, SExpressionEvaluator } from '@almadar/evaluator';
6
6
  export { EvaluationContext, createMinimalContext } from '@almadar/evaluator';
7
7
  import { RenderBindingMarker, SExpr, RuntimeValue, TraitConfigObject, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, ServiceParams, EntityAccessPolicies, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
@@ -571,6 +571,15 @@ interface EntitySchema {
571
571
  name: string;
572
572
  /** V4 dual-carry id sibling of `name` — optional until the Phase-7 flip. */
573
573
  id?: EntityId;
574
+ /**
575
+ * Declared `persistent: <collection>` name. Entities declaring the SAME
576
+ * collection share ONE store — a shadow entity (`WikiTagRef [persistent:
577
+ * tags]`) reads the very rows its sibling (`Tag [persistent: tags]`) seeds,
578
+ * matching the compiled path's dedup-by-collection (orbital-shell-typescript
579
+ * seed.rs) and docs/Almadar_Entity.md's collection rule. Absent → the store
580
+ * is keyed by entity name, as before.
581
+ */
582
+ collection?: string;
574
583
  fields: NamedEntityField[];
575
584
  /** Pre-authored instance data from the schema (used instead of generated mocks) */
576
585
  seedData?: EntityRow[];
@@ -610,8 +619,14 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
610
619
  private stores;
611
620
  private schemas;
612
621
  private idCounters;
613
- /** entityId -> normalized store name, so relation lookups can prefer the id sibling over `relation.entity` name-matching. */
622
+ /** entityId -> store key, so relation lookups can prefer the id sibling over `relation.entity` name-matching. */
614
623
  private storeNameById;
624
+ /** normalized entity name -> store key (the declared collection, else the name).
625
+ * Entities sharing a `persistent:` collection resolve to the same store. */
626
+ private storeKeyByEntity;
627
+ /** store key -> the first registrant's entity name, used as the minted-id label
628
+ * so rows in a shared collection carry one consistent id family. */
629
+ private idLabelByStoreKey;
615
630
  private config;
616
631
  /**
617
632
  * Every (entity, row id, column) cell `seed()` stamped with `config.ownerId`.
@@ -648,14 +663,65 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
648
663
  * No-op when there is no new id, or it matches the current one — an
649
664
  * anonymous switch (`newOwnerId === undefined`) leaves existing stamps as
650
665
  * they are rather than clearing a non-nullable owner column.
666
+ *
667
+ * Both this re-labeling AND `seed()`'s original stamping consult the
668
+ * `ownerGate` hook (see `setOwnerGate`) — a re-register reseeds identical
669
+ * rows, so gating only the restamp path leaks the moment a client
670
+ * connects and the deterministic reseed stamps with the updated ownerId.
651
671
  */
652
672
  restampOwner(newOwnerId: string | undefined): void;
673
+ /**
674
+ * The host's answer to "may the current viewer OWN this row?" — evaluated
675
+ * against the candidate row with the owner column already re-pointed,
676
+ * `storeKey` being the collection key the stores are keyed by. The runtime
677
+ * installs the registered schema's `@create` directive here (the single
678
+ * authority on who authors; no role heuristics), so a read-only persona
679
+ * never inherits authorship of drafts from the demo stamper — neither on a
680
+ * persona switch (`restampOwner`) nor on a deterministic reseed (`seed`).
681
+ * Absent hook = stamp unconditionally (pre-role-model behavior).
682
+ */
683
+ private ownerGate?;
684
+ setOwnerGate(gate: ((storeKey: string, candidateRow: EntityRow) => boolean) | undefined): void;
685
+ /**
686
+ * The host's answer to "may THIS identity row own this candidate row?" —
687
+ * evaluated per candidate rather than against the fixed default viewer, so
688
+ * `linkRelationFields` and the eligible-owner fallback (see
689
+ * `firstEligibleOwner`) can pick a policy-valid owner from the whole
690
+ * identity roster instead of only ever checking the current default user.
691
+ * Absent hook = no candidate filtering (pre-role-model behavior).
692
+ */
693
+ private ownerCandidateGate?;
694
+ setOwnerCandidateGate(gate: ((storeKey: string, candidateRow: EntityRow, candidateIdentityRow: EntityRow) => boolean) | undefined): void;
695
+ /**
696
+ * The store an owner column's relation field targets, resolved the same
697
+ * way `linkRelationFields` resolves any relation target (id sibling wins
698
+ * over the `entity` name string) — but keyed by STORE, mirroring that
699
+ * method's first-declarer-per-collection rule, so `seed()` and
700
+ * `restampOwner()` can find it before a `linkRelationFields` pass has run
701
+ * this registration. Undefined when no registered schema on `storeKey`
702
+ * declares `column` as a relation field.
703
+ */
704
+ private resolveOwnerColumnTargetStore;
705
+ /**
706
+ * The first identity-store row (seed order) whose persona is eligible, per
707
+ * the installed `ownerCandidateGate`, to own `row`'s `column` in `storeKey`
708
+ * — the deterministic replacement for "skip the stamp" when the CURRENT
709
+ * viewer fails the `@create` policy (e.g. a reader-default persona against
710
+ * an author|reviewer-only policy). Undefined when no gate is installed, the
711
+ * column isn't a resolvable relation, or no identity row is eligible — the
712
+ * caller keeps today's skip behavior in that case.
713
+ */
714
+ private firstEligibleOwner;
653
715
  /** Re-anchor the PRNG to the configured seed. Called before every
654
716
  * re-seed loop so identical reseed sequences produce identical rows
655
717
  * (timestamps + generated fields). Without this, the first
656
718
  * reseed produces row set A, the second produces row set B, and
657
719
  * diff observers see all rows as "changed" between frames. */
658
720
  resetFakerSeed(): void;
721
+ /** Resolve an entity name to its store key: the declared collection when the
722
+ * entity is registered, the lowercased name otherwise (unregistered ad-hoc
723
+ * creates keep working). */
724
+ private resolveStoreKey;
659
725
  private getStore;
660
726
  private nextId;
661
727
  /**
@@ -672,12 +738,29 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
672
738
  * of nested-tree atoms (e.g. std-thread-comments-linear with ThreadPost.
673
739
  * replies → [ThreadPost]) render empty reply cards.
674
740
  *
675
- * For self-referential relations, each row gets 2–4 sibling IDs (excluding
676
- * self). For cross-entity relations, IDs are picked from the target store.
677
- * The runtime caps recursion at depth=2 in `populateRelations`, so
678
- * grandparent-of-self cycles render two levels deep then stop.
741
+ * Cross-entity relations pick random IDs from the target store. A
742
+ * SELF-referential `one`-cardinality relation (a parent column like
743
+ * `Tag.parentId : Tag`) is linked deterministically instead: row 0 stays a
744
+ * root (`""`) and row *i* parents to row ⌊(i−1)/2⌋ — a proper forest with
745
+ * real roots and no cycles, so tree views and `parentId = ""` root fetches
746
+ * render sensibly. Self-referential `many` relations keep the 2–4 random
747
+ * sibling IDs (excluding self). The runtime caps recursion at depth=2 in
748
+ * `populateRelations`, so deep chains render two levels then stop.
749
+ *
750
+ * Entities sharing a collection are linked once per STORE, over the union
751
+ * of their declared relation fields (first declarer of a field name wins).
679
752
  */
680
753
  linkRelationFields(): void;
754
+ /**
755
+ * Fill fields a late-registering sibling schema declares that the shared
756
+ * store's existing rows lack. The first registrant on a collection seeds
757
+ * with ITS field list; a sibling declaring extra columns (e.g. `Tag.parentId`
758
+ * arriving after `WikiTagRef {id,name}`) would otherwise read `undefined`
759
+ * where its filters expect a value. Values come from the same canonical
760
+ * sample policy as seeding; relation fields are then linked by the global
761
+ * `linkRelationFields` pass that follows registration.
762
+ */
763
+ private backfillFields;
681
764
  /**
682
765
  * Seed an entity with pre-authored instance data.
683
766
  */
@@ -686,6 +769,10 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
686
769
  * Seed an entity with mock data.
687
770
  */
688
771
  seed(entityName: string, fields: EntityField[], count: number, persistence?: EntityPersistence): void;
772
+ /** Parsed `config.ownerFields` pairs — every declared "Entity.field" owner
773
+ * column, split once so `seed()` and `linkRelationFields()` don't each
774
+ * re-parse the same strings. */
775
+ private parsedOwnerFields;
689
776
  /**
690
777
  * Generate a single mock item. Field values come from the canonical policy in
691
778
  * `@almadar/core/mock`; this method owns only the id and timestamp stamping.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { EffectExecutor } from './chunk-MEBKFZC4.js';
2
- export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeCallSiteConfigToValues, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes } from './chunk-MEBKFZC4.js';
1
+ import { EffectExecutor } from './chunk-IFSFX43J.js';
2
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeCallSiteConfigToValues, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes } from './chunk-IFSFX43J.js';
3
3
  export { cronMatches, cronMinuteKey, isValidCronExpression, parseCron, parseCronField } from './chunk-OU3ITB5S.js';
4
4
  import { createContextFromBindings, applyRowAccess, checkMutationAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
5
5
  export { CALLSITE_PAYLOAD_PREFIX, applyRowAccess, checkMutationAccess, containsBindings, createContextFromBindings, createMinimalContext, deferEntityBindings, extractBindings, interpolateProps, interpolateValue, resolveCallSitePayloadCaptures } from './chunk-ZJ62H3ES.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "6.62.0",
3
+ "version": "6.64.0",
4
4
  "description": "Interpreted runtime for Almadar orbital applications (OrbitalServerRuntime)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,11 +57,11 @@
57
57
  "access": "public"
58
58
  },
59
59
  "dependencies": {
60
- "@almadar/core": "^10.71.0",
60
+ "@almadar/core": "^10.75.0",
61
61
  "@almadar/evaluator": "^2.42.0",
62
62
  "@almadar/logger": "^1.11.0",
63
63
  "@almadar/server": "^2.38.0",
64
- "@almadar/std": "^16.190.0"
64
+ "@almadar/std": "^16.194.0"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "express": "^5.0.0"