@almadar/runtime 6.71.0 → 6.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { createMinimalContext, resolveBinding, evaluate } from '@almadar/evaluator';
1
+ import { resolveBinding, evaluate, createMinimalContext } from '@almadar/evaluator';
2
2
  export { createMinimalContext } from '@almadar/evaluator';
3
3
  import { isKnownStdOperator } from '@almadar/std/registry';
4
4
  import { containsEntityBinding, containsPayloadBinding, RENDER_BINDING_MARKER } from '@almadar/core';
@@ -10,33 +10,6 @@ setNamespaceLevel("almadar:runtime:bindings", "WARN");
10
10
  var deferLog = createLogger("almadar:runtime:defer");
11
11
  var renderLog = createLogger("almadar:runtime:render-ui");
12
12
  var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
13
- var CALLSITE_PAYLOAD_PREFIX = "@callsitePayload.";
14
- function payloadValueToConfigValue(v) {
15
- if (v === null || v === void 0) return null;
16
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return v;
17
- if (v instanceof Date) return v.toISOString();
18
- if (Array.isArray(v)) return v.map(payloadValueToConfigValue);
19
- if (typeof v === "object") {
20
- const obj = {};
21
- for (const [k, val] of Object.entries(v)) obj[k] = payloadValueToConfigValue(val);
22
- return obj;
23
- }
24
- return String(v);
25
- }
26
- function resolveCallSitePayloadCaptures(config, payload) {
27
- let ctx;
28
- const out = {};
29
- for (const [key, value] of Object.entries(config)) {
30
- if (typeof value === "string" && value.startsWith(CALLSITE_PAYLOAD_PREFIX)) {
31
- const field = value.slice(CALLSITE_PAYLOAD_PREFIX.length);
32
- if (!ctx) ctx = createMinimalContext({}, payload ?? {}, "idle");
33
- out[key] = payloadValueToConfigValue(resolveBinding(`@payload.${field}`, ctx));
34
- } else {
35
- out[key] = value;
36
- }
37
- }
38
- return out;
39
- }
40
13
  function isClientOnlyBinding(value) {
41
14
  if (!value.startsWith("@")) return false;
42
15
  const afterAt = value.slice(1);
@@ -330,6 +303,9 @@ function createContextFromBindings(bindings, strictBindings, contextExtensions)
330
303
  if (bindings.currentTheme) {
331
304
  ctx.currentTheme = bindings.currentTheme;
332
305
  }
306
+ if (bindings.callsitePayload) {
307
+ ctx.callsitePayload = bindings.callsitePayload;
308
+ }
333
309
  if (bindings.locals) {
334
310
  ctx.locals = bindings.locals;
335
311
  }
@@ -379,4 +355,4 @@ function checkMutationAccess(row, policy, bindings) {
379
355
  }
380
356
  }
381
357
 
382
- export { CALLSITE_PAYLOAD_PREFIX, accessDeniedMessage, applyRowAccess, checkMutationAccess, containsBindings, createContextFromBindings, deferEntityBindings, extractBindings, interpolateProps, interpolateValue, resolveCallSitePayloadCaptures };
358
+ export { accessDeniedMessage, applyRowAccess, checkMutationAccess, containsBindings, createContextFromBindings, deferEntityBindings, extractBindings, interpolateProps, interpolateValue };
@@ -1,4 +1,4 @@
1
- import { E as EffectHandlers } from './types-BaD_ox7e.js';
1
+ import { E as EffectHandlers } from './types-CWaIuEQn.js';
2
2
  import { EventPayload } from '@almadar/core';
3
3
 
4
4
  /**
@@ -1,2 +1,2 @@
1
- export { accessDeniedMessage, applyRowAccess, checkMutationAccess } from './chunk-ZJ62H3ES.js';
1
+ export { accessDeniedMessage, applyRowAccess, checkMutationAccess } from './chunk-ZWAOJT6R.js';
2
2
  import './chunk-MLKGABMK.js';
@@ -155,6 +155,7 @@ var ExternalOrbitalLoader = class {
155
155
  data: {
156
156
  orbital,
157
157
  orbitals: schema.orbitals,
158
+ ...schema.config ? { schemaConfig: schema.config } : {},
158
159
  sourcePath: schemaResult.data.sourcePath,
159
160
  importPath
160
161
  }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
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
- 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-DnDJhpLS.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-DnDJhpLS.js';
1
+ import { h as RuntimePatternValue, B as BindingContext, f as EvaluationContextExtensions, P as PatternProps, E as EffectHandlers, g as EffectContext, i as ExecutionEnvironment, j as EffectResult, T as TraitDefinition } from './types-CWaIuEQn.js';
2
+ export { k as BrowserFileMeta, l as BrowserFilePickerOptions, m as BrowserGeolocationOptions, n as BrowserGeolocationPosition, C as ConfigContext, o as Effect, a as EventListener, H as HANDLER_MANIFEST, I as IEventBus, b as RuntimeConfig, R as RuntimeEvent, d as TraitState, c as TransitionObserver, e as TransitionResult, U as Unsubscribe } from './types-CWaIuEQn.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-BiEntWd-.js';
4
+ export { C as CreateServerEffectHandlersOptions, E as EntitySharingMap, c as EventBus, d as EventNamespaceMap, e as InMemoryPersistence, f as LIFECYCLE_EVENTS, O as OrbitalEventRequest, g as OrbitalEventResponse, h as OrbitalServerRuntimeConfig, i as PreprocessOptions, j as PreprocessResult, k as PreprocessedSchema, l as ProcessEventOptions, R as RegisteredOrbital, m as RuntimeOrbital, n as RuntimeOrbitalSchema, o as RuntimeTrait, p as ServerEffectResult, q as StateMachineManager, r as collectDeclaredConfigDefaults, s as collectDeclaredEntityDefaults, t as createInitialTraitState, u as createServerEffectHandlers, v as findInitialState, w as findTransition, x as getIsolatedCollectionName, y as getNamespacedEvent, z as isBrowser, A as isElectron, B as isNamespacedEvent, D as isNode, F as normalizeEventKey, G as parseNamespacedEvent, H as preprocessSchema, J as processEvent } from './OrbitalServerRuntime-BiEntWd-.js';
5
5
  import { EvaluationContext, SExpressionEvaluator } from '@almadar/evaluator';
6
6
  export { EvaluationContext, createMinimalContext } from '@almadar/evaluator';
7
- import { RenderBindingMarker, SExpr, RuntimeValue, TraitConfigObject, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, ServiceParams, EntityAccessPolicies, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
7
+ import { RenderBindingMarker, SExpr, RuntimeValue, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
8
8
  export { EntityField, normalizeCallSiteConfigToValues } from '@almadar/core';
9
9
  export { AccessBindings, applyRowAccess, checkMutationAccess } from './entityAccess.js';
10
10
  export { ServerBridgeConfig, ServerBridgeState } from './ServerBridge.js';
@@ -268,23 +268,6 @@ declare function isValidDurationString(interval: string): boolean;
268
268
  * @packageDocumentation
269
269
  */
270
270
 
271
- /**
272
- * Call-site payload capture grammar. Mirrors the Rust orbital-core
273
- * `CALLSITE_PAYLOAD_PREFIX` (`@callsitePayload.`). A composed trait's call-site
274
- * config value of this form is a snapshot of the COMPOSING effect's triggering
275
- * event payload, captured at the call site — deliberately distinct from
276
- * `@payload.` so it is never evaluated in the child's own INIT scope.
277
- */
278
- declare const CALLSITE_PAYLOAD_PREFIX = "@callsitePayload.";
279
- /**
280
- * Resolve call-site payload captures in a child trait's call-site config
281
- * against the COMPOSING effect's triggering event payload. Each value of the
282
- * form `@callsitePayload.<field>` becomes the literal read from `payload`
283
- * (snapshot semantics — the child sees a plain value, never a payload ref).
284
- * Non-capture entries pass through untouched. Returns a new object; the input
285
- * is not mutated.
286
- */
287
- declare function resolveCallSitePayloadCaptures(config: TraitConfigObject, payload: EventPayload | undefined): TraitConfigObject;
288
271
  /**
289
272
  * Interpolate binding references in props.
290
273
  *
@@ -481,6 +464,14 @@ declare class EffectExecutor {
481
464
  * substrate-op args. Returns `[positionalArgs, emitConfig]`.
482
465
  */
483
466
  private splitSubstrateEmit;
467
+ /**
468
+ * Dispatches one effect. Returns `void` for the ordinary case (the
469
+ * caller only cares whether it threw). A `persist` that resolves
470
+ * without throwing — the store DENIED the write, signalled by return
471
+ * value rather than an exception, per the "close the circuit" doctrine
472
+ * — reports that outcome here so `executeWithResults` can record it as
473
+ * `status: 'failed'` instead of the default `'executed'`.
474
+ */
484
475
  private dispatch;
485
476
  private logUnsupported;
486
477
  }
@@ -839,115 +830,6 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
839
830
  */
840
831
  declare function createMockPersistence(config?: MockPersistenceConfig): MockPersistenceAdapter;
841
832
 
842
- /**
843
- * ServerEffectHandlers — reusable factory for the server-side effect layer.
844
- *
845
- * Mirrors the handlers built inline in
846
- * `OrbitalServerRuntime.executeEffects` so both the real server runtime
847
- * AND the in-browser mock runtime (`@almadar/ui` OrbPreview autoMock) can
848
- * share the same `fetch` / `persist` / `set` / `ref` / `deref` / `swap!` /
849
- * `atomic` / `callService` semantics against a `PersistenceAdapter`.
850
- *
851
- * Browser-safe: only imports core + this package's browser-safe modules.
852
- * Does NOT import express/Node, so it is reachable from `@almadar/ui`.
853
- *
854
- * @packageDocumentation
855
- */
856
-
857
- /**
858
- * Minimal event-bus contract the server handlers need. Narrower than the
859
- * full `IEventBus` so clients with a React-context bus (only `emit` is
860
- * relevant for effect dispatch) can hand it in without adapter code.
861
- */
862
- interface ServerEffectEventBus {
863
- emit(event: string, payload?: EventPayload, source?: {
864
- orbital?: string;
865
- trait?: string;
866
- }): void;
867
- }
868
- /**
869
- * Result entry recorded for each effect invocation. Mirrors the server
870
- * runtime's `EffectResult`. Callers who want telemetry pass an array that
871
- * the factory appends to; otherwise it's unused.
872
- */
873
- interface ServerEffectResult {
874
- effect: "set" | "persist" | "call-service" | "fetch" | "ref" | "deref" | "swap" | "atomic";
875
- action?: string;
876
- entityType?: string;
877
- data?: unknown;
878
- success: boolean;
879
- error?: string;
880
- }
881
- interface CreateServerEffectHandlersOptions {
882
- /** Persistent store backing `fetch` / `persist` / `ref` / `deref` / `swap`. */
883
- persistence: PersistenceAdapter;
884
- /** Event bus that `emit` delegates to. Only `.emit()` is required. */
885
- eventBus: ServerEffectEventBus;
886
- /** The trait's linked entity type (used as the default for persist/set). */
887
- entityType: string;
888
- /** Current entity row id (used by `set`, `persist update/delete` fallback). */
889
- entityId?: string;
890
- /**
891
- * Binding object passed to inner `atomic` evaluator. When absent, atomic
892
- * effects still run but have no access to `@entity` / `@payload` bindings.
893
- */
894
- bindings?: BindingContext;
895
- /** Effect context passed to the inner `atomic` executor. */
896
- context?: EffectContext;
897
- /** Per-event result sink. Optional — telemetry only. */
898
- effectResults?: ServerEffectResult[];
899
- /**
900
- * Per-event fetched-data cache. `fetch` writes here so downstream UI
901
- * renderers can resolve `@entity` bindings from the latest load without
902
- * a separate round-trip.
903
- */
904
- fetchedData?: Record<string, EntityRow[]>;
905
- /** Per-event emit log. Optional — telemetry only. */
906
- emittedEvents?: Array<{
907
- event: string;
908
- payload?: EventPayload;
909
- }>;
910
- /** Source stamp applied to all emits. */
911
- source?: {
912
- orbital?: string;
913
- trait?: string;
914
- };
915
- /** Consumer-supplied `call-service` handler. When absent, calls warn and return null. */
916
- callService?: (service: string, action: string, params?: ServiceParams, context?: ServiceCallContext) => Promise<EventPayload | null>;
917
- /**
918
- * The declared `@read`/`@create`/`@update`/`@delete` directives, keyed by
919
- * entity name. Build it with `entityAccessTable(schema)` from `@almadar/core`.
920
- *
921
- * Optional: a caller that holds no schema (the client offline-preview path)
922
- * passes nothing and gets today's unrestricted behavior. A preview is not a
923
- * security boundary — the generated server is, and that one always has the
924
- * policies compiled in.
925
- */
926
- entityAccess?: ReadonlyMap<string, EntityAccessPolicies>;
927
- /** Verbose logging. */
928
- debug?: boolean;
929
- }
930
- /**
931
- * Build the full server-side effect handler set bound to a persistence
932
- * adapter. The returned object satisfies `EffectHandlers` and can be
933
- * handed directly to `EffectExecutor`.
934
- *
935
- * Scope: one handler object per transition — capture `entityId`,
936
- * `bindings`, `context`, and the sink arrays at build time. For a new
937
- * transition, call this factory again.
938
- *
939
- * Intentionally does NOT implement:
940
- * - `renderUI` / `notify` / `navigate` — these are client-side, provided
941
- * by `createClientEffectHandlers`. A mock runtime merges both handler
942
- * sets.
943
- * - `os/watch-*` observers — these are a no-op outside the server.
944
- * - Schema-aware relation cardinality / on-delete cascades — those live
945
- * in `OrbitalServerRuntime` and depend on the full registered schema.
946
- * Clients that need them can wrap the persist handler with their own
947
- * validation.
948
- */
949
- declare function createServerEffectHandlers(opts: CreateServerEffectHandlersOptions): EffectHandlers;
950
-
951
833
  /**
952
834
  * PayloadValidator - Cross-Trait Payload Shape Validation (RCG-10)
953
835
  *
@@ -1222,4 +1104,4 @@ declare namespace index {
1222
1104
  export { type index_ComposeBehaviorsInput as ComposeBehaviorsInput, type index_ComposeBehaviorsResult as ComposeBehaviorsResult, type index_EventWiringEntry as EventWiringEntry, type index_LayoutStrategy as LayoutStrategy, type index_PipeStep as PipeStep, index_applyEventWiring as applyEventWiring, index_composeBehaviors as composeBehaviors, index_detectLayoutStrategy as detectLayoutStrategy, index_pipeBehaviors as pipeBehaviors };
1223
1105
  }
1224
1106
 
1225
- export { BindingContext, CALLSITE_PAYLOAD_PREFIX, type ClientEventBus, type ComposeBehaviorsInput, type ComposeBehaviorsResult, type CreateClientEffectHandlersOptions, type CreateServerEffectHandlersOptions, type CronFields, type DeferredPatternValue, EffectContext, EffectExecutor, type EffectExecutorOptions, EffectHandlers, EffectResult, type EntitySchema, type EventWiringEntry, ExecutionEnvironment, ImportChainLike, type LayoutStrategy, LoadResult, LoadedOrbital, LoadedSchema, MockPersistenceAdapter, type MockPersistenceConfig, type PayloadMismatch, type PayloadValidationFailure, PersistenceAdapter, type PipeStep, SchemaLoader, type ServerEffectResult, type SlotSetter, type TickHandle, TickScheduler, TraitDefinition, UnifiedLoaderOptions, applyEventWiring, buildEmitsFromTraits, composeBehaviors, index as composition, containsBindings, createClientEffectHandlers, createContextFromBindings, createMockPersistence, createServerEffectHandlers, createTestExecutor, createTickScheduler, createUnifiedLoader, cronMatches, cronMinuteKey, deferEntityBindings, detectLayoutStrategy, extractBindings, formatPayloadValidationError, interpolateProps, interpolateValue, isValidCronExpression, isValidDurationString, parseCron, parseCronField, parseDurationString, pipeBehaviors, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes };
1107
+ export { BindingContext, type ClientEventBus, type ComposeBehaviorsInput, type ComposeBehaviorsResult, type CreateClientEffectHandlersOptions, type CronFields, type DeferredPatternValue, EffectContext, EffectExecutor, type EffectExecutorOptions, EffectHandlers, EffectResult, type EntitySchema, type EventWiringEntry, ExecutionEnvironment, ImportChainLike, type LayoutStrategy, LoadResult, LoadedOrbital, LoadedSchema, MockPersistenceAdapter, type MockPersistenceConfig, type PayloadMismatch, type PayloadValidationFailure, PersistenceAdapter, type PipeStep, SchemaLoader, type SlotSetter, type TickHandle, TickScheduler, TraitDefinition, UnifiedLoaderOptions, applyEventWiring, buildEmitsFromTraits, composeBehaviors, index as composition, containsBindings, createClientEffectHandlers, createContextFromBindings, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, cronMatches, cronMinuteKey, deferEntityBindings, detectLayoutStrategy, extractBindings, formatPayloadValidationError, interpolateProps, interpolateValue, isValidCronExpression, isValidDurationString, parseCron, parseCronField, parseDurationString, pipeBehaviors, validateEventPayload, validatePayloadShapes };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { EffectExecutor } from './chunk-PAEMYGKV.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-PAEMYGKV.js';
1
+ import { EffectExecutor } from './chunk-A474B57C.js';
2
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, LIFECYCLE_EVENTS, 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-A474B57C.js';
3
3
  export { cronMatches, cronMinuteKey, isValidCronExpression, parseCron, parseCronField } from './chunk-OU3ITB5S.js';
4
- import { createContextFromBindings, applyRowAccess, checkMutationAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
5
- export { CALLSITE_PAYLOAD_PREFIX, applyRowAccess, checkMutationAccess, containsBindings, createContextFromBindings, createMinimalContext, deferEntityBindings, extractBindings, interpolateProps, interpolateValue, resolveCallSitePayloadCaptures } from './chunk-ZJ62H3ES.js';
4
+ import { createContextFromBindings, applyRowAccess, checkMutationAccess, accessDeniedMessage } from './chunk-ZWAOJT6R.js';
5
+ export { applyRowAccess, checkMutationAccess, containsBindings, createContextFromBindings, createMinimalContext, deferEntityBindings, extractBindings, interpolateProps, interpolateValue } from './chunk-ZWAOJT6R.js';
6
6
  import './chunk-T4VDAB4C.js';
7
7
  export { PERF_NAMESPACE, RendererContractViolationError, adjustSchemaForMockData, aggregateSlotContent, assertIsMultiSourceSlotManager, assertIsSlotManager, bindEventBus, bindTraitStateGetter, buildMockData, buildOrbitalsByTrait, clearPerf, createSlotSetter, ensureVerificationApi, getOrbitalVerification, perfEnd, perfGauge, perfStart, perfStore, perfTime, perfTimeAsync, prepareSchemaForPreview, pushPerfEntry, validateSlotContent, wrapCallbackForEvent } from './chunk-F5W3YZGJ.js';
8
8
  export { collectEmbeddedTraits, collectTraitRefsFromResolvedTrait } from './chunk-SCRAHWOC.js';
@@ -283,10 +283,10 @@ function createServerEffectHandlers(opts) {
283
283
  createData
284
284
  );
285
285
  batchResults.push({
286
+ ...createData,
286
287
  action: "create",
287
288
  entityType: opEntityType,
288
- id: newId,
289
- ...createData
289
+ id: newId
290
290
  });
291
291
  completed.push({
292
292
  action: "create",
@@ -301,10 +301,10 @@ function createServerEffectHandlers(opts) {
301
301
  await persistence.update(opEntityType, updateId, updateData);
302
302
  const updated = await persistence.getById(opEntityType, updateId);
303
303
  batchResults.push({
304
+ ...updated || updateData,
304
305
  action: "update",
305
306
  entityType: opEntityType,
306
- id: updateId,
307
- ...updated || updateData
307
+ id: updateId
308
308
  });
309
309
  completed.push({
310
310
  action: "update",
@@ -358,15 +358,17 @@ function createServerEffectHandlers(opts) {
358
358
  const type = targetEntityType || entityType;
359
359
  let resultData;
360
360
  const sizeBefore = (await persistence.list(type)).length;
361
+ let deniedReason;
361
362
  try {
362
363
  const mutationPolicy = entityAccess?.get(type)?.[action];
363
364
  switch (action) {
364
365
  case "create": {
365
366
  if (!checkMutationAccess(data ?? {}, mutationPolicy, accessBindings(type))) {
367
+ deniedReason = "access-denied";
366
368
  throw new Error(accessDeniedMessage("create", type));
367
369
  }
368
370
  const { id } = await persistence.create(type, data ?? {});
369
- resultData = { id, ...data ?? {} };
371
+ resultData = { ...data ?? {}, id };
370
372
  break;
371
373
  }
372
374
  case "update": {
@@ -376,15 +378,17 @@ function createServerEffectHandlers(opts) {
376
378
  if (mutationPolicy !== void 0) {
377
379
  const existing = await persistence.getById(type, idOrFallback);
378
380
  if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings(type))) {
381
+ deniedReason = "access-denied";
379
382
  throw new Error(accessDeniedMessage("update", type));
380
383
  }
381
384
  }
382
385
  await persistence.update(type, idOrFallback, row);
383
386
  const updated = await persistence.getById(type, idOrFallback);
384
- resultData = updated ?? { id: idOrFallback, ...row };
387
+ resultData = updated ?? { ...row, id: idOrFallback };
385
388
  } else {
386
389
  effectLog.error("persist:no-row-key", { action, entityType: type });
387
390
  if (NO_ROW_KEY_IS_FATAL) {
391
+ deniedReason = "no-row-key";
388
392
  throw new Error(
389
393
  `persist ${action} ${type} resolved no row key \u2014 the id was neither on the row being written nor on the request. Bind it before the write, e.g. (set @entity.id ?row.id) on the transition that selects it.`
390
394
  );
@@ -400,6 +404,7 @@ function createServerEffectHandlers(opts) {
400
404
  if (mutationPolicy !== void 0) {
401
405
  const existing = await persistence.getById(type, deleteId);
402
406
  if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings(type))) {
407
+ deniedReason = "access-denied";
403
408
  throw new Error(accessDeniedMessage("delete", type));
404
409
  }
405
410
  }
@@ -408,6 +413,7 @@ function createServerEffectHandlers(opts) {
408
413
  } else {
409
414
  effectLog.error("persist:no-row-key", { action, entityType: type });
410
415
  if (NO_ROW_KEY_IS_FATAL) {
416
+ deniedReason = "no-row-key";
411
417
  throw new Error(
412
418
  `persist ${action} ${type} resolved no row key \u2014 the id was neither on the row being written nor on the request. Bind it before the write, e.g. (set @entity.id ?row.id) on the transition that selects it.`
413
419
  );
@@ -416,6 +422,17 @@ function createServerEffectHandlers(opts) {
416
422
  break;
417
423
  }
418
424
  }
425
+ if (resultData === void 0 && (action === "update" || action === "delete")) {
426
+ record({
427
+ effect: "persist",
428
+ action,
429
+ entityType: type,
430
+ success: false,
431
+ denied: true,
432
+ error: `persist ${action} ${type} resolved no row key`
433
+ });
434
+ return void 0;
435
+ }
419
436
  const sizeAfter = (await persistence.list(type)).length;
420
437
  effectLog.debug("persist:store-mutate", {
421
438
  action,
@@ -443,6 +460,7 @@ function createServerEffectHandlers(opts) {
443
460
  effect: "persist",
444
461
  action,
445
462
  entityType: type,
463
+ ...deniedReason !== void 0 ? { denied: true } : {},
446
464
  success: false,
447
465
  error: err instanceof Error ? err.message : String(err)
448
466
  });
@@ -157,11 +157,31 @@ interface EffectHandlers {
157
157
  * envelope carries. Without it a `*_CREATED` listener sees no id (the
158
158
  * compiled path emits the created row), so anything routing on the new
159
159
  * row's identity — navigating to the page just created — worked in one
160
- * execution path only. Implementations that have no row to hand back
161
- * (batch, a denied write, a no-op stub) return undefined and the
162
- * envelope falls back to the submitted data.
160
+ * execution path only.
161
+ *
162
+ * `create`/`update`/`delete` (batch is separate see `PersistBatchSummary`)
163
+ * return `undefined` ONLY to signal the write was DENIED or otherwise
164
+ * failed (a policy rejection, a missing row key, a no-op stub) — never
165
+ * a genuine success with nothing to report, since every real success
166
+ * branch sets a row. The caller (`EffectExecutor`'s `persist` case)
167
+ * treats `undefined` as a failure: it skips the declared `success`
168
+ * emit entirely rather than falling back to the submitted data, which
169
+ * previously made a denied delete indistinguishable from a completed
170
+ * one.
163
171
  */
164
172
  persist: (action: 'create' | 'update' | 'delete' | 'batch', entityType: string, data?: EntityRow) => Promise<EntityRow | undefined>;
173
+ /**
174
+ * Bridge mode: the SERVER executes every persist of this dispatch and
175
+ * returns the real outcome in its response (`effectResults`), so the
176
+ * client's `persist` member is a placeholder that must never be read as
177
+ * a denial. When `true`, `EffectExecutor` skips the persist locally —
178
+ * no `persist:denied` error, no client-side `failure` emit (which fired
179
+ * the declared failure event in the browser while the server had
180
+ * succeeded), no `success` emit either (the server's cascade carries it).
181
+ * Set by `@almadar/ui`'s `createClientEffectHandlers` when no live
182
+ * client entity is supplied; absent everywhere a handler really writes.
183
+ */
184
+ persistDelegated?: true;
165
185
  /** Set a field value on an entity */
166
186
  set: (entityId: string, field: string, value: FieldValue) => void;
167
187
  /** Call an external service. `context` carries the caller's identity
@@ -383,6 +403,16 @@ interface BindingContext {
383
403
  * `let`-bound locals.
384
404
  */
385
405
  locals?: Map<string, RuntimeValue>;
406
+ /**
407
+ * The composing effect's triggering payload, for a trait embedded via
408
+ * `@trait.X` inline into a parent's render-ui. Set when re-running an
409
+ * embedded child's lifecycle transition under its embedder's payload
410
+ * (see `OrbitalServerRuntime.executeEffects` / the client's
411
+ * `useTraitStateMachine`), so `@callsitePayload.<field>` resolves on the
412
+ * child's evaluation context — mirrors the compiled path's
413
+ * `CALLSITE_PAYLOAD_PREFIX` capture.
414
+ */
415
+ callsitePayload?: EventPayload;
386
416
  /** Additional custom bindings */
387
417
  [key: string]: unknown;
388
418
  }
@@ -512,4 +542,4 @@ interface TransitionObserver {
512
542
  */
513
543
  declare const HANDLER_MANIFEST: Record<ExecutionEnvironment, string[]>;
514
544
 
515
- export { type BindingContext as B, type ConfigContext as C, type EffectHandlers as E, HANDLER_MANIFEST as H, type IEventBus as I, type PatternProps as P, type RuntimeEvent as R, type ServiceCallContext as S, type TraitDefinition as T, type Unsubscribe as U, type EventListener as a, type RuntimeConfig as b, type TransitionObserver as c, type TraitState as d, type TransitionResult as e, type EvaluationContextExtensions as f, type RuntimePatternValue as g, type EffectContext as h, type ExecutionEnvironment as i, type EffectResult as j, type BrowserFileMeta as k, type BrowserFilePickerOptions as l, type BrowserGeolocationOptions as m, type BrowserGeolocationPosition as n, type Effect as o };
545
+ export { type BindingContext as B, type ConfigContext as C, type EffectHandlers as E, HANDLER_MANIFEST as H, type IEventBus as I, type PatternProps as P, type RuntimeEvent as R, type ServiceCallContext as S, type TraitDefinition as T, type Unsubscribe as U, type EventListener as a, type RuntimeConfig as b, type TransitionObserver as c, type TraitState as d, type TransitionResult as e, type EvaluationContextExtensions as f, type EffectContext as g, type RuntimePatternValue as h, type ExecutionEnvironment as i, type EffectResult as j, type BrowserFileMeta as k, type BrowserFilePickerOptions as l, type BrowserGeolocationOptions as m, type BrowserGeolocationPosition as n, type Effect as o };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "6.71.0",
3
+ "version": "6.72.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.84.0",
61
- "@almadar/evaluator": "^2.44.0",
60
+ "@almadar/core": "^10.87.0",
61
+ "@almadar/evaluator": "^2.45.0",
62
62
  "@almadar/logger": "^1.12.0",
63
- "@almadar/server": "^2.39.0",
64
- "@almadar/std": "^16.204.0"
63
+ "@almadar/server": "^2.40.0",
64
+ "@almadar/std": "^16.208.0"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "express": "^5.0.0"
@@ -72,7 +72,7 @@
72
72
  }
73
73
  },
74
74
  "devDependencies": {
75
- "@almadar/eslint-plugin": "^2.17.0",
75
+ "@almadar/eslint-plugin": "^2.18.0",
76
76
  "@types/express": "^5.0.0",
77
77
  "@types/node": "^20.0.0",
78
78
  "@typescript-eslint/parser": "8.65.0",
@@ -102,7 +102,7 @@
102
102
  "build": "tsup",
103
103
  "build:watch": "tsup --watch",
104
104
  "lint": "eslint src/",
105
- "typecheck": "tsc --noEmit",
105
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
106
106
  "test": "vitest --run"
107
107
  }
108
108
  }