xcomponent-ai 0.2.2 → 0.3.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.
Files changed (42) hide show
  1. package/EVENT-ACCUMULATION-GUIDE.md +505 -0
  2. package/EXTERNAL-API.md +719 -0
  3. package/LLM-GUIDE.md +575 -0
  4. package/QUICKSTART.md +1 -1
  5. package/SCALABILITY.md +455 -0
  6. package/dist/cli.js +170 -63
  7. package/dist/cli.js.map +1 -1
  8. package/dist/component-registry.d.ts +35 -4
  9. package/dist/component-registry.d.ts.map +1 -1
  10. package/dist/component-registry.js +194 -9
  11. package/dist/component-registry.js.map +1 -1
  12. package/dist/external-broker-api.d.ts +205 -0
  13. package/dist/external-broker-api.d.ts.map +1 -0
  14. package/dist/external-broker-api.js +222 -0
  15. package/dist/external-broker-api.js.map +1 -0
  16. package/dist/fsm-runtime.d.ts +47 -17
  17. package/dist/fsm-runtime.d.ts.map +1 -1
  18. package/dist/fsm-runtime.js +333 -136
  19. package/dist/fsm-runtime.js.map +1 -1
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +11 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/mermaid-generator.d.ts.map +1 -1
  25. package/dist/mermaid-generator.js +1 -17
  26. package/dist/mermaid-generator.js.map +1 -1
  27. package/dist/message-broker.d.ts +116 -0
  28. package/dist/message-broker.d.ts.map +1 -0
  29. package/dist/message-broker.js +225 -0
  30. package/dist/message-broker.js.map +1 -0
  31. package/dist/types.d.ts +92 -19
  32. package/dist/types.d.ts.map +1 -1
  33. package/examples/advanced-patterns-demo.yaml +339 -0
  34. package/examples/cross-component-demo.yaml +68 -0
  35. package/examples/distributed-demo/README.md +234 -0
  36. package/examples/distributed-demo/order.yaml +71 -0
  37. package/examples/distributed-demo/payment.yaml +60 -0
  38. package/examples/event-accumulation-demo.yaml +172 -0
  39. package/examples/explicit-transitions-demo.yaml +236 -0
  40. package/examples/payment-receiver.yaml +56 -0
  41. package/package.json +8 -1
  42. package/public/dashboard.html +647 -110
@@ -0,0 +1,505 @@
1
+ # 📊 Event Accumulation & Explicit Control Guide
2
+
3
+ This guide shows how to handle **multiple events** that accumulate data and **explicitly control transitions** based on business logic.
4
+
5
+ ## 🎯 Use Case
6
+
7
+ **Trading Order Example:**
8
+ - Order for 1000 shares receives multiple execution notifications
9
+ - Each notification has a partial quantity (e.g., 100, 250, 150...)
10
+ - Order should transition to "Fully Executed" **only when** `executedQuantity >= totalQuantity`
11
+
12
+ **Key Challenge:** How to accumulate data from multiple events and explicitly decide when to transition?
13
+
14
+ ---
15
+
16
+ ## 💡 Solution: Explicit Control with sender.sendToSelf()
17
+
18
+ xcomponent-ai provides **explicit control** through triggered methods:
19
+
20
+ 1. **Context** - Stores accumulated state
21
+ 2. **Triggered Methods** - Accumulate data from events and decide when to transition
22
+ 3. **sender.sendToSelf()** - Explicitly trigger state transitions from code
23
+
24
+ **Philosophy:** Business logic should be in code (triggered methods), not in YAML configuration.
25
+
26
+ ---
27
+
28
+ ## 🔧 Implementation
29
+
30
+ ### Step 1: Define Context Schema
31
+
32
+ ```yaml
33
+ stateMachines:
34
+ - name: TradingOrder
35
+ initialState: Created
36
+
37
+ contextSchema:
38
+ orderId:
39
+ type: text
40
+ required: true
41
+ totalQuantity:
42
+ type: number
43
+ required: true
44
+ min: 1
45
+ # These will be populated by triggered methods
46
+ executedQuantity:
47
+ type: number
48
+ default: 0
49
+ executions:
50
+ type: array
51
+ default: []
52
+ ```
53
+
54
+ ### Step 2: Create Accumulation Method with Explicit Control
55
+
56
+ ```yaml
57
+ triggeredMethods:
58
+ accumulateExecution: |
59
+ async function(event, context, sender) {
60
+ // Initialize counters
61
+ if (!context.executedQuantity) {
62
+ context.executedQuantity = 0;
63
+ }
64
+ if (!context.executions) {
65
+ context.executions = [];
66
+ }
67
+
68
+ // Accumulate quantity from event
69
+ const qty = event.payload.quantity || 0;
70
+ context.executedQuantity += qty;
71
+
72
+ // Track individual executions
73
+ context.executions.push({
74
+ quantity: qty,
75
+ price: event.payload.price,
76
+ executionId: event.payload.executionId,
77
+ timestamp: event.timestamp
78
+ });
79
+
80
+ console.log(`Executed: ${context.executedQuantity}/${context.totalQuantity}`);
81
+
82
+ // EXPLICIT CONTROL: Decide when to transition
83
+ if (context.executedQuantity >= context.totalQuantity) {
84
+ // Send event to self to trigger completion
85
+ await sender.sendToSelf({
86
+ type: 'FULLY_EXECUTED',
87
+ payload: {
88
+ totalExecuted: context.executedQuantity,
89
+ executionCount: context.executions.length,
90
+ averagePrice: context.executions.reduce((sum, e) => sum + e.price, 0) / context.executions.length
91
+ },
92
+ timestamp: Date.now()
93
+ });
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Key Points:**
99
+ - Method accumulates data from the event
100
+ - Method **decides** when to transition based on business logic
101
+ - Uses `sender.sendToSelf()` to **explicitly** trigger the transition
102
+ - Event is queued asynchronously to avoid race conditions
103
+
104
+ ### Step 3: Define Transitions (No Guards!)
105
+
106
+ ```yaml
107
+ transitions:
108
+ # SELF-LOOP: Stay in PartiallyExecuted
109
+ # Triggered method decides when to send FULLY_EXECUTED event
110
+ - from: PartiallyExecuted
111
+ to: PartiallyExecuted
112
+ event: EXECUTION_NOTIFICATION
113
+ type: triggerable
114
+ triggeredMethod: accumulateExecution
115
+
116
+ # EXPLICIT TRANSITION: Triggered by sender.sendToSelf()
117
+ # No guards - logic is in the triggered method
118
+ - from: PartiallyExecuted
119
+ to: FullyExecuted
120
+ event: FULLY_EXECUTED
121
+ type: triggerable
122
+ triggeredMethod: handleCompletion
123
+
124
+ # Also handle direct full execution from Submitted
125
+ - from: Submitted
126
+ to: FullyExecuted
127
+ event: FULLY_EXECUTED
128
+ type: triggerable
129
+ triggeredMethod: handleCompletion
130
+ ```
131
+
132
+ **How it works:**
133
+ 1. Event `EXECUTION_NOTIFICATION` arrives
134
+ 2. `accumulateExecution` method executes, updates context
135
+ 3. Method checks if `executedQuantity >= totalQuantity`
136
+ 4. If true, method calls `sender.sendToSelf()` with `FULLY_EXECUTED` event
137
+ 5. `FULLY_EXECUTED` event triggers transition to `FullyExecuted` state
138
+
139
+ **Benefits:**
140
+ - ✅ All business logic in one place (triggered method)
141
+ - ✅ Easy to test and debug
142
+ - ✅ Can set event payload properties dynamically
143
+ - ✅ Full control over transition timing
144
+
145
+ ---
146
+
147
+ ## 📝 Complete Example
148
+
149
+ ```yaml
150
+ name: TradingComponent
151
+ version: 1.0.0
152
+
153
+ triggeredMethods:
154
+ accumulateExecution: |
155
+ async function(event, context, sender) {
156
+ if (!context.executedQuantity) context.executedQuantity = 0;
157
+ if (!context.executions) context.executions = [];
158
+
159
+ const qty = event.payload.quantity || 0;
160
+ context.executedQuantity += qty;
161
+ context.executions.push({
162
+ quantity: qty,
163
+ price: event.payload.price,
164
+ executionId: event.payload.executionId,
165
+ timestamp: event.timestamp
166
+ });
167
+
168
+ console.log(`Executed: ${context.executedQuantity}/${context.totalQuantity}`);
169
+
170
+ // EXPLICIT CONTROL
171
+ if (context.executedQuantity >= context.totalQuantity) {
172
+ await sender.sendToSelf({
173
+ type: 'FULLY_EXECUTED',
174
+ payload: {
175
+ totalExecuted: context.executedQuantity,
176
+ executionCount: context.executions.length
177
+ },
178
+ timestamp: Date.now()
179
+ });
180
+ }
181
+ }
182
+
183
+ handleCompletion: |
184
+ async function(event, context, sender) {
185
+ console.log(`Order completed!`);
186
+ console.log(` Total: ${event.payload.totalExecuted}`);
187
+ console.log(` Executions: ${event.payload.executionCount}`);
188
+ context.stats = event.payload;
189
+ }
190
+
191
+ stateMachines:
192
+ - name: TradingOrder
193
+ initialState: Created
194
+
195
+ states:
196
+ - name: Created
197
+ type: entry
198
+ - name: Submitted
199
+ type: regular
200
+ - name: PartiallyExecuted
201
+ type: regular
202
+ - name: FullyExecuted
203
+ type: regular
204
+ - name: Completed
205
+ type: final
206
+
207
+ transitions:
208
+ - from: Created
209
+ to: Submitted
210
+ event: SUBMIT
211
+ type: triggerable
212
+
213
+ - from: Submitted
214
+ to: PartiallyExecuted
215
+ event: EXECUTION_NOTIFICATION
216
+ type: triggerable
217
+ triggeredMethod: accumulateExecution
218
+
219
+ # SELF-LOOP
220
+ - from: PartiallyExecuted
221
+ to: PartiallyExecuted
222
+ event: EXECUTION_NOTIFICATION
223
+ type: triggerable
224
+ triggeredMethod: accumulateExecution
225
+
226
+ # EXPLICIT TRANSITION
227
+ - from: PartiallyExecuted
228
+ to: FullyExecuted
229
+ event: FULLY_EXECUTED
230
+ type: triggerable
231
+ triggeredMethod: handleCompletion
232
+
233
+ - from: Submitted
234
+ to: FullyExecuted
235
+ event: FULLY_EXECUTED
236
+ type: triggerable
237
+ triggeredMethod: handleCompletion
238
+
239
+ - from: FullyExecuted
240
+ to: Completed
241
+ event: SETTLE
242
+ type: triggerable
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 🧪 Testing
248
+
249
+ ```bash
250
+ # Start the server
251
+ xcomponent-ai serve examples/event-accumulation-demo.yaml
252
+
253
+ # Create order for 1000 shares
254
+ curl -X POST http://localhost:3000/api/instances \
255
+ -H "Content-Type: application/json" \
256
+ -d '{
257
+ "machineName": "TradingOrder",
258
+ "context": {
259
+ "orderId": "ORD-001",
260
+ "symbol": "AAPL",
261
+ "totalQuantity": 1000,
262
+ "side": "BUY"
263
+ }
264
+ }'
265
+
266
+ # Submit order
267
+ curl -X POST http://localhost:3000/api/instances/{instanceId}/events \
268
+ -H "Content-Type: application/json" \
269
+ -d '{"type": "SUBMIT"}'
270
+
271
+ # Send execution notification #1: 300 shares
272
+ curl -X POST http://localhost:3000/api/instances/{instanceId}/events \
273
+ -H "Content-Type: application/json" \
274
+ -d '{
275
+ "type": "EXECUTION_NOTIFICATION",
276
+ "payload": {
277
+ "quantity": 300,
278
+ "price": 150.50,
279
+ "executionId": "EXEC-001"
280
+ }
281
+ }'
282
+ # → State: PartiallyExecuted
283
+
284
+ # Send execution notification #2: 400 shares
285
+ curl -X POST http://localhost:3000/api/instances/{instanceId}/events \
286
+ -H "Content-Type: application/json" \
287
+ -d '{
288
+ "type": "EXECUTION_NOTIFICATION",
289
+ "payload": {
290
+ "quantity": 400,
291
+ "price": 150.45,
292
+ "executionId": "EXEC-002"
293
+ }
294
+ }'
295
+ # → State: PartiallyExecuted (700/1000)
296
+
297
+ # Send execution notification #3: 300 shares
298
+ curl -X POST http://localhost:3000/api/instances/{instanceId}/events \
299
+ -H "Content-Type: application/json" \
300
+ -d '{
301
+ "type": "EXECUTION_NOTIFICATION",
302
+ "payload": {
303
+ "quantity": 300,
304
+ "price": 150.40,
305
+ "executionId": "EXEC-003"
306
+ }
307
+ }'
308
+ # → State: FullyExecuted (1000/1000) ✅
309
+
310
+ # Check final state
311
+ curl http://localhost:3000/api/instances/{instanceId}
312
+ ```
313
+
314
+ **Expected context:**
315
+ ```json
316
+ {
317
+ "orderId": "ORD-001",
318
+ "symbol": "AAPL",
319
+ "totalQuantity": 1000,
320
+ "side": "BUY",
321
+ "executedQuantity": 1000,
322
+ "executions": [
323
+ {"quantity": 300, "price": 150.50, "executionId": "EXEC-001"},
324
+ {"quantity": 400, "price": 150.45, "executionId": "EXEC-002"},
325
+ {"quantity": 300, "price": 150.40, "executionId": "EXEC-003"}
326
+ ],
327
+ "stats": {
328
+ "totalExecuted": 1000,
329
+ "executionCount": 3
330
+ }
331
+ }
332
+ ```
333
+
334
+ ---
335
+
336
+ ## 📤 Sender Methods Reference
337
+
338
+ Triggered methods receive a `sender` parameter with methods for cross-instance communication.
339
+
340
+ ### sendToSelf(event)
341
+
342
+ Send event to current instance (explicit control):
343
+
344
+ ```javascript
345
+ await sender.sendToSelf({
346
+ type: 'FULLY_EXECUTED',
347
+ payload: { totalExecuted: context.executedQuantity },
348
+ timestamp: Date.now()
349
+ });
350
+ ```
351
+
352
+ ### sendTo(instanceId, event)
353
+
354
+ Send event to specific instance:
355
+
356
+ ```javascript
357
+ await sender.sendTo(context.paymentInstanceId, {
358
+ type: 'ORDER_CONFIRMED',
359
+ payload: { orderId: context.orderId },
360
+ timestamp: Date.now()
361
+ });
362
+ ```
363
+
364
+ ### broadcast(machineName, event, currentState?, componentName?)
365
+
366
+ Broadcast to all matching instances. Filtering is done via **matchingRules in YAML**, not in code:
367
+
368
+ ```javascript
369
+ // Broadcast to all Orders (any state)
370
+ await sender.broadcast('Order', {
371
+ type: 'SYSTEM_ALERT',
372
+ payload: {},
373
+ timestamp: Date.now()
374
+ });
375
+
376
+ // Broadcast to Orders in Pending state only
377
+ await sender.broadcast('Order', {
378
+ type: 'TIMEOUT',
379
+ payload: {},
380
+ timestamp: Date.now()
381
+ }, 'Pending');
382
+
383
+ // Cross-component broadcast
384
+ await sender.broadcast('Payment', {
385
+ type: 'ORDER_COMPLETED',
386
+ payload: { orderId: context.orderId },
387
+ timestamp: Date.now()
388
+ }, undefined, 'PaymentComponent');
389
+ ```
390
+
391
+ **Filtering via matchingRules in YAML:**
392
+
393
+ ```yaml
394
+ # In the receiving machine's transition
395
+ transitions:
396
+ - from: Monitoring
397
+ to: Monitoring
398
+ event: ORDER_UPDATE
399
+ type: triggerable
400
+ matchingRules:
401
+ # Only instances with matching customerId receive event
402
+ - eventProperty: payload.customerId
403
+ instanceProperty: customerId
404
+ ```
405
+
406
+ ### createInstance(machineName, initialContext)
407
+
408
+ Create new instance:
409
+
410
+ ```javascript
411
+ const newInstanceId = sender.createInstance('Order', {
412
+ orderId: 'ORD-001',
413
+ totalQuantity: 1000
414
+ });
415
+ ```
416
+
417
+ ### createInstanceInComponent(componentName, machineName, initialContext)
418
+
419
+ Create instance in another component:
420
+
421
+ ```javascript
422
+ sender.createInstanceInComponent('PaymentComponent', 'Payment', {
423
+ orderId: context.orderId,
424
+ amount: context.totalAmount
425
+ });
426
+ ```
427
+
428
+ ---
429
+
430
+ ## 🎯 Other Use Cases
431
+
432
+ ### 1. Payment Installments
433
+
434
+ ```javascript
435
+ async function(event, context, sender) {
436
+ if (!context.paidInstallments) context.paidInstallments = 0;
437
+ context.paidInstallments += 1;
438
+
439
+ if (context.paidInstallments >= context.totalInstallments) {
440
+ await sender.sendToSelf({
441
+ type: 'FULLY_PAID',
442
+ payload: { installments: context.paidInstallments },
443
+ timestamp: Date.now()
444
+ });
445
+ }
446
+ }
447
+ ```
448
+
449
+ ### 2. Multi-Step Approval
450
+
451
+ ```javascript
452
+ async function(event, context, sender) {
453
+ if (!context.approvals) context.approvals = [];
454
+ context.approvals.push({
455
+ approver: event.payload.approver,
456
+ timestamp: event.timestamp
457
+ });
458
+
459
+ if (context.approvals.length >= 3) {
460
+ await sender.sendToSelf({
461
+ type: 'APPROVED',
462
+ payload: { approvers: context.approvals.map(a => a.approver) },
463
+ timestamp: Date.now()
464
+ });
465
+ }
466
+ }
467
+ ```
468
+
469
+ ### 3. Time-based Accumulation
470
+
471
+ ```javascript
472
+ async function(event, context, sender) {
473
+ const elapsed = Date.now() - context.createdAt;
474
+
475
+ if (elapsed > 86400000) { // 24 hours
476
+ await sender.sendToSelf({
477
+ type: 'EXPIRED',
478
+ payload: { duration: elapsed },
479
+ timestamp: Date.now()
480
+ });
481
+ }
482
+ }
483
+ ```
484
+
485
+ ---
486
+
487
+ ## ⚠️ Best Practices
488
+
489
+ 1. **Initialize accumulators** in triggered methods (handle first event)
490
+ 2. **Use sender.sendToSelf()** for explicit control instead of guards
491
+ 3. **Log state changes** for debugging (`console.log` in triggered methods)
492
+ 4. **Test edge cases** (exact match, overfill, underfill)
493
+ 5. **Keep business logic in code** (triggered methods), not YAML
494
+ 6. **Document transitions** with clear comments in YAML
495
+
496
+ ---
497
+
498
+ ## 🔗 See Also
499
+
500
+ - [LLM-GUIDE.md](./LLM-GUIDE.md) - Complete YAML patterns
501
+ - [examples/event-accumulation-demo.yaml](./examples/event-accumulation-demo.yaml) - Full example
502
+ - [examples/explicit-transitions-demo.yaml](./examples/explicit-transitions-demo.yaml) - Explicit control demo
503
+ - [QUICKSTART.md](./QUICKSTART.md) - Getting started
504
+
505
+ **Built for explicit control.** 🚀