@almadar/runtime 1.2.3 → 2.0.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/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { B as BindingContext, T as TraitState, a as TraitDefinition, b as TransitionResult, E as EffectHandlers, c as EffectContext } from './types-LiBPiu-u.js';
2
- export { d as Effect, e as EventListener, I as IEventBus, R as RuntimeEvent, U as Unsubscribe } from './types-LiBPiu-u.js';
3
- export { E as EntitySharingMap, a as EventBus, b as EventNamespaceMap, O as OrbitalEventRequest, c as OrbitalEventResponse, d as OrbitalServerRuntimeConfig, P as PersistenceAdapter, e as PreprocessOptions, f as PreprocessResult, g as PreprocessedSchema, R as RuntimeOrbital, h as RuntimeOrbitalSchema, i as RuntimeTrait, j as getIsolatedCollectionName, k as getNamespacedEvent, l as isNamespacedEvent, p as parseNamespacedEvent, m as preprocessSchema } from './OrbitalServerRuntime-Bp1YgmI1.js';
1
+ import { B as BindingContext, T as TraitState, a as TraitDefinition, R as RuntimeConfig, b as TransitionObserver, c as TransitionResult, E as EffectHandlers, d as EffectContext, e as ExecutionEnvironment, f as EffectResult } from './types-9hbY6RWC.js';
2
+ export { g as Effect, h as EventListener, H as HANDLER_MANIFEST, I as IEventBus, i as RuntimeEvent, U as Unsubscribe } from './types-9hbY6RWC.js';
3
+ export { E as EntitySharingMap, a as EventBus, b as EventNamespaceMap, O as OrbitalEventRequest, c as OrbitalEventResponse, d as OrbitalServerRuntimeConfig, P as PersistenceAdapter, e as PreprocessOptions, f as PreprocessResult, g as PreprocessedSchema, R as RuntimeOrbital, h as RuntimeOrbitalSchema, i as RuntimeTrait, j as getIsolatedCollectionName, k as getNamespacedEvent, l as isNamespacedEvent, p as parseNamespacedEvent, m as preprocessSchema } from './OrbitalServerRuntime-CNyOL8XD.js';
4
4
  import { EvaluationContext } from '@almadar/evaluator';
5
5
  export { EvaluationContext, createMinimalContext } from '@almadar/evaluator';
6
6
  export { ServerBridgeConfig, ServerBridgeState } from './ServerBridge.js';
@@ -51,8 +51,11 @@ declare function containsBindings(value: unknown): boolean;
51
51
  declare function extractBindings(value: unknown): string[];
52
52
  /**
53
53
  * Create an EvaluationContext from a BindingContext.
54
+ *
55
+ * @param bindings - Binding context with entity, payload, state data
56
+ * @param strictBindings - When true, log warnings for undefined binding paths (RCG-01)
54
57
  */
55
- declare function createContextFromBindings(bindings: BindingContext): EvaluationContext;
58
+ declare function createContextFromBindings(bindings: BindingContext, strictBindings?: boolean): EvaluationContext;
56
59
 
57
60
  /**
58
61
  * StateMachineCore - Platform-Agnostic State Machine Logic
@@ -94,6 +97,16 @@ interface ProcessEventOptions {
94
97
  payload?: Record<string, unknown>;
95
98
  /** Entity data for binding resolution */
96
99
  entityData?: Record<string, unknown>;
100
+ /**
101
+ * Guard evaluation error handling mode. (RCG-02)
102
+ * - "permissive": Guard errors allow the transition (default, backwards-compatible)
103
+ * - "strict": Guard errors block the transition
104
+ */
105
+ guardMode?: "strict" | "permissive";
106
+ /**
107
+ * When true, log warnings when bindings resolve to undefined. (RCG-01)
108
+ */
109
+ strictBindings?: boolean;
97
110
  }
98
111
  /**
99
112
  * Process an event through a trait's state machine.
@@ -145,7 +158,15 @@ declare function processEvent(options: ProcessEventOptions): TransitionResult;
145
158
  declare class StateMachineManager {
146
159
  private traits;
147
160
  private states;
148
- constructor(traits?: TraitDefinition[]);
161
+ private config;
162
+ private observer?;
163
+ constructor(traits?: TraitDefinition[], config?: RuntimeConfig, observer?: TransitionObserver);
164
+ /**
165
+ * Set the transition observer for runtime verification.
166
+ * Wire this to `verificationRegistry.recordTransition()` to enable
167
+ * automatic verification tracking.
168
+ */
169
+ setObserver(observer: TransitionObserver): void;
149
170
  /**
150
171
  * Add a trait to the manager.
151
172
  */
@@ -206,6 +227,8 @@ interface EffectExecutorOptions {
206
227
  context: EffectContext;
207
228
  /** Enable debug logging */
208
229
  debug?: boolean;
230
+ /** When true, log warnings when bindings resolve to undefined (RCG-01) */
231
+ strictBindings?: boolean;
209
232
  }
210
233
  /**
211
234
  * EffectExecutor - Routes effects to handlers.
@@ -238,7 +261,33 @@ declare class EffectExecutor {
238
261
  private bindings;
239
262
  private context;
240
263
  private debug;
264
+ private strictBindings;
241
265
  constructor(options: EffectExecutorOptions);
266
+ /**
267
+ * Validate that all effect types used in a schema have handlers registered.
268
+ * Call this at runtime startup to catch missing handler setup immediately.
269
+ *
270
+ * @param usedEffectTypes - Effect operator names used in the loaded schemas
271
+ * @param environment - Execution environment for context-aware error messages
272
+ * @returns Array of missing handler errors (empty if all handlers are available)
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * const missing = EffectExecutor.validateHandlers(
277
+ * ['persist', 'render-ui', 'fetch'],
278
+ * executor.getRegisteredHandlers(),
279
+ * 'client'
280
+ * );
281
+ * if (missing.length > 0) {
282
+ * console.error('Missing handlers:', missing);
283
+ * }
284
+ * ```
285
+ */
286
+ static validateHandlers(usedEffectTypes: string[], registeredHandlers: string[], environment?: ExecutionEnvironment): string[];
287
+ /**
288
+ * Get list of effect operators that have handlers registered.
289
+ */
290
+ getRegisteredHandlers(): string[];
242
291
  /**
243
292
  * Execute a single effect.
244
293
  */
@@ -251,6 +300,14 @@ declare class EffectExecutor {
251
300
  * Execute multiple effects in parallel.
252
301
  */
253
302
  executeParallel(effects: unknown[]): Promise<void>;
303
+ /**
304
+ * Execute effects and return detailed results for each.
305
+ * Enables compensating transitions by reporting which effects failed.
306
+ *
307
+ * Unlike `executeAll`, this method does NOT throw on effect errors.
308
+ * Instead, it captures errors in the returned `EffectResult[]` array.
309
+ */
310
+ executeWithResults(effects: unknown[]): Promise<EffectResult[]>;
254
311
  private dispatch;
255
312
  private logUnsupported;
256
313
  }
@@ -351,4 +408,68 @@ interface MockPersistenceConfig {
351
408
  debug?: boolean;
352
409
  }
353
410
 
354
- export { BindingContext, type ClientEventBus, type CreateClientEffectHandlersOptions, EffectContext, EffectExecutor, type EffectExecutorOptions, EffectHandlers, type EntityField, type EntitySchema, type MockPersistenceConfig, type ProcessEventOptions, type SlotSetter, StateMachineManager, TraitDefinition, TraitState, TransitionResult, containsBindings, createClientEffectHandlers, createContextFromBindings, createInitialTraitState, createTestExecutor, extractBindings, findInitialState, findTransition, interpolateProps, interpolateValue, normalizeEventKey, processEvent };
411
+ /**
412
+ * PayloadValidator - Cross-Trait Payload Shape Validation (RCG-10)
413
+ *
414
+ * Validates that listener `payloadMapping` references match the emitter's
415
+ * payload field names. Catches mismatches like `@payload.task_id` when the
416
+ * emitter defines `taskId`.
417
+ *
418
+ * @packageDocumentation
419
+ */
420
+
421
+ /**
422
+ * Emit declaration from a trait.
423
+ */
424
+ interface EmitDeclaration {
425
+ event: string;
426
+ payload?: Array<{
427
+ name: string;
428
+ type?: string;
429
+ }>;
430
+ }
431
+ /**
432
+ * Payload validation error.
433
+ */
434
+ interface PayloadMismatch {
435
+ /** Listening trait name */
436
+ listenerTrait: string;
437
+ /** Emitting trait name */
438
+ emitterTrait: string;
439
+ /** Event name */
440
+ event: string;
441
+ /** The payload field referenced in the listener's payloadMapping */
442
+ referencedField: string;
443
+ /** Available fields from the emitter's payload declaration */
444
+ availableFields: string[];
445
+ }
446
+ /**
447
+ * Validate that all listener payloadMapping references match emitter payload fields.
448
+ *
449
+ * @param traits - All trait definitions in the schema
450
+ * @param emits - Emit declarations per trait (traitName → EmitDeclaration[])
451
+ * @returns Array of payload mismatches (empty if all valid)
452
+ *
453
+ * @example
454
+ * ```ts
455
+ * const mismatches = validatePayloadShapes(traits, emitsMap);
456
+ * for (const m of mismatches) {
457
+ * console.warn(
458
+ * `Trait "${m.listenerTrait}" references @payload.${m.referencedField} ` +
459
+ * `for event "${m.event}" but emitter "${m.emitterTrait}" only declares: ` +
460
+ * `${m.availableFields.join(', ')}`
461
+ * );
462
+ * }
463
+ * ```
464
+ */
465
+ declare function validatePayloadShapes(traits: TraitDefinition[], emits: Map<string, EmitDeclaration[]>): PayloadMismatch[];
466
+ /**
467
+ * Build emit declarations map from trait definitions.
468
+ * Extracts emits from transitions that use the `emit` effect.
469
+ *
470
+ * Note: This is a heuristic — it parses emit effects from transitions.
471
+ * For full accuracy, the schema should include explicit `emits` declarations.
472
+ */
473
+ declare function buildEmitsFromTraits(traits: TraitDefinition[], explicitEmits?: Map<string, EmitDeclaration[]>): Map<string, EmitDeclaration[]>;
474
+
475
+ export { BindingContext, type ClientEventBus, type CreateClientEffectHandlersOptions, EffectContext, EffectExecutor, type EffectExecutorOptions, EffectHandlers, EffectResult, type EntityField, type EntitySchema, ExecutionEnvironment, type MockPersistenceConfig, type PayloadMismatch, type ProcessEventOptions, RuntimeConfig, type SlotSetter, StateMachineManager, TraitDefinition, TraitState, TransitionObserver, TransitionResult, buildEmitsFromTraits, containsBindings, createClientEffectHandlers, createContextFromBindings, createInitialTraitState, createTestExecutor, extractBindings, findInitialState, findTransition, interpolateProps, interpolateValue, normalizeEventKey, processEvent, validatePayloadShapes };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { EffectExecutor, EventBus, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createMinimalContext, createTestExecutor, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isNamespacedEvent, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent } from './chunk-QBQSJJF6.js';
1
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createMinimalContext, createTestExecutor, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isNamespacedEvent, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent } from './chunk-EOMQVFFE.js';
2
2
 
3
3
  // src/ClientEffectHandlers.ts
4
4
  function createClientEffectHandlers(options) {
@@ -35,6 +35,83 @@ function createClientEffectHandlers(options) {
35
35
  };
36
36
  }
37
37
 
38
- export { createClientEffectHandlers };
38
+ // src/PayloadValidator.ts
39
+ function validatePayloadShapes(traits, emits) {
40
+ const mismatches = [];
41
+ const emitIndex = /* @__PURE__ */ new Map();
42
+ for (const [traitName, declarations] of emits) {
43
+ for (const decl of declarations) {
44
+ const fields = decl.payload?.map((p) => p.name) ?? [];
45
+ emitIndex.set(decl.event, { traitName, fields });
46
+ }
47
+ }
48
+ for (const trait of traits) {
49
+ if (!trait.listens) continue;
50
+ for (const listener of trait.listens) {
51
+ const emitter = emitIndex.get(listener.event);
52
+ if (!emitter) continue;
53
+ if (!listener.payloadMapping) continue;
54
+ const payloadRefs = extractPayloadReferences(listener.payloadMapping);
55
+ for (const ref of payloadRefs) {
56
+ if (!emitter.fields.includes(ref)) {
57
+ mismatches.push({
58
+ listenerTrait: trait.name,
59
+ emitterTrait: emitter.traitName,
60
+ event: listener.event,
61
+ referencedField: ref,
62
+ availableFields: emitter.fields
63
+ });
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return mismatches;
69
+ }
70
+ function extractPayloadReferences(mapping) {
71
+ const refs = [];
72
+ function collect(value) {
73
+ if (typeof value === "string") {
74
+ const match = value.match(/^@payload\.(\w+)$/);
75
+ if (match) {
76
+ refs.push(match[1]);
77
+ }
78
+ } else if (typeof value === "object" && value !== null) {
79
+ if (Array.isArray(value)) {
80
+ value.forEach(collect);
81
+ } else {
82
+ Object.values(value).forEach(collect);
83
+ }
84
+ }
85
+ }
86
+ Object.values(mapping).forEach(collect);
87
+ return [...new Set(refs)];
88
+ }
89
+ function buildEmitsFromTraits(traits, explicitEmits) {
90
+ const result = new Map(explicitEmits ?? []);
91
+ for (const trait of traits) {
92
+ if (result.has(trait.name)) continue;
93
+ const emitDecls = [];
94
+ for (const transition of trait.transitions) {
95
+ if (!transition.effects) continue;
96
+ for (const effect of transition.effects) {
97
+ if (!Array.isArray(effect)) continue;
98
+ if (effect[0] === "emit" && typeof effect[1] === "string") {
99
+ const event = effect[1];
100
+ const payloadObj = effect[2];
101
+ const payload = payloadObj ? Object.keys(payloadObj).map((name) => ({ name })) : void 0;
102
+ if (!emitDecls.some((d) => d.event === event)) {
103
+ emitDecls.push({ event, payload });
104
+ }
105
+ }
106
+ }
107
+ }
108
+ if (emitDecls.length > 0) {
109
+ result.set(trait.name, emitDecls);
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+
115
+ export { buildEmitsFromTraits, createClientEffectHandlers, validatePayloadShapes };
39
116
  //# sourceMappingURL=index.js.map
40
117
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ClientEffectHandlers.ts"],"names":[],"mappings":";;;AAwEO,SAAS,2BACZ,OAAA,EACc;AACd,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAY,QAAA,EAAU,MAAA,EAAQ,eAAc,GAAI,OAAA;AAElE,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,CAAC,KAAA,EAAe,OAAA,KAAsC;AACxD,MAAA,MAAM,gBAAgB,KAAA,CAAM,UAAA,CAAW,KAAK,CAAA,GAAI,KAAA,GAAQ,MAAM,KAAK,CAAA,CAAA;AACnE,MAAA,QAAA,CAAS,IAAA,CAAK,aAAA,EAAe,EAAE,OAAA,EAAS,CAAA;AAAA,IAC5C,CAAA;AAAA,IAEA,SAAS,YAAY;AACjB,MAAA,OAAA,CAAQ,KAAK,uEAAuE,CAAA;AAAA,IACxF,CAAA;AAAA,IAEA,KAAK,MAAM;AACP,MAAA,OAAA,CAAQ,KAAK,mEAAmE,CAAA;AAAA,IACpF,CAAA;AAAA,IAEA,aAAa,YAAY;AACrB,MAAA,OAAA,CAAQ,KAAK,2EAA2E,CAAA;AACxF,MAAA,OAAO,EAAC;AAAA,IACZ,CAAA;AAAA,IAEA,QAAA,EAAU,CAAC,IAAA,EAAc,OAAA,EAAkB,KAAA,KAAoC;AAC3E,MAAA,IAAI,YAAY,IAAA,EAAM;AAClB,QAAA,UAAA,CAAW,UAAU,IAAI,CAAA;AACzB,QAAA;AAAA,MACJ;AACA,MAAA,MAAM,QAAA,GAAW,aAAA,GAAgB,aAAA,CAAc,OAAO,CAAA,GAAI,OAAA;AAC1D,MAAA,UAAA,CAAW,UAAA,CAAW,IAAA,EAAM,QAAA,EAAU,KAAK,CAAA;AAAA,IAC/C,CAAA;AAAA,IAEA,QAAA,EAAU,QAAA,KAAa,CAAC,IAAA,KAAiB;AACrC,MAAA,OAAA,CAAQ,IAAA,CAAK,yDAAyD,IAAI,CAAA;AAAA,IAC9E,CAAA,CAAA;AAAA,IAEA,MAAA,EAAQ,MAAA,KAAW,CAAC,GAAA,EAAa,IAAA,KAAkB;AAC/C,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,+BAAA,EAAkC,IAAI,CAAA,EAAA,CAAA,EAAM,GAAG,CAAA;AAAA,IAC/D,CAAA;AAAA,GACJ;AACJ","file":"index.js","sourcesContent":["/**\n * Client Effect Handlers Factory\n *\n * Creates the standard effect handler set for client-side trait execution.\n * Platform-agnostic — works with any UI framework that provides the required interfaces.\n *\n * @packageDocumentation\n */\n\nimport type { EffectHandlers } from './types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Minimal event bus interface required by the factory.\n */\nexport interface ClientEventBus {\n emit: (type: string, payload?: Record<string, unknown>) => void;\n}\n\n/**\n * Slot setter interface for render-ui effects.\n * The factory doesn't know about React state — it just calls this function.\n */\nexport interface SlotSetter {\n /** Accumulate a pattern into the pending slot map */\n addPattern: (slot: string, pattern: unknown, props?: Record<string, unknown>) => void;\n /** Mark a slot for clearing */\n clearSlot: (slot: string) => void;\n}\n\n/**\n * Options for creating client effect handlers.\n */\nexport interface CreateClientEffectHandlersOptions {\n /** Event bus for emit effects */\n eventBus: ClientEventBus;\n /** Slot setter for render-ui effects */\n slotSetter: SlotSetter;\n /** Navigate function for navigate effects */\n navigate?: (path: string, params?: Record<string, unknown>) => void;\n /** Notify function for notification effects */\n notify?: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void;\n /** Entity enrichment: inject linkedEntity into entity-aware patterns */\n enrichPattern?: (pattern: unknown) => unknown;\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create client-side effect handlers for trait state machine execution.\n *\n * Client handles: emit, renderUI, navigate, notify\n * Server handles: persist, set, callService (logged as warnings on client)\n *\n * @example\n * ```ts\n * const handlers = createClientEffectHandlers({\n * eventBus,\n * slotSetter: {\n * addPattern: (slot, pattern, props) => pendingSlots.get(slot)?.push({ pattern, props }),\n * clearSlot: (slot) => pendingSlots.set(slot, []),\n * },\n * navigate: (path) => router.push(path),\n * notify: (msg, type) => toast[type](msg),\n * });\n * ```\n */\nexport function createClientEffectHandlers(\n options: CreateClientEffectHandlersOptions\n): EffectHandlers {\n const { eventBus, slotSetter, navigate, notify, enrichPattern } = options;\n\n return {\n emit: (event: string, payload?: Record<string, unknown>) => {\n const prefixedEvent = event.startsWith('UI:') ? event : `UI:${event}`;\n eventBus.emit(prefixedEvent, { payload });\n },\n\n persist: async () => {\n console.warn('[ClientEffectHandlers] persist is server-side only, ignored on client');\n },\n\n set: () => {\n console.warn('[ClientEffectHandlers] set is server-side only, ignored on client');\n },\n\n callService: async () => {\n console.warn('[ClientEffectHandlers] callService is server-side only, ignored on client');\n return {};\n },\n\n renderUI: (slot: string, pattern: unknown, props?: Record<string, unknown>) => {\n if (pattern === null) {\n slotSetter.clearSlot(slot);\n return;\n }\n const enriched = enrichPattern ? enrichPattern(pattern) : pattern;\n slotSetter.addPattern(slot, enriched, props);\n },\n\n navigate: navigate ?? ((path: string) => {\n console.warn('[ClientEffectHandlers] No navigate handler, ignoring:', path);\n }),\n\n notify: notify ?? ((msg: string, type?: string) => {\n console.log(`[ClientEffectHandlers] notify (${type}):`, msg);\n }),\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/ClientEffectHandlers.ts","../src/PayloadValidator.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Client Effect Handlers Factory\n *\n * Creates the standard effect handler set for client-side trait execution.\n * Platform-agnostic — works with any UI framework that provides the required interfaces.\n *\n * @packageDocumentation\n */\n\nimport type { EffectHandlers } from './types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Minimal event bus interface required by the factory.\n */\nexport interface ClientEventBus {\n emit: (type: string, payload?: Record<string, unknown>) => void;\n}\n\n/**\n * Slot setter interface for render-ui effects.\n * The factory doesn't know about React state — it just calls this function.\n */\nexport interface SlotSetter {\n /** Accumulate a pattern into the pending slot map */\n addPattern: (slot: string, pattern: unknown, props?: Record<string, unknown>) => void;\n /** Mark a slot for clearing */\n clearSlot: (slot: string) => void;\n}\n\n/**\n * Options for creating client effect handlers.\n */\nexport interface CreateClientEffectHandlersOptions {\n /** Event bus for emit effects */\n eventBus: ClientEventBus;\n /** Slot setter for render-ui effects */\n slotSetter: SlotSetter;\n /** Navigate function for navigate effects */\n navigate?: (path: string, params?: Record<string, unknown>) => void;\n /** Notify function for notification effects */\n notify?: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void;\n /** Entity enrichment: inject linkedEntity into entity-aware patterns */\n enrichPattern?: (pattern: unknown) => unknown;\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create client-side effect handlers for trait state machine execution.\n *\n * Client handles: emit, renderUI, navigate, notify\n * Server handles: persist, set, callService (logged as warnings on client)\n *\n * @example\n * ```ts\n * const handlers = createClientEffectHandlers({\n * eventBus,\n * slotSetter: {\n * addPattern: (slot, pattern, props) => pendingSlots.get(slot)?.push({ pattern, props }),\n * clearSlot: (slot) => pendingSlots.set(slot, []),\n * },\n * navigate: (path) => router.push(path),\n * notify: (msg, type) => toast[type](msg),\n * });\n * ```\n */\nexport function createClientEffectHandlers(\n options: CreateClientEffectHandlersOptions\n): EffectHandlers {\n const { eventBus, slotSetter, navigate, notify, enrichPattern } = options;\n\n return {\n emit: (event: string, payload?: Record<string, unknown>) => {\n const prefixedEvent = event.startsWith('UI:') ? event : `UI:${event}`;\n eventBus.emit(prefixedEvent, { payload });\n },\n\n persist: async () => {\n console.warn('[ClientEffectHandlers] persist is server-side only, ignored on client');\n },\n\n set: () => {\n console.warn('[ClientEffectHandlers] set is server-side only, ignored on client');\n },\n\n callService: async () => {\n console.warn('[ClientEffectHandlers] callService is server-side only, ignored on client');\n return {};\n },\n\n renderUI: (slot: string, pattern: unknown, props?: Record<string, unknown>) => {\n if (pattern === null) {\n slotSetter.clearSlot(slot);\n return;\n }\n const enriched = enrichPattern ? enrichPattern(pattern) : pattern;\n slotSetter.addPattern(slot, enriched, props);\n },\n\n navigate: navigate ?? ((path: string) => {\n console.warn('[ClientEffectHandlers] No navigate handler, ignoring:', path);\n }),\n\n notify: notify ?? ((msg: string, type?: string) => {\n console.log(`[ClientEffectHandlers] notify (${type}):`, msg);\n }),\n };\n}\n","/**\n * PayloadValidator - Cross-Trait Payload Shape Validation (RCG-10)\n *\n * Validates that listener `payloadMapping` references match the emitter's\n * payload field names. Catches mismatches like `@payload.task_id` when the\n * emitter defines `taskId`.\n *\n * @packageDocumentation\n */\n\nimport type { TraitDefinition } from './types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Emit declaration from a trait.\n */\ninterface EmitDeclaration {\n event: string;\n payload?: Array<{ name: string; type?: string }>;\n}\n\n/**\n * Payload validation error.\n */\nexport interface PayloadMismatch {\n /** Listening trait name */\n listenerTrait: string;\n /** Emitting trait name */\n emitterTrait: string;\n /** Event name */\n event: string;\n /** The payload field referenced in the listener's payloadMapping */\n referencedField: string;\n /** Available fields from the emitter's payload declaration */\n availableFields: string[];\n}\n\n// ============================================================================\n// Validation\n// ============================================================================\n\n/**\n * Validate that all listener payloadMapping references match emitter payload fields.\n *\n * @param traits - All trait definitions in the schema\n * @param emits - Emit declarations per trait (traitName → EmitDeclaration[])\n * @returns Array of payload mismatches (empty if all valid)\n *\n * @example\n * ```ts\n * const mismatches = validatePayloadShapes(traits, emitsMap);\n * for (const m of mismatches) {\n * console.warn(\n * `Trait \"${m.listenerTrait}\" references @payload.${m.referencedField} ` +\n * `for event \"${m.event}\" but emitter \"${m.emitterTrait}\" only declares: ` +\n * `${m.availableFields.join(', ')}`\n * );\n * }\n * ```\n */\nexport function validatePayloadShapes(\n traits: TraitDefinition[],\n emits: Map<string, EmitDeclaration[]>\n): PayloadMismatch[] {\n const mismatches: PayloadMismatch[] = [];\n\n // Build event→emitter lookup: event name → { traitName, payload fields }\n const emitIndex = new Map<string, { traitName: string; fields: string[] }>();\n for (const [traitName, declarations] of emits) {\n for (const decl of declarations) {\n const fields = decl.payload?.map((p) => p.name) ?? [];\n emitIndex.set(decl.event, { traitName, fields });\n }\n }\n\n // Check each listener's payloadMapping references\n for (const trait of traits) {\n if (!trait.listens) continue;\n\n for (const listener of trait.listens) {\n const emitter = emitIndex.get(listener.event);\n if (!emitter) continue; // No emitter found — separate validation concern\n\n if (!listener.payloadMapping) continue;\n\n // Extract @payload.X references from payloadMapping values\n const payloadRefs = extractPayloadReferences(listener.payloadMapping);\n\n for (const ref of payloadRefs) {\n if (!emitter.fields.includes(ref)) {\n mismatches.push({\n listenerTrait: trait.name,\n emitterTrait: emitter.traitName,\n event: listener.event,\n referencedField: ref,\n availableFields: emitter.fields,\n });\n }\n }\n }\n }\n\n return mismatches;\n}\n\n/**\n * Extract payload field references from a payloadMapping object.\n * Finds all `@payload.fieldName` patterns and returns the field names.\n */\nfunction extractPayloadReferences(mapping: Record<string, unknown>): string[] {\n const refs: string[] = [];\n\n function collect(value: unknown): void {\n if (typeof value === 'string') {\n const match = value.match(/^@payload\\.(\\w+)$/);\n if (match) {\n refs.push(match[1]);\n }\n } else if (typeof value === 'object' && value !== null) {\n if (Array.isArray(value)) {\n value.forEach(collect);\n } else {\n Object.values(value as Record<string, unknown>).forEach(collect);\n }\n }\n }\n\n Object.values(mapping).forEach(collect);\n return [...new Set(refs)];\n}\n\n/**\n * Build emit declarations map from trait definitions.\n * Extracts emits from transitions that use the `emit` effect.\n *\n * Note: This is a heuristic — it parses emit effects from transitions.\n * For full accuracy, the schema should include explicit `emits` declarations.\n */\nexport function buildEmitsFromTraits(\n traits: TraitDefinition[],\n explicitEmits?: Map<string, EmitDeclaration[]>\n): Map<string, EmitDeclaration[]> {\n // Start with explicit emits if provided\n const result = new Map<string, EmitDeclaration[]>(explicitEmits ?? []);\n\n for (const trait of traits) {\n if (result.has(trait.name)) continue; // Explicit declarations take precedence\n\n const emitDecls: EmitDeclaration[] = [];\n for (const transition of trait.transitions) {\n if (!transition.effects) continue;\n\n for (const effect of transition.effects) {\n if (!Array.isArray(effect)) continue;\n if (effect[0] === 'emit' && typeof effect[1] === 'string') {\n const event = effect[1] as string;\n // Payload is in effect[2] if present\n const payloadObj = effect[2] as Record<string, unknown> | undefined;\n const payload = payloadObj\n ? Object.keys(payloadObj).map((name) => ({ name }))\n : undefined;\n\n // Avoid duplicates\n if (!emitDecls.some((d) => d.event === event)) {\n emitDecls.push({ event, payload });\n }\n }\n }\n }\n\n if (emitDecls.length > 0) {\n result.set(trait.name, emitDecls);\n }\n }\n\n return result;\n}\n"]}
@@ -93,6 +93,11 @@ interface TraitDefinition {
93
93
  event: string;
94
94
  guard?: unknown;
95
95
  effects?: unknown[];
96
+ /** Compensating transition when effects fail (RCG-04) */
97
+ onEffectError?: {
98
+ to: string;
99
+ effects?: unknown[];
100
+ };
96
101
  }>;
97
102
  /** Cross-trait event listeners (optional) */
98
103
  listens?: Array<{
@@ -110,8 +115,8 @@ interface TraitDefinition {
110
115
  interface EffectHandlers {
111
116
  /** Emit an event to the event bus */
112
117
  emit: (event: string, payload?: Record<string, unknown>) => void;
113
- /** Persist data (create/update/delete) */
114
- persist: (action: 'create' | 'update' | 'delete', entityType: string, data?: Record<string, unknown>) => Promise<void>;
118
+ /** Persist data (create/update/delete/batch) */
119
+ persist: (action: 'create' | 'update' | 'delete' | 'batch', entityType: string, data?: Record<string, unknown>) => Promise<void>;
115
120
  /** Set a field value on an entity */
116
121
  set: (entityId: string, field: string, value: unknown) => void;
117
122
  /** Call an external service */
@@ -173,5 +178,84 @@ interface EffectContext {
173
178
  /** Entity ID (if available) */
174
179
  entityId?: string;
175
180
  }
181
+ /**
182
+ * Result of executing a single effect, with status tracking.
183
+ */
184
+ interface EffectResult {
185
+ /** Effect operator (e.g., "persist", "render-ui") */
186
+ type: string;
187
+ /** Effect arguments */
188
+ args: unknown[];
189
+ /** Whether the effect executed successfully */
190
+ status: "executed" | "failed" | "skipped";
191
+ /** Error message if failed */
192
+ error?: string;
193
+ /** Execution duration in milliseconds */
194
+ durationMs?: number;
195
+ }
196
+ /**
197
+ * Runtime configuration for strictness and safety modes.
198
+ *
199
+ * These options control how the runtime handles edge cases:
200
+ * - `strictBindings`: Log warnings when bindings resolve to undefined (RCG-01)
201
+ * - `guardMode`: Control whether guard errors block or allow transitions (RCG-02)
202
+ * - `maxEventDepth`: Prevent infinite event loops (RCG-05)
203
+ */
204
+ interface RuntimeConfig {
205
+ /**
206
+ * When true, log warnings when bindings like @entity.field resolve to undefined.
207
+ * Helps detect typos and missing fields early. (RCG-01)
208
+ * @default false
209
+ */
210
+ strictBindings?: boolean;
211
+ /**
212
+ * Guard evaluation error handling mode. (RCG-02)
213
+ * - "permissive": Guard errors allow the transition (current default behavior)
214
+ * - "strict": Guard errors block the transition
215
+ * @default "permissive"
216
+ */
217
+ guardMode?: "strict" | "permissive";
218
+ /**
219
+ * Maximum event emission depth before triggering circuit breaker. (RCG-05)
220
+ * Prevents infinite loops from circular emit/listen chains.
221
+ * @default 10
222
+ */
223
+ maxEventDepth?: number;
224
+ }
225
+ /**
226
+ * Execution context for handler manifest validation.
227
+ * Defines which effect handlers should be available in each environment.
228
+ */
229
+ type ExecutionEnvironment = "client" | "server" | "test" | "ssr";
230
+ /**
231
+ * Observer interface for recording transition and effect traces.
232
+ * Implement this to wire in the verificationRegistry or other monitoring tools.
233
+ *
234
+ * The runtime calls these hooks automatically when transitions execute and
235
+ * effects complete, enabling runtime verification without tight coupling.
236
+ */
237
+ interface TransitionObserver {
238
+ /**
239
+ * Called after a transition is processed (whether or not it executed).
240
+ */
241
+ onTransition(trace: {
242
+ traitName: string;
243
+ from: string;
244
+ to: string;
245
+ event: string;
246
+ guardResult?: boolean;
247
+ effects: Array<{
248
+ type: string;
249
+ args: unknown[];
250
+ status: "executed" | "failed" | "skipped";
251
+ error?: string;
252
+ durationMs?: number;
253
+ }>;
254
+ }): void;
255
+ }
256
+ /**
257
+ * Maps execution environments to their available effect handlers.
258
+ */
259
+ declare const HANDLER_MANIFEST: Record<ExecutionEnvironment, string[]>;
176
260
 
177
- export type { BindingContext as B, EffectHandlers as E, IEventBus as I, RuntimeEvent as R, TraitState as T, Unsubscribe as U, TraitDefinition as a, TransitionResult as b, EffectContext as c, Effect as d, EventListener as e };
261
+ export { type BindingContext as B, type EffectHandlers as E, HANDLER_MANIFEST as H, type IEventBus as I, type RuntimeConfig as R, type TraitState as T, type Unsubscribe as U, type TraitDefinition as a, type TransitionObserver as b, type TransitionResult as c, type EffectContext as d, type ExecutionEnvironment as e, type EffectResult as f, type Effect as g, type EventListener as h, type RuntimeEvent as i };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "1.2.3",
3
+ "version": "2.0.0",
4
4
  "description": "Interpreted runtime for Almadar orbital applications (OrbitalServerRuntime)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,9 +27,9 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@faker-js/faker": "^9.3.0",
31
- "@almadar/evaluator": "1.0.16",
32
- "@almadar/core": "1.0.18"
30
+ "@almadar/evaluator": ">=2.0.0",
31
+ "@almadar/core": ">=2.0.0",
32
+ "@faker-js/faker": "^9.3.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "express": "^4.0.0"
@@ -45,7 +45,7 @@
45
45
  "repository": {
46
46
  "type": "git",
47
47
  "url": "https://github.com/almadar-io/almadar.git",
48
- "directory": "packages/almadar-runtime"
48
+ "directory": "docs/packages/runtime"
49
49
  },
50
50
  "license": "MIT",
51
51
  "keywords": [
@@ -54,6 +54,7 @@
54
54
  "orbital",
55
55
  "interpreter"
56
56
  ],
57
+ "homepage": "https://github.com/almadar-io/almadar#readme",
57
58
  "scripts": {
58
59
  "build": "tsup",
59
60
  "build:watch": "tsup --watch",