xcomponent-ai 0.1.1

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.
Files changed (49) hide show
  1. package/CONTRIBUTING.md +195 -0
  2. package/LICENSE +45 -0
  3. package/PERSISTENCE.md +774 -0
  4. package/README.md +594 -0
  5. package/dist/agents.d.ts +81 -0
  6. package/dist/agents.d.ts.map +1 -0
  7. package/dist/agents.js +405 -0
  8. package/dist/agents.js.map +1 -0
  9. package/dist/api.d.ts +36 -0
  10. package/dist/api.d.ts.map +1 -0
  11. package/dist/api.js +404 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/cli.d.ts +7 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +437 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/component-registry.d.ts +190 -0
  18. package/dist/component-registry.d.ts.map +1 -0
  19. package/dist/component-registry.js +382 -0
  20. package/dist/component-registry.js.map +1 -0
  21. package/dist/fsm-runtime.d.ts +263 -0
  22. package/dist/fsm-runtime.d.ts.map +1 -0
  23. package/dist/fsm-runtime.js +1122 -0
  24. package/dist/fsm-runtime.js.map +1 -0
  25. package/dist/index.d.ts +23 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +56 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/monitoring.d.ts +68 -0
  30. package/dist/monitoring.d.ts.map +1 -0
  31. package/dist/monitoring.js +176 -0
  32. package/dist/monitoring.js.map +1 -0
  33. package/dist/persistence.d.ts +100 -0
  34. package/dist/persistence.d.ts.map +1 -0
  35. package/dist/persistence.js +270 -0
  36. package/dist/persistence.js.map +1 -0
  37. package/dist/timer-wheel.d.ts +85 -0
  38. package/dist/timer-wheel.d.ts.map +1 -0
  39. package/dist/timer-wheel.js +181 -0
  40. package/dist/timer-wheel.js.map +1 -0
  41. package/dist/types.d.ts +404 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +40 -0
  44. package/dist/types.js.map +1 -0
  45. package/dist/websockets.d.ts +32 -0
  46. package/dist/websockets.d.ts.map +1 -0
  47. package/dist/websockets.js +117 -0
  48. package/dist/websockets.js.map +1 -0
  49. package/package.json +103 -0
package/PERSISTENCE.md ADDED
@@ -0,0 +1,774 @@
1
+ # Persistence and Event Sourcing
2
+
3
+ This document covers how to configure and use persistence in xcomponent-ai for event sourcing, snapshots, and production deployments.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Configuration](#configuration)
9
+ - [In-Memory Storage](#in-memory-storage)
10
+ - [Custom Storage Implementations](#custom-storage-implementations)
11
+ - [EventStore Interface](#eventstore-interface)
12
+ - [SnapshotStore Interface](#snapshotstore-interface)
13
+ - [PostgreSQL Implementation](#postgresql-implementation)
14
+ - [MongoDB Implementation](#mongodb-implementation)
15
+ - [Cross-Component Traceability](#cross-component-traceability)
16
+ - [Best Practices](#best-practices)
17
+
18
+ ## Overview
19
+
20
+ xcomponent-ai supports event sourcing and state snapshots for:
21
+
22
+ - **Event Sourcing**: Full audit trail of all state transitions
23
+ - **Snapshots**: Fast state restoration without replaying all events
24
+ - **Cross-Component Traceability**: Trace events across component boundaries
25
+ - **Disaster Recovery**: Reconstruct system state from persisted events
26
+ - **Debugging**: Analyze event causality chains
27
+
28
+ ## Configuration
29
+
30
+ Enable persistence when creating an FSMRuntime:
31
+
32
+ ```typescript
33
+ import { FSMRuntime } from './fsm-runtime';
34
+ import { InMemoryEventStore, InMemorySnapshotStore } from './persistence';
35
+
36
+ const runtime = new FSMRuntime(component, {
37
+ // Event sourcing: persist all events
38
+ eventSourcing: true,
39
+
40
+ // Snapshots: periodically save full state
41
+ snapshots: true,
42
+
43
+ // Snapshot interval: save every N transitions (default: 10)
44
+ snapshotInterval: 20,
45
+
46
+ // Custom stores (optional)
47
+ eventStore: new InMemoryEventStore(),
48
+ snapshotStore: new InMemorySnapshotStore(),
49
+ });
50
+ ```
51
+
52
+ ## In-Memory Storage
53
+
54
+ For development and testing, use the built-in in-memory stores:
55
+
56
+ ```typescript
57
+ import { InMemoryEventStore, InMemorySnapshotStore } from './persistence';
58
+
59
+ const eventStore = new InMemoryEventStore();
60
+ const snapshotStore = new InMemorySnapshotStore();
61
+
62
+ const runtime = new FSMRuntime(component, {
63
+ eventSourcing: true,
64
+ snapshots: true,
65
+ eventStore,
66
+ snapshotStore,
67
+ });
68
+ ```
69
+
70
+ **Note**: In-memory stores lose data on restart. Use database-backed stores for production.
71
+
72
+ ## Custom Storage Implementations
73
+
74
+ ### EventStore Interface
75
+
76
+ Implement the `EventStore` interface for custom event storage:
77
+
78
+ ```typescript
79
+ import { EventStore, PersistedEvent } from './types';
80
+
81
+ export interface EventStore {
82
+ /**
83
+ * Append event to store
84
+ */
85
+ append(event: PersistedEvent): Promise<void>;
86
+
87
+ /**
88
+ * Get all events for a specific instance
89
+ */
90
+ getEventsForInstance(instanceId: string): Promise<PersistedEvent[]>;
91
+
92
+ /**
93
+ * Get events within time range
94
+ */
95
+ getEventsByTimeRange(startTime: number, endTime: number): Promise<PersistedEvent[]>;
96
+
97
+ /**
98
+ * Get events caused by a specific event (causality)
99
+ */
100
+ getCausedEvents(eventId: string): Promise<PersistedEvent[]>;
101
+
102
+ /**
103
+ * Get all events (for backup/export)
104
+ */
105
+ getAllEvents(): Promise<PersistedEvent[]>;
106
+ }
107
+ ```
108
+
109
+ ### SnapshotStore Interface
110
+
111
+ Implement the `SnapshotStore` interface for custom snapshot storage:
112
+
113
+ ```typescript
114
+ import { SnapshotStore, InstanceSnapshot } from './types';
115
+
116
+ export interface SnapshotStore {
117
+ /**
118
+ * Save instance snapshot
119
+ */
120
+ saveSnapshot(snapshot: InstanceSnapshot): Promise<void>;
121
+
122
+ /**
123
+ * Get latest snapshot for instance
124
+ */
125
+ getSnapshot(instanceId: string): Promise<InstanceSnapshot | null>;
126
+
127
+ /**
128
+ * Get all snapshots (for backup/export)
129
+ */
130
+ getAllSnapshots(): Promise<InstanceSnapshot[]>;
131
+
132
+ /**
133
+ * Delete snapshot
134
+ */
135
+ deleteSnapshot(instanceId: string): Promise<void>;
136
+ }
137
+ ```
138
+
139
+ ## PostgreSQL Implementation
140
+
141
+ Example PostgreSQL-backed persistence:
142
+
143
+ ### Schema
144
+
145
+ ```sql
146
+ -- Events table
147
+ CREATE TABLE fsm_events (
148
+ id VARCHAR(50) PRIMARY KEY,
149
+ instance_id VARCHAR(50) NOT NULL,
150
+ machine_name VARCHAR(100) NOT NULL,
151
+ component_name VARCHAR(100) NOT NULL,
152
+ event_type VARCHAR(50) NOT NULL,
153
+ event_payload JSONB NOT NULL,
154
+ state_before VARCHAR(50) NOT NULL,
155
+ state_after VARCHAR(50) NOT NULL,
156
+ persisted_at BIGINT NOT NULL,
157
+ caused_by VARCHAR(50)[],
158
+ caused VARCHAR(50)[],
159
+ source_component_name VARCHAR(100),
160
+ target_component_name VARCHAR(100),
161
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
162
+ );
163
+
164
+ -- Indexes for performance
165
+ CREATE INDEX idx_events_instance ON fsm_events(instance_id);
166
+ CREATE INDEX idx_events_component ON fsm_events(component_name);
167
+ CREATE INDEX idx_events_machine ON fsm_events(machine_name);
168
+ CREATE INDEX idx_events_timestamp ON fsm_events(persisted_at);
169
+ CREATE INDEX idx_events_caused_by ON fsm_events USING GIN(caused_by);
170
+
171
+ -- Snapshots table
172
+ CREATE TABLE fsm_snapshots (
173
+ instance_id VARCHAR(50) PRIMARY KEY,
174
+ machine_name VARCHAR(100) NOT NULL,
175
+ current_state VARCHAR(50) NOT NULL,
176
+ status VARCHAR(20) NOT NULL,
177
+ context JSONB NOT NULL,
178
+ public_member JSONB,
179
+ snapshot_at BIGINT NOT NULL,
180
+ last_event_id VARCHAR(50) NOT NULL,
181
+ pending_timeouts JSONB,
182
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
183
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
184
+ );
185
+ ```
186
+
187
+ ### Implementation
188
+
189
+ ```typescript
190
+ import { Pool } from 'pg';
191
+ import { EventStore, PersistedEvent } from './types';
192
+
193
+ export class PostgreSQLEventStore implements EventStore {
194
+ private pool: Pool;
195
+
196
+ constructor(connectionString: string) {
197
+ this.pool = new Pool({ connectionString });
198
+ }
199
+
200
+ async append(event: PersistedEvent): Promise<void> {
201
+ const query = `
202
+ INSERT INTO fsm_events (
203
+ id, instance_id, machine_name, component_name,
204
+ event_type, event_payload, state_before, state_after,
205
+ persisted_at, caused_by, caused,
206
+ source_component_name, target_component_name
207
+ )
208
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
209
+ `;
210
+
211
+ await this.pool.query(query, [
212
+ event.id,
213
+ event.instanceId,
214
+ event.machineName,
215
+ event.componentName,
216
+ event.event.type,
217
+ JSON.stringify(event.event.payload),
218
+ event.stateBefore,
219
+ event.stateAfter,
220
+ event.persistedAt,
221
+ event.causedBy || [],
222
+ event.caused || [],
223
+ event.sourceComponentName || null,
224
+ event.targetComponentName || null,
225
+ ]);
226
+ }
227
+
228
+ async getEventsForInstance(instanceId: string): Promise<PersistedEvent[]> {
229
+ const query = `
230
+ SELECT * FROM fsm_events
231
+ WHERE instance_id = $1
232
+ ORDER BY persisted_at ASC
233
+ `;
234
+
235
+ const result = await this.pool.query(query, [instanceId]);
236
+ return result.rows.map(row => this.rowToEvent(row));
237
+ }
238
+
239
+ async getEventsByTimeRange(startTime: number, endTime: number): Promise<PersistedEvent[]> {
240
+ const query = `
241
+ SELECT * FROM fsm_events
242
+ WHERE persisted_at >= $1 AND persisted_at <= $2
243
+ ORDER BY persisted_at ASC
244
+ `;
245
+
246
+ const result = await this.pool.query(query, [startTime, endTime]);
247
+ return result.rows.map(row => this.rowToEvent(row));
248
+ }
249
+
250
+ async getCausedEvents(eventId: string): Promise<PersistedEvent[]> {
251
+ const query = `
252
+ SELECT * FROM fsm_events
253
+ WHERE $1 = ANY(caused_by)
254
+ ORDER BY persisted_at ASC
255
+ `;
256
+
257
+ const result = await this.pool.query(query, [eventId]);
258
+ return result.rows.map(row => this.rowToEvent(row));
259
+ }
260
+
261
+ async getAllEvents(): Promise<PersistedEvent[]> {
262
+ const query = `
263
+ SELECT * FROM fsm_events
264
+ ORDER BY persisted_at ASC
265
+ `;
266
+
267
+ const result = await this.pool.query(query);
268
+ return result.rows.map(row => this.rowToEvent(row));
269
+ }
270
+
271
+ private rowToEvent(row: any): PersistedEvent {
272
+ return {
273
+ id: row.id,
274
+ instanceId: row.instance_id,
275
+ machineName: row.machine_name,
276
+ componentName: row.component_name,
277
+ event: {
278
+ type: row.event_type,
279
+ payload: JSON.parse(row.event_payload),
280
+ timestamp: row.persisted_at,
281
+ },
282
+ stateBefore: row.state_before,
283
+ stateAfter: row.state_after,
284
+ persistedAt: row.persisted_at,
285
+ causedBy: row.caused_by,
286
+ caused: row.caused,
287
+ sourceComponentName: row.source_component_name,
288
+ targetComponentName: row.target_component_name,
289
+ };
290
+ }
291
+
292
+ async close(): Promise<void> {
293
+ await this.pool.end();
294
+ }
295
+ }
296
+
297
+ export class PostgreSQLSnapshotStore implements SnapshotStore {
298
+ private pool: Pool;
299
+
300
+ constructor(connectionString: string) {
301
+ this.pool = new Pool({ connectionString });
302
+ }
303
+
304
+ async saveSnapshot(snapshot: InstanceSnapshot): Promise<void> {
305
+ const query = `
306
+ INSERT INTO fsm_snapshots (
307
+ instance_id, machine_name, current_state, status,
308
+ context, public_member, snapshot_at, last_event_id,
309
+ pending_timeouts, updated_at
310
+ )
311
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP)
312
+ ON CONFLICT (instance_id)
313
+ DO UPDATE SET
314
+ current_state = $3,
315
+ status = $4,
316
+ context = $5,
317
+ public_member = $6,
318
+ snapshot_at = $7,
319
+ last_event_id = $8,
320
+ pending_timeouts = $9,
321
+ updated_at = CURRENT_TIMESTAMP
322
+ `;
323
+
324
+ await this.pool.query(query, [
325
+ snapshot.instance.id,
326
+ snapshot.instance.machineName,
327
+ snapshot.instance.currentState,
328
+ snapshot.instance.status,
329
+ JSON.stringify(snapshot.instance.context),
330
+ snapshot.instance.publicMember ? JSON.stringify(snapshot.instance.publicMember) : null,
331
+ snapshot.snapshotAt,
332
+ snapshot.lastEventId,
333
+ snapshot.pendingTimeouts ? JSON.stringify(snapshot.pendingTimeouts) : null,
334
+ ]);
335
+ }
336
+
337
+ async getSnapshot(instanceId: string): Promise<InstanceSnapshot | null> {
338
+ const query = `
339
+ SELECT * FROM fsm_snapshots
340
+ WHERE instance_id = $1
341
+ `;
342
+
343
+ const result = await this.pool.query(query, [instanceId]);
344
+ if (result.rows.length === 0) {
345
+ return null;
346
+ }
347
+
348
+ const row = result.rows[0];
349
+ return {
350
+ instance: {
351
+ id: row.instance_id,
352
+ machineName: row.machine_name,
353
+ currentState: row.current_state,
354
+ status: row.status,
355
+ context: JSON.parse(row.context),
356
+ publicMember: row.public_member ? JSON.parse(row.public_member) : undefined,
357
+ createdAt: 0, // Not stored
358
+ updatedAt: 0, // Not stored
359
+ },
360
+ snapshotAt: row.snapshot_at,
361
+ lastEventId: row.last_event_id,
362
+ pendingTimeouts: row.pending_timeouts ? JSON.parse(row.pending_timeouts) : undefined,
363
+ };
364
+ }
365
+
366
+ async getAllSnapshots(): Promise<InstanceSnapshot[]> {
367
+ const query = `SELECT * FROM fsm_snapshots`;
368
+ const result = await this.pool.query(query);
369
+
370
+ return result.rows.map(row => ({
371
+ instance: {
372
+ id: row.instance_id,
373
+ machineName: row.machine_name,
374
+ currentState: row.current_state,
375
+ status: row.status,
376
+ context: JSON.parse(row.context),
377
+ publicMember: row.public_member ? JSON.parse(row.public_member) : undefined,
378
+ createdAt: 0,
379
+ updatedAt: 0,
380
+ },
381
+ snapshotAt: row.snapshot_at,
382
+ lastEventId: row.last_event_id,
383
+ pendingTimeouts: row.pending_timeouts ? JSON.parse(row.pending_timeouts) : undefined,
384
+ }));
385
+ }
386
+
387
+ async deleteSnapshot(instanceId: string): Promise<void> {
388
+ const query = `DELETE FROM fsm_snapshots WHERE instance_id = $1`;
389
+ await this.pool.query(query, [instanceId]);
390
+ }
391
+
392
+ async close(): Promise<void> {
393
+ await this.pool.end();
394
+ }
395
+ }
396
+ ```
397
+
398
+ ### Usage
399
+
400
+ ```typescript
401
+ import { FSMRuntime } from './fsm-runtime';
402
+ import { PostgreSQLEventStore, PostgreSQLSnapshotStore } from './persistence/postgresql';
403
+
404
+ const connectionString = process.env.DATABASE_URL || 'postgresql://user:pass@localhost/xcomponent';
405
+
406
+ const eventStore = new PostgreSQLEventStore(connectionString);
407
+ const snapshotStore = new PostgreSQLSnapshotStore(connectionString);
408
+
409
+ const runtime = new FSMRuntime(component, {
410
+ eventSourcing: true,
411
+ snapshots: true,
412
+ snapshotInterval: 50,
413
+ eventStore,
414
+ snapshotStore,
415
+ });
416
+ ```
417
+
418
+ ## MongoDB Implementation
419
+
420
+ Example MongoDB-backed persistence:
421
+
422
+ ```typescript
423
+ import { MongoClient, Collection, Db } from 'mongodb';
424
+ import { EventStore, SnapshotStore, PersistedEvent, InstanceSnapshot } from './types';
425
+
426
+ export class MongoDBEventStore implements EventStore {
427
+ private client: MongoClient;
428
+ private db: Db | null = null;
429
+ private events: Collection | null = null;
430
+
431
+ constructor(private uri: string, private dbName: string = 'xcomponent') {}
432
+
433
+ async connect(): Promise<void> {
434
+ this.client = new MongoClient(this.uri);
435
+ await this.client.connect();
436
+ this.db = this.client.db(this.dbName);
437
+ this.events = this.db.collection('events');
438
+
439
+ // Create indexes
440
+ await this.events.createIndex({ instanceId: 1 });
441
+ await this.events.createIndex({ componentName: 1 });
442
+ await this.events.createIndex({ persistedAt: 1 });
443
+ await this.events.createIndex({ 'causedBy': 1 });
444
+ }
445
+
446
+ async append(event: PersistedEvent): Promise<void> {
447
+ await this.events!.insertOne({
448
+ ...event,
449
+ _id: event.id,
450
+ });
451
+ }
452
+
453
+ async getEventsForInstance(instanceId: string): Promise<PersistedEvent[]> {
454
+ const docs = await this.events!
455
+ .find({ instanceId })
456
+ .sort({ persistedAt: 1 })
457
+ .toArray();
458
+
459
+ return docs.map(doc => this.docToEvent(doc));
460
+ }
461
+
462
+ async getEventsByTimeRange(startTime: number, endTime: number): Promise<PersistedEvent[]> {
463
+ const docs = await this.events!
464
+ .find({
465
+ persistedAt: { $gte: startTime, $lte: endTime },
466
+ })
467
+ .sort({ persistedAt: 1 })
468
+ .toArray();
469
+
470
+ return docs.map(doc => this.docToEvent(doc));
471
+ }
472
+
473
+ async getCausedEvents(eventId: string): Promise<PersistedEvent[]> {
474
+ const docs = await this.events!
475
+ .find({ causedBy: eventId })
476
+ .sort({ persistedAt: 1 })
477
+ .toArray();
478
+
479
+ return docs.map(doc => this.docToEvent(doc));
480
+ }
481
+
482
+ async getAllEvents(): Promise<PersistedEvent[]> {
483
+ const docs = await this.events!
484
+ .find({})
485
+ .sort({ persistedAt: 1 })
486
+ .toArray();
487
+
488
+ return docs.map(doc => this.docToEvent(doc));
489
+ }
490
+
491
+ private docToEvent(doc: any): PersistedEvent {
492
+ const { _id, ...rest } = doc;
493
+ return { id: _id, ...rest } as PersistedEvent;
494
+ }
495
+
496
+ async close(): Promise<void> {
497
+ await this.client.close();
498
+ }
499
+ }
500
+
501
+ export class MongoDBSnapshotStore implements SnapshotStore {
502
+ private client: MongoClient;
503
+ private db: Db | null = null;
504
+ private snapshots: Collection | null = null;
505
+
506
+ constructor(private uri: string, private dbName: string = 'xcomponent') {}
507
+
508
+ async connect(): Promise<void> {
509
+ this.client = new MongoClient(this.uri);
510
+ await this.client.connect();
511
+ this.db = this.client.db(this.dbName);
512
+ this.snapshots = this.db.collection('snapshots');
513
+
514
+ // Create index
515
+ await this.snapshots.createIndex({ 'instance.id': 1 }, { unique: true });
516
+ }
517
+
518
+ async saveSnapshot(snapshot: InstanceSnapshot): Promise<void> {
519
+ await this.snapshots!.replaceOne(
520
+ { 'instance.id': snapshot.instance.id },
521
+ snapshot,
522
+ { upsert: true }
523
+ );
524
+ }
525
+
526
+ async getSnapshot(instanceId: string): Promise<InstanceSnapshot | null> {
527
+ const doc = await this.snapshots!.findOne({ 'instance.id': instanceId });
528
+ return doc as InstanceSnapshot | null;
529
+ }
530
+
531
+ async getAllSnapshots(): Promise<InstanceSnapshot[]> {
532
+ const docs = await this.snapshots!.find({}).toArray();
533
+ return docs as InstanceSnapshot[];
534
+ }
535
+
536
+ async deleteSnapshot(instanceId: string): Promise<void> {
537
+ await this.snapshots!.deleteOne({ 'instance.id': instanceId });
538
+ }
539
+
540
+ async close(): Promise<void> {
541
+ await this.client.close();
542
+ }
543
+ }
544
+ ```
545
+
546
+ ### Usage
547
+
548
+ ```typescript
549
+ import { FSMRuntime } from './fsm-runtime';
550
+ import { MongoDBEventStore, MongoDBSnapshotStore } from './persistence/mongodb';
551
+
552
+ const mongoUri = process.env.MONGO_URI || 'mongodb://localhost:27017';
553
+
554
+ const eventStore = new MongoDBEventStore(mongoUri, 'xcomponent');
555
+ const snapshotStore = new MongoDBSnapshotStore(mongoUri, 'xcomponent');
556
+
557
+ await eventStore.connect();
558
+ await snapshotStore.connect();
559
+
560
+ const runtime = new FSMRuntime(component, {
561
+ eventSourcing: true,
562
+ snapshots: true,
563
+ eventStore,
564
+ snapshotStore,
565
+ });
566
+ ```
567
+
568
+ ## Cross-Component Traceability
569
+
570
+ xcomponent-ai supports tracing events across component boundaries:
571
+
572
+ ### Component Name Tracking
573
+
574
+ All persisted events include the component name:
575
+
576
+ ```typescript
577
+ interface PersistedEvent {
578
+ id: string;
579
+ instanceId: string;
580
+ machineName: string;
581
+ componentName: string; // Component where event occurred
582
+ // ...
583
+ sourceComponentName?: string; // Optional: source component
584
+ targetComponentName?: string; // Optional: target component
585
+ }
586
+ ```
587
+
588
+ ### Tracing Across Components
589
+
590
+ Use ComponentRegistry for cross-component traceability:
591
+
592
+ ```typescript
593
+ import { ComponentRegistry } from './component-registry';
594
+
595
+ const registry = new ComponentRegistry();
596
+
597
+ // Register components
598
+ registry.registerComponent(orderComponent, orderRuntime);
599
+ registry.registerComponent(inventoryComponent, inventoryRuntime);
600
+ registry.registerComponent(shippingComponent, shippingRuntime);
601
+
602
+ // Get all events across components
603
+ const allEvents = await registry.getAllPersistedEvents();
604
+
605
+ // Trace causality chain across components
606
+ const causalityChain = await registry.traceEventAcrossComponents(rootEventId);
607
+
608
+ // Find instance regardless of component
609
+ const result = registry.findInstance(instanceId);
610
+ ```
611
+
612
+ ### API Endpoints
613
+
614
+ The API server exposes cross-component traceability endpoints:
615
+
616
+ ```bash
617
+ # Trace event causality across all components
618
+ GET /api/cross-component/causality/:eventId
619
+
620
+ # Get all events from all components
621
+ GET /api/cross-component/events
622
+
623
+ # Get instance history (searches all components)
624
+ GET /api/cross-component/instance/:instanceId/history
625
+ ```
626
+
627
+ ## Best Practices
628
+
629
+ ### 1. **Event Store Selection**
630
+
631
+ - **Development**: Use `InMemoryEventStore`
632
+ - **Production**: Use PostgreSQL, MongoDB, or similar
633
+ - **High Volume**: Consider time-series databases (TimescaleDB, InfluxDB)
634
+
635
+ ### 2. **Snapshot Frequency**
636
+
637
+ Balance between restoration speed and storage:
638
+
639
+ ```typescript
640
+ const runtime = new FSMRuntime(component, {
641
+ snapshots: true,
642
+ snapshotInterval: 50, // Snapshot every 50 transitions
643
+ });
644
+ ```
645
+
646
+ - **Low-frequency transitions**: Lower interval (10-20)
647
+ - **High-frequency transitions**: Higher interval (50-100)
648
+ - **Long-running workflows**: Lower interval for faster recovery
649
+
650
+ ### 3. **Event Retention**
651
+
652
+ Implement retention policies to manage storage:
653
+
654
+ ```typescript
655
+ // Delete events older than 90 days
656
+ const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
657
+
658
+ const oldEvents = await eventStore.getEventsByTimeRange(0, ninetyDaysAgo);
659
+ // Archive or delete old events
660
+ ```
661
+
662
+ ### 4. **Backup Strategy**
663
+
664
+ Regular backups of event and snapshot stores:
665
+
666
+ ```bash
667
+ # PostgreSQL backup
668
+ pg_dump -t fsm_events -t fsm_snapshots dbname > backup.sql
669
+
670
+ # MongoDB backup
671
+ mongodump --db=xcomponent --collection=events --out=backup/
672
+ mongodump --db=xcomponent --collection=snapshots --out=backup/
673
+ ```
674
+
675
+ ### 5. **Disaster Recovery**
676
+
677
+ Restore from snapshots + replay events:
678
+
679
+ ```typescript
680
+ // 1. Get latest snapshot
681
+ const snapshot = await snapshotStore.getSnapshot(instanceId);
682
+
683
+ // 2. Restore instance from snapshot
684
+ const runtime = new FSMRuntime(component, persistenceConfig);
685
+ await runtime.restoreFromSnapshot(snapshot);
686
+
687
+ // 3. Replay events after snapshot
688
+ const events = await eventStore.getEventsAfterSnapshot(
689
+ instanceId,
690
+ snapshot.lastEventId
691
+ );
692
+
693
+ for (const event of events) {
694
+ await runtime.sendEvent(instanceId, event.event);
695
+ }
696
+ ```
697
+
698
+ ### 6. **Monitoring**
699
+
700
+ Monitor event store health:
701
+
702
+ ```typescript
703
+ // Track event append latency
704
+ const start = Date.now();
705
+ await eventStore.append(event);
706
+ const latency = Date.now() - start;
707
+
708
+ // Alert if latency > threshold
709
+ if (latency > 100) {
710
+ console.warn(`High event store latency: ${latency}ms`);
711
+ }
712
+ ```
713
+
714
+ ### 7. **Connection Pooling**
715
+
716
+ Use connection pools for databases:
717
+
718
+ ```typescript
719
+ // PostgreSQL with pooling
720
+ const pool = new Pool({
721
+ connectionString,
722
+ max: 20, // Max connections
723
+ idleTimeoutMillis: 30000, // Close idle connections
724
+ connectionTimeoutMillis: 2000,
725
+ });
726
+ ```
727
+
728
+ ### 8. **Cross-Component Shared Stores**
729
+
730
+ For cross-component traceability, use shared event stores:
731
+
732
+ ```typescript
733
+ const sharedEventStore = new PostgreSQLEventStore(connectionString);
734
+ const sharedSnapshotStore = new PostgreSQLSnapshotStore(connectionString);
735
+
736
+ // All components use same stores
737
+ const orderRuntime = new FSMRuntime(orderComponent, {
738
+ eventStore: sharedEventStore,
739
+ snapshotStore: sharedSnapshotStore,
740
+ });
741
+
742
+ const inventoryRuntime = new FSMRuntime(inventoryComponent, {
743
+ eventStore: sharedEventStore,
744
+ snapshotStore: sharedSnapshotStore,
745
+ });
746
+ ```
747
+
748
+ This enables system-wide event tracing and causality analysis.
749
+
750
+ ## Environment Variables
751
+
752
+ Recommended environment variable configuration:
753
+
754
+ ```bash
755
+ # Database connection
756
+ DATABASE_URL=postgresql://user:pass@localhost:5432/xcomponent
757
+ MONGO_URI=mongodb://localhost:27017
758
+
759
+ # Persistence settings
760
+ EVENT_SOURCING_ENABLED=true
761
+ SNAPSHOTS_ENABLED=true
762
+ SNAPSHOT_INTERVAL=50
763
+
764
+ # Retention
765
+ EVENT_RETENTION_DAYS=90
766
+ SNAPSHOT_RETENTION_COUNT=10
767
+ ```
768
+
769
+ ## Further Reading
770
+
771
+ - [Event Sourcing Pattern](https://martinfowler.com/eaaDev/EventSourcing.html)
772
+ - [CQRS and Event Sourcing](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)
773
+ - [PostgreSQL Best Practices](https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server)
774
+ - [MongoDB Performance](https://docs.mongodb.com/manual/administration/analyzing-mongodb-performance/)