@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.
- package/dist/{OrbitalServerRuntime-CZHHxo_A.d.ts → OrbitalServerRuntime-DOJDoqXu.d.ts} +28 -0
- package/dist/OrbitalServerRuntime.d.ts +1 -1
- package/dist/OrbitalServerRuntime.js +72 -6
- package/dist/{chunk-MEBKFZC4.js → chunk-IFSFX43J.js} +266 -40
- package/dist/index.d.ts +94 -7
- package/dist/index.js +2 -2
- package/package.json +3 -3
|
@@ -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-
|
|
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-
|
|
2
|
-
export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-
|
|
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,
|
|
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.
|
|
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.
|
|
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 (
|
|
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 ->
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
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(
|
|
2328
|
+
return this.stores.get(key);
|
|
2223
2329
|
}
|
|
2224
2330
|
nextId(entityName) {
|
|
2225
|
-
const
|
|
2226
|
-
const counter = (this.idCounters.get(
|
|
2227
|
-
this.idCounters.set(
|
|
2228
|
-
|
|
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,
|
|
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
|
-
*
|
|
2265
|
-
*
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
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
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
for (const
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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", () => ({
|
|
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
|
|
2471
|
-
this.stores.delete(
|
|
2472
|
-
this.idCounters.delete(
|
|
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-
|
|
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-
|
|
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 ->
|
|
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
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
64
|
+
"@almadar/std": "^16.194.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"express": "^5.0.0"
|