@almadar/runtime 2.5.0 → 2.6.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,6 +1,6 @@
1
1
  import { Router } from 'express';
2
- import { I as IEventBus, i as RuntimeEvent, h as EventListener, U as Unsubscribe, E as EffectHandlers, g as Effect, T as TraitState } from './types-BrbvZxzX.js';
3
- import { OrbitalSchema, Orbital, Trait } from '@almadar/core';
2
+ import { I as IEventBus, f as RuntimeEvent, e as EventListener, U as Unsubscribe, T as TraitDefinition, R as RuntimeConfig, h as TransitionObserver, g as TraitState, i as TransitionResult, d as Effect, E as EffectHandlers } from './types-DYcUvi4H.js';
3
+ import { EventPayload, EntityRow, OrbitalSchema, Orbital, Trait } from '@almadar/core';
4
4
 
5
5
  /**
6
6
  * EventBus - Platform-Agnostic Pub/Sub Implementation
@@ -48,7 +48,7 @@ declare class EventBus implements IEventBus {
48
48
  * beyond `maxDepth`, the event is dropped and an error is logged.
49
49
  * This prevents infinite loops from circular emit/listen chains.
50
50
  */
51
- emit(type: string, payload?: Record<string, unknown>, source?: RuntimeEvent['source']): void;
51
+ emit(type: string, payload?: EventPayload, source?: RuntimeEvent['source']): void;
52
52
  /**
53
53
  * Subscribe to an event type
54
54
  */
@@ -76,6 +76,168 @@ declare class EventBus implements IEventBus {
76
76
  getListenerCount(type: string): number;
77
77
  }
78
78
 
79
+ /**
80
+ * StateMachineCore - Platform-Agnostic State Machine Logic
81
+ *
82
+ * Pure TypeScript implementation of trait state machine execution.
83
+ * Extracts the core logic from useTraitStateMachine for use on
84
+ * both client and server.
85
+ *
86
+ * @packageDocumentation
87
+ */
88
+
89
+ /**
90
+ * Find the initial state for a trait definition.
91
+ */
92
+ declare function findInitialState(trait: TraitDefinition): string;
93
+ /**
94
+ * Create initial trait state for a trait definition.
95
+ */
96
+ declare function createInitialTraitState(trait: TraitDefinition): TraitState;
97
+ /**
98
+ * Find a matching transition from the current state for the given event.
99
+ */
100
+ declare function findTransition(trait: TraitDefinition, currentState: string, eventKey: string): TraitDefinition['transitions'][0] | undefined;
101
+ /**
102
+ * Normalize event key - strip UI: prefix if present.
103
+ */
104
+ declare function normalizeEventKey(eventKey: string): string;
105
+ /**
106
+ * Options for processing an event through the state machine.
107
+ */
108
+ interface ProcessEventOptions {
109
+ /** Current trait state */
110
+ traitState: TraitState;
111
+ /** Trait definition */
112
+ trait: TraitDefinition;
113
+ /** Event key to process */
114
+ eventKey: string;
115
+ /** Event payload */
116
+ payload?: EventPayload;
117
+ /** Entity data for binding resolution */
118
+ entityData?: EntityRow;
119
+ /**
120
+ * Guard evaluation error handling mode. (RCG-02)
121
+ * - "permissive": Guard errors allow the transition (default, backwards-compatible)
122
+ * - "strict": Guard errors block the transition
123
+ */
124
+ guardMode?: "strict" | "permissive";
125
+ /**
126
+ * When true, log warnings when bindings resolve to undefined. (RCG-01)
127
+ */
128
+ strictBindings?: boolean;
129
+ }
130
+ /**
131
+ * Process an event through a trait's state machine.
132
+ *
133
+ * This is a pure function that:
134
+ * 1. Finds matching transitions
135
+ * 2. Evaluates guards
136
+ * 3. Returns the transition result (but does not execute effects)
137
+ *
138
+ * @returns TransitionResult with effects to execute
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const result = processEvent({
143
+ * traitState: { traitName: 'Cart', currentState: 'empty', ... },
144
+ * trait: cartTraitDefinition,
145
+ * eventKey: 'ADD_ITEM',
146
+ * payload: { productId: '123' },
147
+ * });
148
+ *
149
+ * if (result.executed) {
150
+ * // Execute effects
151
+ * for (const effect of result.effects) {
152
+ * effectExecutor.execute(effect);
153
+ * }
154
+ * // Update state
155
+ * traitState.currentState = result.newState;
156
+ * }
157
+ * ```
158
+ */
159
+ declare function processEvent(options: ProcessEventOptions): TransitionResult;
160
+ declare class StateMachineManager {
161
+ private traits;
162
+ private states;
163
+ private config;
164
+ private observer?;
165
+ private queues;
166
+ private processing;
167
+ constructor(traits?: TraitDefinition[], config?: RuntimeConfig, observer?: TransitionObserver);
168
+ /**
169
+ * Set the transition observer for runtime verification.
170
+ * Wire this to `verificationRegistry.recordTransition()` to enable
171
+ * automatic verification tracking.
172
+ */
173
+ setObserver(observer: TransitionObserver): void;
174
+ /**
175
+ * Add a trait to the manager.
176
+ */
177
+ addTrait(trait: TraitDefinition): void;
178
+ /**
179
+ * Remove a trait from the manager.
180
+ */
181
+ removeTrait(traitName: string): void;
182
+ /**
183
+ * Get current state for a trait.
184
+ */
185
+ getState(traitName: string): TraitState | undefined;
186
+ /**
187
+ * Get all current states.
188
+ */
189
+ getAllStates(): Map<string, TraitState>;
190
+ /**
191
+ * Check if a trait can handle an event from its current state.
192
+ */
193
+ canHandleEvent(traitName: string, eventKey: string): boolean;
194
+ /**
195
+ * Send an event to all traits.
196
+ *
197
+ * @returns Array of transition results (one per trait that had a matching transition)
198
+ */
199
+ sendEvent(eventKey: string, payload?: EventPayload, entityData?: EntityRow): Array<{
200
+ traitName: string;
201
+ result: TransitionResult;
202
+ }>;
203
+ /**
204
+ * Enqueue an event into every trait's per-trait queue.
205
+ *
206
+ * Events are not processed immediately. Call `drainQueue()` for each
207
+ * trait to process them sequentially (actor-model guarantee: one event
208
+ * at a time per trait, effects fully awaited before the next event).
209
+ */
210
+ enqueueEvent(eventKey: string, payload?: EventPayload, entityData?: EntityRow): void;
211
+ /**
212
+ * Drain a single trait's event queue, processing events sequentially.
213
+ *
214
+ * This is the core actor loop: each event is fully processed (including
215
+ * awaiting all effects) before the next event is dequeued. If the queue
216
+ * is already being drained for this trait, this call is a no-op (the
217
+ * running drain will pick up newly enqueued events).
218
+ *
219
+ * @param traitName - Which trait's queue to drain
220
+ * @param executeEffects - Async callback to run effects for a successful transition
221
+ */
222
+ drainQueue(traitName: string, executeEffects: (traitName: string, result: TransitionResult, payload?: EventPayload) => Promise<void>): Promise<void>;
223
+ /**
224
+ * Check whether a trait's queue is currently being drained.
225
+ */
226
+ isProcessing(traitName: string): boolean;
227
+ /**
228
+ * Get the number of pending events in a trait's queue.
229
+ */
230
+ getQueueLength(traitName: string): number;
231
+ /**
232
+ * Reset a trait to its initial state.
233
+ */
234
+ resetTrait(traitName: string): void;
235
+ /**
236
+ * Reset all traits to initial states.
237
+ */
238
+ resetAll(): void;
239
+ }
240
+
79
241
  /**
80
242
  * External Orbital Loader
81
243
  *
@@ -436,7 +598,7 @@ interface RuntimeOrbital {
436
598
  };
437
599
  }>;
438
600
  /** Pre-defined entity instances to seed on registration */
439
- instances?: Array<Record<string, unknown>>;
601
+ instances?: Array<EntityRow>;
440
602
  };
441
603
  traits: RuntimeTrait[];
442
604
  }
@@ -471,18 +633,26 @@ interface RuntimeTrait {
471
633
  listens?: Array<{
472
634
  event: string;
473
635
  triggers: string;
474
- payloadMapping?: Record<string, unknown>;
636
+ payloadMapping?: EventPayload;
475
637
  }>;
476
638
  emits?: string[];
477
639
  /** Scheduled ticks for this trait */
478
640
  ticks?: RuntimeTraitTick[];
479
641
  }
642
+ /**
643
+ * Registered orbital with runtime state
644
+ */
645
+ interface RegisteredOrbital {
646
+ schema: RuntimeOrbital;
647
+ manager: StateMachineManager;
648
+ entityData: Map<string, EntityRow>;
649
+ }
480
650
  /**
481
651
  * Event sent from client to server
482
652
  */
483
653
  interface OrbitalEventRequest {
484
654
  event: string;
485
- payload?: Record<string, unknown>;
655
+ payload?: EventPayload;
486
656
  entityId?: string;
487
657
  /** User context for @user bindings (from Firebase auth) */
488
658
  user?: {
@@ -504,7 +674,9 @@ interface OrbitalEventResponse {
504
674
  payload?: unknown;
505
675
  }>;
506
676
  /** Entity data fetched by `fetch` effects - keyed by entity type */
507
- data?: Record<string, Record<string, unknown> | Record<string, unknown>[]>;
677
+ data?: {
678
+ [entityType: string]: EntityRow | EntityRow[];
679
+ };
508
680
  /** Client-side effects to execute (render-ui, navigate, notify) */
509
681
  clientEffects?: Array<unknown>;
510
682
  /** Results from server-side effects (persist, call-service, set) */
@@ -523,7 +695,7 @@ interface EffectResult {
523
695
  /** Entity type affected (for persist/set/ref/deref/swap) */
524
696
  entityType?: string;
525
697
  /** Result data from the effect */
526
- data?: Record<string, unknown>;
698
+ data?: EntityRow;
527
699
  /** Whether the effect succeeded */
528
700
  success: boolean;
529
701
  /** Error message if failed */
@@ -583,16 +755,16 @@ interface OrbitalServerRuntimeConfig {
583
755
  * Adapter for persisting entity data
584
756
  */
585
757
  interface PersistenceAdapter {
586
- create(entityType: string, data: Record<string, unknown>): Promise<{
758
+ create(entityType: string, data: EntityRow): Promise<{
587
759
  id: string;
588
760
  }>;
589
- update(entityType: string, id: string, data: Record<string, unknown>): Promise<void>;
761
+ update(entityType: string, id: string, data: EntityRow): Promise<void>;
590
762
  delete(entityType: string, id: string): Promise<void>;
591
- getById(entityType: string, id: string): Promise<Record<string, unknown> | null>;
592
- list(entityType: string): Promise<Array<Record<string, unknown>>>;
763
+ getById(entityType: string, id: string): Promise<EntityRow | null>;
764
+ list(entityType: string): Promise<Array<EntityRow>>;
593
765
  }
594
766
  declare class OrbitalServerRuntime {
595
- private orbitals;
767
+ protected orbitals: Map<string, RegisteredOrbital>;
596
768
  private eventBus;
597
769
  private config;
598
770
  private persistence;
@@ -782,4 +954,4 @@ declare class OrbitalServerRuntime {
782
954
  */
783
955
  declare function createOrbitalServerRuntime(config?: OrbitalServerRuntimeConfig): OrbitalServerRuntime;
784
956
 
785
- export { type EntitySharingMap as E, type LoaderConfig as L, type OrbitalEventRequest as O, type PersistenceAdapter as P, type RuntimeOrbital as R, EventBus as a, type EventNamespaceMap as b, type OrbitalEventResponse as c, type OrbitalServerRuntimeConfig as d, type PreprocessOptions as e, type PreprocessResult as f, type PreprocessedSchema as g, type RuntimeOrbitalSchema as h, type RuntimeTrait as i, getIsolatedCollectionName as j, getNamespacedEvent as k, isNamespacedEvent as l, preprocessSchema as m, type EffectResult as n, OrbitalServerRuntime as o, parseNamespacedEvent as p, type RuntimeTraitTick as q, createOrbitalServerRuntime as r };
957
+ export { type EntitySharingMap as E, type LoaderConfig as L, type OrbitalEventRequest as O, type PersistenceAdapter as P, type RegisteredOrbital as R, StateMachineManager as S, EventBus as a, type EventNamespaceMap as b, type OrbitalEventResponse as c, type OrbitalServerRuntimeConfig as d, type PreprocessOptions as e, type PreprocessResult as f, type PreprocessedSchema as g, type ProcessEventOptions as h, type RuntimeOrbital as i, type RuntimeOrbitalSchema as j, type RuntimeTrait as k, createInitialTraitState as l, findInitialState as m, findTransition as n, getIsolatedCollectionName as o, getNamespacedEvent as p, isNamespacedEvent as q, normalizeEventKey as r, parseNamespacedEvent as s, preprocessSchema as t, processEvent as u, type EffectResult as v, OrbitalServerRuntime as w, type RuntimeTraitTick as x, createOrbitalServerRuntime as y };
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- export { n as EffectResult, L as LoaderConfig, O as OrbitalEventRequest, c as OrbitalEventResponse, o as OrbitalServerRuntime, d as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RuntimeOrbital, h as RuntimeOrbitalSchema, i as RuntimeTrait, q as RuntimeTraitTick, r as createOrbitalServerRuntime } from './OrbitalServerRuntime-Bel-jQ_o.js';
3
- import './types-BrbvZxzX.js';
2
+ export { v as EffectResult, L as LoaderConfig, O as OrbitalEventRequest, c as OrbitalEventResponse, w as OrbitalServerRuntime, d as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, i as RuntimeOrbital, j as RuntimeOrbitalSchema, k as RuntimeTrait, x as RuntimeTraitTick, y as createOrbitalServerRuntime } from './OrbitalServerRuntime-B-QeKtKd.js';
3
+ import './types-DYcUvi4H.js';
4
4
  import '@almadar/core';
@@ -1,4 +1,4 @@
1
- import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-ICTFAD3I.js';
1
+ import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-ESNML4B4.js';
2
2
  import { Router } from 'express';
3
3
  import { evaluateGuard, evaluate } from '@almadar/evaluator';
4
4
  import { faker } from '@faker-js/faker';
@@ -146,8 +146,9 @@ var MockPersistenceAdapter = class {
146
146
  return null;
147
147
  // Relations need special handling
148
148
  case "array":
149
+ return [];
149
150
  case "object":
150
- return field.type === "array" ? [] : {};
151
+ return null;
151
152
  default:
152
153
  return this.generateStringValue(entityName, field, index);
153
154
  }
@@ -1727,15 +1728,17 @@ var OrbitalServerRuntime = class {
1727
1728
  const populatedFieldName = includeField.endsWith("Id") ? includeField.slice(0, -2) : includeField;
1728
1729
  for (const entity of entities) {
1729
1730
  const fkValue = entity[foreignKeyField];
1731
+ const entityRecord = entity;
1730
1732
  if (cardinality === "one" || cardinality === "many-to-one") {
1731
1733
  if (typeof fkValue === "string" && relatedEntities.has(fkValue)) {
1732
- entity[populatedFieldName] = relatedEntities.get(fkValue);
1734
+ entityRecord[populatedFieldName] = relatedEntities.get(fkValue);
1733
1735
  }
1734
1736
  } else {
1735
1737
  if (Array.isArray(fkValue)) {
1736
- entity[populatedFieldName] = fkValue.filter((id) => typeof id === "string").map((id) => relatedEntities.get(id)).filter(Boolean);
1738
+ const fkIds = fkValue.filter((id) => typeof id === "string");
1739
+ entityRecord[populatedFieldName] = fkIds.map((id) => relatedEntities.get(id)).filter(Boolean);
1737
1740
  } else if (typeof fkValue === "string" && relatedEntities.has(fkValue)) {
1738
- entity[populatedFieldName] = [relatedEntities.get(fkValue)];
1741
+ entityRecord[populatedFieldName] = [relatedEntities.get(fkValue)];
1739
1742
  }
1740
1743
  }
1741
1744
  }
@@ -1802,10 +1805,8 @@ var OrbitalServerRuntime = class {
1802
1805
  const orbitalName = req.params.orbital;
1803
1806
  const firebaseUser = req.firebaseUser;
1804
1807
  const user = firebaseUser ? {
1805
- uid: firebaseUser.uid,
1806
- email: firebaseUser.email,
1807
- displayName: firebaseUser.name,
1808
- ...firebaseUser
1808
+ ...firebaseUser,
1809
+ displayName: firebaseUser.name ?? firebaseUser.displayName
1809
1810
  } : void 0;
1810
1811
  const result = await this.processOrbitalEvent(orbitalName, {
1811
1812
  ...req.body,