@almadar/std 1.0.14 → 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.
@@ -0,0 +1,940 @@
1
+ // behaviors/infrastructure.ts
2
+ var CIRCUIT_BREAKER_BEHAVIOR = {
3
+ name: "std-circuit-breaker",
4
+ version: "1.0.0",
5
+ description: "Circuit breaker pattern with automatic recovery",
6
+ orbitals: [
7
+ {
8
+ name: "CircuitBreakerOrbital",
9
+ entity: {
10
+ name: "CircuitBreakerState",
11
+ persistence: "runtime",
12
+ fields: [
13
+ { name: "id", type: "string", required: true },
14
+ { name: "circuitState", type: "string", default: "closed" },
15
+ { name: "errorCount", type: "number", default: 0 },
16
+ { name: "errorRate", type: "number", default: 0 },
17
+ { name: "successCount", type: "number", default: 0 },
18
+ { name: "totalCount", type: "number", default: 0 },
19
+ { name: "lastFailure", type: "number", default: null },
20
+ { name: "lastSuccess", type: "number", default: null },
21
+ { name: "errorThreshold", type: "number", default: 5 },
22
+ { name: "errorRateThreshold", type: "number", default: 0.5 },
23
+ { name: "resetAfterMs", type: "number", default: 6e4 },
24
+ { name: "halfOpenMaxAttempts", type: "number", default: 3 },
25
+ { name: "halfOpenAttempts", type: "number", default: 0 }
26
+ ]
27
+ },
28
+ traits: [
29
+ {
30
+ name: "CircuitBreaker",
31
+ linkedEntity: "CircuitBreakerState",
32
+ category: "lifecycle",
33
+ emits: [
34
+ { event: "CIRCUIT_OPENED", scope: "external" },
35
+ { event: "CIRCUIT_CLOSED", scope: "external" },
36
+ { event: "CIRCUIT_HALF_OPEN", scope: "external" }
37
+ ],
38
+ stateMachine: {
39
+ states: [
40
+ { name: "Closed", isInitial: true },
41
+ { name: "Open" },
42
+ { name: "HalfOpen" }
43
+ ],
44
+ events: [
45
+ { key: "RECORD_SUCCESS", name: "Record Success" },
46
+ { key: "RECORD_FAILURE", name: "Record Failure" },
47
+ { key: "PROBE", name: "Probe" },
48
+ { key: "RESET", name: "Reset" }
49
+ ],
50
+ transitions: [
51
+ // Closed: record success
52
+ {
53
+ from: "Closed",
54
+ to: "Closed",
55
+ event: "RECORD_SUCCESS",
56
+ effects: [
57
+ ["set", "@entity.successCount", ["+", "@entity.successCount", 1]],
58
+ ["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
59
+ ["set", "@entity.lastSuccess", ["time/now"]],
60
+ ["set", "@entity.errorRate", ["/", "@entity.errorCount", ["math/max", "@entity.totalCount", 1]]]
61
+ ]
62
+ },
63
+ // Closed: record failure, stay closed if under threshold
64
+ {
65
+ from: "Closed",
66
+ to: "Closed",
67
+ event: "RECORD_FAILURE",
68
+ guard: ["<", ["+", "@entity.errorCount", 1], "@entity.errorThreshold"],
69
+ effects: [
70
+ ["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
71
+ ["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
72
+ ["set", "@entity.lastFailure", ["time/now"]],
73
+ ["set", "@entity.errorRate", ["/", ["+", "@entity.errorCount", 1], ["math/max", "@entity.totalCount", 1]]]
74
+ ]
75
+ },
76
+ // Closed -> Open: threshold exceeded
77
+ {
78
+ from: "Closed",
79
+ to: "Open",
80
+ event: "RECORD_FAILURE",
81
+ guard: [">=", ["+", "@entity.errorCount", 1], "@entity.errorThreshold"],
82
+ effects: [
83
+ ["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
84
+ ["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
85
+ ["set", "@entity.lastFailure", ["time/now"]],
86
+ ["set", "@entity.errorRate", ["/", ["+", "@entity.errorCount", 1], ["math/max", "@entity.totalCount", 1]]],
87
+ ["emit", "CIRCUIT_OPENED", { errorCount: "@entity.errorCount", errorRate: "@entity.errorRate" }]
88
+ ]
89
+ },
90
+ // Open -> HalfOpen: probe after reset timeout
91
+ {
92
+ from: "Open",
93
+ to: "HalfOpen",
94
+ event: "PROBE",
95
+ guard: [">", ["-", ["time/now"], "@entity.lastFailure"], "@entity.resetAfterMs"],
96
+ effects: [
97
+ ["set", "@entity.halfOpenAttempts", 0],
98
+ ["emit", "CIRCUIT_HALF_OPEN", {}]
99
+ ]
100
+ },
101
+ // HalfOpen: success -> close
102
+ {
103
+ from: "HalfOpen",
104
+ to: "Closed",
105
+ event: "RECORD_SUCCESS",
106
+ effects: [
107
+ ["set", "@entity.errorCount", 0],
108
+ ["set", "@entity.errorRate", 0],
109
+ ["set", "@entity.halfOpenAttempts", 0],
110
+ ["set", "@entity.successCount", ["+", "@entity.successCount", 1]],
111
+ ["set", "@entity.lastSuccess", ["time/now"]],
112
+ ["emit", "CIRCUIT_CLOSED", {}]
113
+ ]
114
+ },
115
+ // HalfOpen: failure -> back to open
116
+ {
117
+ from: "HalfOpen",
118
+ to: "Open",
119
+ event: "RECORD_FAILURE",
120
+ effects: [
121
+ ["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
122
+ ["set", "@entity.lastFailure", ["time/now"]],
123
+ ["emit", "CIRCUIT_OPENED", { errorCount: "@entity.errorCount" }]
124
+ ]
125
+ },
126
+ // Reset from any state
127
+ {
128
+ from: ["Closed", "Open", "HalfOpen"],
129
+ to: "Closed",
130
+ event: "RESET",
131
+ effects: [
132
+ ["set", "@entity.errorCount", 0],
133
+ ["set", "@entity.successCount", 0],
134
+ ["set", "@entity.totalCount", 0],
135
+ ["set", "@entity.errorRate", 0],
136
+ ["set", "@entity.halfOpenAttempts", 0],
137
+ ["set", "@entity.circuitState", "closed"]
138
+ ]
139
+ }
140
+ ]
141
+ },
142
+ ticks: [
143
+ {
144
+ name: "probe_half_open",
145
+ interval: "30000",
146
+ guard: ["=", "@entity.circuitState", "open"],
147
+ effects: [["emit", "PROBE"]],
148
+ description: "Periodically probe to transition from Open to HalfOpen"
149
+ }
150
+ ]
151
+ }
152
+ ],
153
+ pages: []
154
+ }
155
+ ]
156
+ };
157
+ var HEALTH_CHECK_BEHAVIOR = {
158
+ name: "std-health-check",
159
+ version: "1.0.0",
160
+ description: "Tick-based health monitoring with degradation detection",
161
+ orbitals: [
162
+ {
163
+ name: "HealthCheckOrbital",
164
+ entity: {
165
+ name: "HealthCheckState",
166
+ persistence: "runtime",
167
+ fields: [
168
+ { name: "id", type: "string", required: true },
169
+ { name: "healthStatus", type: "string", default: "unknown" },
170
+ { name: "lastCheck", type: "number", default: null },
171
+ { name: "lastHealthy", type: "number", default: null },
172
+ { name: "consecutiveFailures", type: "number", default: 0 },
173
+ { name: "consecutiveSuccesses", type: "number", default: 0 },
174
+ { name: "checkIntervalMs", type: "number", default: 3e4 },
175
+ { name: "degradedThreshold", type: "number", default: 2 },
176
+ { name: "unhealthyThreshold", type: "number", default: 5 },
177
+ { name: "recoveryThreshold", type: "number", default: 3 },
178
+ { name: "totalChecks", type: "number", default: 0 },
179
+ { name: "totalFailures", type: "number", default: 0 }
180
+ ]
181
+ },
182
+ traits: [
183
+ {
184
+ name: "HealthCheck",
185
+ linkedEntity: "HealthCheckState",
186
+ category: "lifecycle",
187
+ emits: [
188
+ { event: "SERVICE_HEALTHY", scope: "external" },
189
+ { event: "SERVICE_DEGRADED", scope: "external" },
190
+ { event: "SERVICE_UNHEALTHY", scope: "external" }
191
+ ],
192
+ stateMachine: {
193
+ states: [
194
+ { name: "Unknown", isInitial: true },
195
+ { name: "Healthy" },
196
+ { name: "Degraded" },
197
+ { name: "Unhealthy" }
198
+ ],
199
+ events: [
200
+ { key: "CHECK_SUCCESS", name: "Check Success" },
201
+ { key: "CHECK_FAILURE", name: "Check Failure" },
202
+ { key: "HEALTH_TICK", name: "Health Tick" },
203
+ { key: "RESET", name: "Reset" }
204
+ ],
205
+ transitions: [
206
+ // Unknown -> Healthy on first success
207
+ {
208
+ from: "Unknown",
209
+ to: "Healthy",
210
+ event: "CHECK_SUCCESS",
211
+ effects: [
212
+ ["set", "@entity.healthStatus", "healthy"],
213
+ ["set", "@entity.consecutiveSuccesses", 1],
214
+ ["set", "@entity.consecutiveFailures", 0],
215
+ ["set", "@entity.lastCheck", ["time/now"]],
216
+ ["set", "@entity.lastHealthy", ["time/now"]],
217
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
218
+ ["emit", "SERVICE_HEALTHY", {}]
219
+ ]
220
+ },
221
+ // Unknown -> Degraded on first failure
222
+ {
223
+ from: "Unknown",
224
+ to: "Degraded",
225
+ event: "CHECK_FAILURE",
226
+ effects: [
227
+ ["set", "@entity.healthStatus", "degraded"],
228
+ ["set", "@entity.consecutiveFailures", 1],
229
+ ["set", "@entity.consecutiveSuccesses", 0],
230
+ ["set", "@entity.lastCheck", ["time/now"]],
231
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
232
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
233
+ ["emit", "SERVICE_DEGRADED", { consecutiveFailures: 1 }]
234
+ ]
235
+ },
236
+ // Healthy: stay healthy on success
237
+ {
238
+ from: "Healthy",
239
+ to: "Healthy",
240
+ event: "CHECK_SUCCESS",
241
+ effects: [
242
+ ["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
243
+ ["set", "@entity.consecutiveFailures", 0],
244
+ ["set", "@entity.lastCheck", ["time/now"]],
245
+ ["set", "@entity.lastHealthy", ["time/now"]],
246
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]]
247
+ ]
248
+ },
249
+ // Healthy -> Degraded on failure
250
+ {
251
+ from: "Healthy",
252
+ to: "Degraded",
253
+ event: "CHECK_FAILURE",
254
+ effects: [
255
+ ["set", "@entity.healthStatus", "degraded"],
256
+ ["set", "@entity.consecutiveFailures", 1],
257
+ ["set", "@entity.consecutiveSuccesses", 0],
258
+ ["set", "@entity.lastCheck", ["time/now"]],
259
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
260
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
261
+ ["emit", "SERVICE_DEGRADED", { consecutiveFailures: 1 }]
262
+ ]
263
+ },
264
+ // Degraded: stay degraded on failure (below unhealthy threshold)
265
+ {
266
+ from: "Degraded",
267
+ to: "Degraded",
268
+ event: "CHECK_FAILURE",
269
+ guard: ["<", ["+", "@entity.consecutiveFailures", 1], "@entity.unhealthyThreshold"],
270
+ effects: [
271
+ ["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
272
+ ["set", "@entity.consecutiveSuccesses", 0],
273
+ ["set", "@entity.lastCheck", ["time/now"]],
274
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
275
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]]
276
+ ]
277
+ },
278
+ // Degraded -> Unhealthy when threshold exceeded
279
+ {
280
+ from: "Degraded",
281
+ to: "Unhealthy",
282
+ event: "CHECK_FAILURE",
283
+ guard: [">=", ["+", "@entity.consecutiveFailures", 1], "@entity.unhealthyThreshold"],
284
+ effects: [
285
+ ["set", "@entity.healthStatus", "unhealthy"],
286
+ ["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
287
+ ["set", "@entity.lastCheck", ["time/now"]],
288
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
289
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
290
+ ["emit", "SERVICE_UNHEALTHY", { consecutiveFailures: ["+", "@entity.consecutiveFailures", 1] }]
291
+ ]
292
+ },
293
+ // Degraded -> Healthy on enough successes
294
+ {
295
+ from: "Degraded",
296
+ to: "Healthy",
297
+ event: "CHECK_SUCCESS",
298
+ guard: [">=", ["+", "@entity.consecutiveSuccesses", 1], "@entity.recoveryThreshold"],
299
+ effects: [
300
+ ["set", "@entity.healthStatus", "healthy"],
301
+ ["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
302
+ ["set", "@entity.consecutiveFailures", 0],
303
+ ["set", "@entity.lastCheck", ["time/now"]],
304
+ ["set", "@entity.lastHealthy", ["time/now"]],
305
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
306
+ ["emit", "SERVICE_HEALTHY", {}]
307
+ ]
308
+ },
309
+ // Degraded: stay degraded on success (not enough to recover)
310
+ {
311
+ from: "Degraded",
312
+ to: "Degraded",
313
+ event: "CHECK_SUCCESS",
314
+ guard: ["<", ["+", "@entity.consecutiveSuccesses", 1], "@entity.recoveryThreshold"],
315
+ effects: [
316
+ ["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
317
+ ["set", "@entity.consecutiveFailures", 0],
318
+ ["set", "@entity.lastCheck", ["time/now"]],
319
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]]
320
+ ]
321
+ },
322
+ // Unhealthy: stay unhealthy on failure
323
+ {
324
+ from: "Unhealthy",
325
+ to: "Unhealthy",
326
+ event: "CHECK_FAILURE",
327
+ effects: [
328
+ ["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
329
+ ["set", "@entity.lastCheck", ["time/now"]],
330
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
331
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]]
332
+ ]
333
+ },
334
+ // Unhealthy -> Degraded on first success (recovery begins)
335
+ {
336
+ from: "Unhealthy",
337
+ to: "Degraded",
338
+ event: "CHECK_SUCCESS",
339
+ effects: [
340
+ ["set", "@entity.healthStatus", "degraded"],
341
+ ["set", "@entity.consecutiveSuccesses", 1],
342
+ ["set", "@entity.consecutiveFailures", 0],
343
+ ["set", "@entity.lastCheck", ["time/now"]],
344
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
345
+ ["emit", "SERVICE_DEGRADED", { recovering: true }]
346
+ ]
347
+ },
348
+ // Reset from any state
349
+ {
350
+ from: ["Unknown", "Healthy", "Degraded", "Unhealthy"],
351
+ to: "Unknown",
352
+ event: "RESET",
353
+ effects: [
354
+ ["set", "@entity.healthStatus", "unknown"],
355
+ ["set", "@entity.consecutiveFailures", 0],
356
+ ["set", "@entity.consecutiveSuccesses", 0],
357
+ ["set", "@entity.totalChecks", 0],
358
+ ["set", "@entity.totalFailures", 0]
359
+ ]
360
+ }
361
+ ]
362
+ },
363
+ ticks: [
364
+ {
365
+ name: "periodic_health_check",
366
+ interval: "@entity.checkIntervalMs",
367
+ effects: [["emit", "HEALTH_TICK"]],
368
+ description: "Periodically trigger health check"
369
+ }
370
+ ]
371
+ }
372
+ ],
373
+ pages: []
374
+ }
375
+ ]
376
+ };
377
+ var RATE_LIMITER_BEHAVIOR = {
378
+ name: "std-rate-limiter",
379
+ version: "1.0.0",
380
+ description: "Guard-based rate limiting with sliding window reset",
381
+ orbitals: [
382
+ {
383
+ name: "RateLimiterOrbital",
384
+ entity: {
385
+ name: "RateLimiterState",
386
+ persistence: "runtime",
387
+ fields: [
388
+ { name: "id", type: "string", required: true },
389
+ { name: "requestCount", type: "number", default: 0 },
390
+ { name: "windowStart", type: "number", default: 0 },
391
+ { name: "rateLimit", type: "number", default: 60 },
392
+ { name: "windowMs", type: "number", default: 6e4 },
393
+ { name: "totalRequests", type: "number", default: 0 },
394
+ { name: "rejectedRequests", type: "number", default: 0 }
395
+ ]
396
+ },
397
+ traits: [
398
+ {
399
+ name: "RateLimiter",
400
+ linkedEntity: "RateLimiterState",
401
+ category: "lifecycle",
402
+ emits: [
403
+ { event: "RATE_LIMIT_EXCEEDED", scope: "external" }
404
+ ],
405
+ stateMachine: {
406
+ states: [
407
+ { name: "Active", isInitial: true }
408
+ ],
409
+ events: [
410
+ { key: "REQUEST", name: "Record Request" },
411
+ { key: "REQUEST_REJECTED", name: "Request Rejected" },
412
+ { key: "WINDOW_RESET", name: "Window Reset" },
413
+ { key: "RESET", name: "Full Reset" }
414
+ ],
415
+ transitions: [
416
+ // Request allowed
417
+ {
418
+ from: "Active",
419
+ to: "Active",
420
+ event: "REQUEST",
421
+ guard: ["<", "@entity.requestCount", "@entity.rateLimit"],
422
+ effects: [
423
+ ["set", "@entity.requestCount", ["+", "@entity.requestCount", 1]],
424
+ ["set", "@entity.totalRequests", ["+", "@entity.totalRequests", 1]]
425
+ ]
426
+ },
427
+ // Request rejected — over limit
428
+ {
429
+ from: "Active",
430
+ to: "Active",
431
+ event: "REQUEST",
432
+ guard: [">=", "@entity.requestCount", "@entity.rateLimit"],
433
+ effects: [
434
+ ["set", "@entity.rejectedRequests", ["+", "@entity.rejectedRequests", 1]],
435
+ ["emit", "RATE_LIMIT_EXCEEDED", {
436
+ requestCount: "@entity.requestCount",
437
+ rateLimit: "@entity.rateLimit"
438
+ }]
439
+ ]
440
+ },
441
+ // Sliding window reset
442
+ {
443
+ from: "Active",
444
+ to: "Active",
445
+ event: "WINDOW_RESET",
446
+ effects: [
447
+ ["set", "@entity.requestCount", 0],
448
+ ["set", "@entity.windowStart", ["time/now"]]
449
+ ]
450
+ },
451
+ // Full counter reset
452
+ {
453
+ from: "Active",
454
+ to: "Active",
455
+ event: "RESET",
456
+ effects: [
457
+ ["set", "@entity.requestCount", 0],
458
+ ["set", "@entity.totalRequests", 0],
459
+ ["set", "@entity.rejectedRequests", 0],
460
+ ["set", "@entity.windowStart", ["time/now"]]
461
+ ]
462
+ }
463
+ ]
464
+ },
465
+ ticks: [
466
+ {
467
+ name: "window_reset",
468
+ interval: "@entity.windowMs",
469
+ effects: [["emit", "WINDOW_RESET"]],
470
+ description: "Reset request counter on sliding window expiry"
471
+ }
472
+ ]
473
+ }
474
+ ],
475
+ pages: []
476
+ }
477
+ ]
478
+ };
479
+ var CACHE_ASIDE_BEHAVIOR = {
480
+ name: "std-cache-aside",
481
+ version: "1.0.0",
482
+ description: "Cache-aside pattern with TTL-based freshness and eviction",
483
+ orbitals: [
484
+ {
485
+ name: "CacheAsideOrbital",
486
+ entity: {
487
+ name: "CacheEntry",
488
+ persistence: "runtime",
489
+ fields: [
490
+ { name: "id", type: "string", required: true },
491
+ { name: "cacheKey", type: "string", default: "" },
492
+ { name: "cachedValue", type: "object", default: null },
493
+ { name: "cachedAt", type: "number", default: 0 },
494
+ { name: "ttlMs", type: "number", default: 3e5 },
495
+ { name: "cacheHits", type: "number", default: 0 },
496
+ { name: "cacheMisses", type: "number", default: 0 },
497
+ { name: "isFresh", type: "boolean", default: false },
498
+ { name: "lastAccessed", type: "number", default: 0 }
499
+ ]
500
+ },
501
+ traits: [
502
+ {
503
+ name: "CacheAside",
504
+ linkedEntity: "CacheEntry",
505
+ category: "lifecycle",
506
+ emits: [
507
+ { event: "CACHE_HIT", scope: "internal" },
508
+ { event: "CACHE_MISS", scope: "internal" },
509
+ { event: "CACHE_EVICTED", scope: "internal" }
510
+ ],
511
+ stateMachine: {
512
+ states: [
513
+ { name: "Empty", isInitial: true },
514
+ { name: "Fresh" },
515
+ { name: "Stale" }
516
+ ],
517
+ events: [
518
+ { key: "LOOKUP", name: "Cache Lookup" },
519
+ { key: "POPULATE", name: "Populate Cache" },
520
+ { key: "INVALIDATE", name: "Invalidate" },
521
+ { key: "EVICT", name: "Evict" },
522
+ { key: "EVICTION_TICK", name: "Eviction Tick" }
523
+ ],
524
+ transitions: [
525
+ // Empty: lookup is a miss
526
+ {
527
+ from: "Empty",
528
+ to: "Empty",
529
+ event: "LOOKUP",
530
+ effects: [
531
+ ["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
532
+ ["set", "@entity.lastAccessed", ["time/now"]],
533
+ ["emit", "CACHE_MISS", { key: "@entity.cacheKey" }]
534
+ ]
535
+ },
536
+ // Empty → Fresh: populate after fetch
537
+ {
538
+ from: "Empty",
539
+ to: "Fresh",
540
+ event: "POPULATE",
541
+ effects: [
542
+ ["set", "@entity.cachedValue", "@payload.value"],
543
+ ["set", "@entity.cacheKey", "@payload.key"],
544
+ ["set", "@entity.cachedAt", ["time/now"]],
545
+ ["set", "@entity.isFresh", true]
546
+ ]
547
+ },
548
+ // Fresh: lookup is a hit
549
+ {
550
+ from: "Fresh",
551
+ to: "Fresh",
552
+ event: "LOOKUP",
553
+ guard: ["<", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"],
554
+ effects: [
555
+ ["set", "@entity.cacheHits", ["+", "@entity.cacheHits", 1]],
556
+ ["set", "@entity.lastAccessed", ["time/now"]],
557
+ ["emit", "CACHE_HIT", { key: "@entity.cacheKey" }]
558
+ ]
559
+ },
560
+ // Fresh → Stale: TTL expired on lookup
561
+ {
562
+ from: "Fresh",
563
+ to: "Stale",
564
+ event: "LOOKUP",
565
+ guard: [">=", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"],
566
+ effects: [
567
+ ["set", "@entity.isFresh", false],
568
+ ["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
569
+ ["set", "@entity.lastAccessed", ["time/now"]],
570
+ ["emit", "CACHE_MISS", { key: "@entity.cacheKey", reason: "ttl_expired" }]
571
+ ]
572
+ },
573
+ // Stale: lookup is a miss
574
+ {
575
+ from: "Stale",
576
+ to: "Stale",
577
+ event: "LOOKUP",
578
+ effects: [
579
+ ["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
580
+ ["set", "@entity.lastAccessed", ["time/now"]],
581
+ ["emit", "CACHE_MISS", { key: "@entity.cacheKey", reason: "stale" }]
582
+ ]
583
+ },
584
+ // Stale → Fresh: re-populate
585
+ {
586
+ from: "Stale",
587
+ to: "Fresh",
588
+ event: "POPULATE",
589
+ effects: [
590
+ ["set", "@entity.cachedValue", "@payload.value"],
591
+ ["set", "@entity.cachedAt", ["time/now"]],
592
+ ["set", "@entity.isFresh", true]
593
+ ]
594
+ },
595
+ // Fresh → Fresh: update cache
596
+ {
597
+ from: "Fresh",
598
+ to: "Fresh",
599
+ event: "POPULATE",
600
+ effects: [
601
+ ["set", "@entity.cachedValue", "@payload.value"],
602
+ ["set", "@entity.cachedAt", ["time/now"]]
603
+ ]
604
+ },
605
+ // Invalidate from any cached state
606
+ {
607
+ from: ["Fresh", "Stale"],
608
+ to: "Empty",
609
+ event: "INVALIDATE",
610
+ effects: [
611
+ ["set", "@entity.cachedValue", null],
612
+ ["set", "@entity.isFresh", false],
613
+ ["set", "@entity.cachedAt", 0]
614
+ ]
615
+ },
616
+ // Evict (with event)
617
+ {
618
+ from: ["Fresh", "Stale"],
619
+ to: "Empty",
620
+ event: "EVICT",
621
+ effects: [
622
+ ["set", "@entity.cachedValue", null],
623
+ ["set", "@entity.isFresh", false],
624
+ ["set", "@entity.cachedAt", 0],
625
+ ["emit", "CACHE_EVICTED", { key: "@entity.cacheKey" }]
626
+ ]
627
+ },
628
+ // Eviction tick: evict if stale
629
+ {
630
+ from: "Stale",
631
+ to: "Empty",
632
+ event: "EVICTION_TICK",
633
+ effects: [
634
+ ["set", "@entity.cachedValue", null],
635
+ ["set", "@entity.isFresh", false],
636
+ ["set", "@entity.cachedAt", 0],
637
+ ["emit", "CACHE_EVICTED", { key: "@entity.cacheKey", reason: "ttl_eviction" }]
638
+ ]
639
+ }
640
+ ]
641
+ },
642
+ ticks: [
643
+ {
644
+ name: "eviction_sweep",
645
+ interval: "60000",
646
+ guard: ["and", ["!=", "@entity.cachedAt", 0], [">=", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"]],
647
+ effects: [["emit", "EVICTION_TICK"]],
648
+ description: "Periodically evict stale cache entries"
649
+ }
650
+ ]
651
+ }
652
+ ],
653
+ pages: []
654
+ }
655
+ ]
656
+ };
657
+ var SAGA_BEHAVIOR = {
658
+ name: "std-saga",
659
+ version: "1.0.0",
660
+ description: "Saga pattern with step-by-step execution and reverse compensation on failure",
661
+ orbitals: [
662
+ {
663
+ name: "SagaOrbital",
664
+ entity: {
665
+ name: "SagaState",
666
+ persistence: "runtime",
667
+ fields: [
668
+ { name: "id", type: "string", required: true },
669
+ { name: "sagaName", type: "string", default: "" },
670
+ { name: "currentStep", type: "number", default: 0 },
671
+ { name: "totalSteps", type: "number", default: 0 },
672
+ { name: "sagaStatus", type: "string", default: "idle" },
673
+ { name: "completedSteps", type: "array", default: [] },
674
+ { name: "compensatedSteps", type: "array", default: [] },
675
+ { name: "failedStep", type: "number", default: -1 },
676
+ { name: "failureReason", type: "string", default: "" },
677
+ { name: "startedAt", type: "number", default: 0 },
678
+ { name: "completedAt", type: "number", default: 0 }
679
+ ]
680
+ },
681
+ traits: [
682
+ {
683
+ name: "Saga",
684
+ linkedEntity: "SagaState",
685
+ category: "lifecycle",
686
+ emits: [
687
+ { event: "SAGA_STARTED", scope: "external" },
688
+ { event: "SAGA_STEP_COMPLETED", scope: "external" },
689
+ { event: "SAGA_COMPLETED", scope: "external" },
690
+ { event: "SAGA_COMPENSATING", scope: "external" },
691
+ { event: "SAGA_COMPENSATION_DONE", scope: "external" },
692
+ { event: "SAGA_FAILED", scope: "external" }
693
+ ],
694
+ stateMachine: {
695
+ states: [
696
+ { name: "Idle", isInitial: true },
697
+ { name: "Running" },
698
+ { name: "Compensating" },
699
+ { name: "Completed" },
700
+ { name: "Failed" }
701
+ ],
702
+ events: [
703
+ { key: "START_SAGA", name: "Start Saga" },
704
+ { key: "STEP_SUCCESS", name: "Step Success" },
705
+ { key: "STEP_FAILURE", name: "Step Failure" },
706
+ { key: "COMPENSATE_SUCCESS", name: "Compensate Success" },
707
+ { key: "COMPENSATE_FAILURE", name: "Compensate Failure" },
708
+ { key: "RESET", name: "Reset" }
709
+ ],
710
+ transitions: [
711
+ // Idle → Running: start the saga
712
+ {
713
+ from: "Idle",
714
+ to: "Running",
715
+ event: "START_SAGA",
716
+ effects: [
717
+ ["set", "@entity.sagaStatus", "running"],
718
+ ["set", "@entity.currentStep", 0],
719
+ ["set", "@entity.completedSteps", []],
720
+ ["set", "@entity.compensatedSteps", []],
721
+ ["set", "@entity.failedStep", -1],
722
+ ["set", "@entity.failureReason", ""],
723
+ ["set", "@entity.startedAt", ["time/now"]],
724
+ ["emit", "SAGA_STARTED", { sagaName: "@entity.sagaName" }]
725
+ ]
726
+ },
727
+ // Running: step success, more steps remaining
728
+ {
729
+ from: "Running",
730
+ to: "Running",
731
+ event: "STEP_SUCCESS",
732
+ guard: ["<", ["+", "@entity.currentStep", 1], "@entity.totalSteps"],
733
+ effects: [
734
+ ["set", "@entity.currentStep", ["+", "@entity.currentStep", 1]],
735
+ ["emit", "SAGA_STEP_COMPLETED", {
736
+ step: "@entity.currentStep",
737
+ totalSteps: "@entity.totalSteps"
738
+ }]
739
+ ]
740
+ },
741
+ // Running → Completed: last step succeeded
742
+ {
743
+ from: "Running",
744
+ to: "Completed",
745
+ event: "STEP_SUCCESS",
746
+ guard: [">=", ["+", "@entity.currentStep", 1], "@entity.totalSteps"],
747
+ effects: [
748
+ ["set", "@entity.sagaStatus", "completed"],
749
+ ["set", "@entity.completedAt", ["time/now"]],
750
+ ["emit", "SAGA_COMPLETED", { sagaName: "@entity.sagaName" }]
751
+ ]
752
+ },
753
+ // Running → Compensating: a step failed
754
+ {
755
+ from: "Running",
756
+ to: "Compensating",
757
+ event: "STEP_FAILURE",
758
+ effects: [
759
+ ["set", "@entity.sagaStatus", "compensating"],
760
+ ["set", "@entity.failedStep", "@entity.currentStep"],
761
+ ["emit", "SAGA_COMPENSATING", {
762
+ failedStep: "@entity.currentStep",
763
+ sagaName: "@entity.sagaName"
764
+ }]
765
+ ]
766
+ },
767
+ // Compensating: compensation step succeeded, more to undo
768
+ {
769
+ from: "Compensating",
770
+ to: "Compensating",
771
+ event: "COMPENSATE_SUCCESS",
772
+ guard: [">", "@entity.currentStep", 0],
773
+ effects: [
774
+ ["set", "@entity.currentStep", ["-", "@entity.currentStep", 1]]
775
+ ]
776
+ },
777
+ // Compensating → Failed: all compensations done (reached step 0)
778
+ {
779
+ from: "Compensating",
780
+ to: "Failed",
781
+ event: "COMPENSATE_SUCCESS",
782
+ guard: ["<=", "@entity.currentStep", 0],
783
+ effects: [
784
+ ["set", "@entity.sagaStatus", "failed"],
785
+ ["set", "@entity.completedAt", ["time/now"]],
786
+ ["emit", "SAGA_COMPENSATION_DONE", { sagaName: "@entity.sagaName" }]
787
+ ]
788
+ },
789
+ // Compensating → Failed: compensation itself failed
790
+ {
791
+ from: "Compensating",
792
+ to: "Failed",
793
+ event: "COMPENSATE_FAILURE",
794
+ effects: [
795
+ ["set", "@entity.sagaStatus", "failed"],
796
+ ["set", "@entity.completedAt", ["time/now"]],
797
+ ["emit", "SAGA_FAILED", {
798
+ sagaName: "@entity.sagaName",
799
+ reason: "Compensation failed"
800
+ }]
801
+ ]
802
+ },
803
+ // Reset from terminal states
804
+ {
805
+ from: ["Completed", "Failed"],
806
+ to: "Idle",
807
+ event: "RESET",
808
+ effects: [
809
+ ["set", "@entity.sagaStatus", "idle"],
810
+ ["set", "@entity.currentStep", 0],
811
+ ["set", "@entity.completedSteps", []],
812
+ ["set", "@entity.compensatedSteps", []],
813
+ ["set", "@entity.failedStep", -1],
814
+ ["set", "@entity.failureReason", ""]
815
+ ]
816
+ }
817
+ ]
818
+ },
819
+ ticks: []
820
+ }
821
+ ],
822
+ pages: []
823
+ }
824
+ ]
825
+ };
826
+ var METRICS_COLLECTOR_BEHAVIOR = {
827
+ name: "std-metrics-collector",
828
+ version: "1.0.0",
829
+ description: "Tick-based metrics aggregation with periodic flush and reporting",
830
+ orbitals: [
831
+ {
832
+ name: "MetricsCollectorOrbital",
833
+ entity: {
834
+ name: "MetricsState",
835
+ persistence: "runtime",
836
+ fields: [
837
+ { name: "id", type: "string", required: true },
838
+ { name: "counters", type: "object", default: {} },
839
+ { name: "gauges", type: "object", default: {} },
840
+ { name: "lastFlush", type: "number", default: 0 },
841
+ { name: "flushIntervalMs", type: "number", default: 6e4 },
842
+ { name: "totalFlushes", type: "number", default: 0 },
843
+ { name: "totalRecorded", type: "number", default: 0 }
844
+ ]
845
+ },
846
+ traits: [
847
+ {
848
+ name: "MetricsCollector",
849
+ linkedEntity: "MetricsState",
850
+ category: "lifecycle",
851
+ emits: [
852
+ { event: "METRICS_REPORT", scope: "external" }
853
+ ],
854
+ stateMachine: {
855
+ states: [
856
+ { name: "Collecting", isInitial: true }
857
+ ],
858
+ events: [
859
+ { key: "RECORD_COUNTER", name: "Record Counter" },
860
+ { key: "RECORD_GAUGE", name: "Record Gauge" },
861
+ { key: "FLUSH", name: "Flush Metrics" },
862
+ { key: "RESET", name: "Reset All" }
863
+ ],
864
+ transitions: [
865
+ // Record a counter increment
866
+ {
867
+ from: "Collecting",
868
+ to: "Collecting",
869
+ event: "RECORD_COUNTER",
870
+ effects: [
871
+ ["set", "@entity.totalRecorded", ["+", "@entity.totalRecorded", 1]]
872
+ ]
873
+ },
874
+ // Record a gauge value
875
+ {
876
+ from: "Collecting",
877
+ to: "Collecting",
878
+ event: "RECORD_GAUGE",
879
+ effects: [
880
+ ["set", "@entity.totalRecorded", ["+", "@entity.totalRecorded", 1]]
881
+ ]
882
+ },
883
+ // Flush: emit report and reset counters
884
+ {
885
+ from: "Collecting",
886
+ to: "Collecting",
887
+ event: "FLUSH",
888
+ effects: [
889
+ ["emit", "METRICS_REPORT", {
890
+ counters: "@entity.counters",
891
+ gauges: "@entity.gauges",
892
+ totalRecorded: "@entity.totalRecorded"
893
+ }],
894
+ ["set", "@entity.counters", {}],
895
+ ["set", "@entity.lastFlush", ["time/now"]],
896
+ ["set", "@entity.totalFlushes", ["+", "@entity.totalFlushes", 1]]
897
+ ]
898
+ },
899
+ // Full reset
900
+ {
901
+ from: "Collecting",
902
+ to: "Collecting",
903
+ event: "RESET",
904
+ effects: [
905
+ ["set", "@entity.counters", {}],
906
+ ["set", "@entity.gauges", {}],
907
+ ["set", "@entity.totalRecorded", 0],
908
+ ["set", "@entity.totalFlushes", 0],
909
+ ["set", "@entity.lastFlush", 0]
910
+ ]
911
+ }
912
+ ]
913
+ },
914
+ ticks: [
915
+ {
916
+ name: "periodic_flush",
917
+ interval: "@entity.flushIntervalMs",
918
+ guard: [">", "@entity.totalRecorded", 0],
919
+ effects: [["emit", "FLUSH"]],
920
+ description: "Periodically flush accumulated metrics"
921
+ }
922
+ ]
923
+ }
924
+ ],
925
+ pages: []
926
+ }
927
+ ]
928
+ };
929
+ var INFRASTRUCTURE_BEHAVIORS = [
930
+ CIRCUIT_BREAKER_BEHAVIOR,
931
+ HEALTH_CHECK_BEHAVIOR,
932
+ RATE_LIMITER_BEHAVIOR,
933
+ CACHE_ASIDE_BEHAVIOR,
934
+ SAGA_BEHAVIOR,
935
+ METRICS_COLLECTOR_BEHAVIOR
936
+ ];
937
+
938
+ export { CACHE_ASIDE_BEHAVIOR, CIRCUIT_BREAKER_BEHAVIOR, HEALTH_CHECK_BEHAVIOR, INFRASTRUCTURE_BEHAVIORS, METRICS_COLLECTOR_BEHAVIOR, RATE_LIMITER_BEHAVIOR, SAGA_BEHAVIOR };
939
+ //# sourceMappingURL=infrastructure.js.map
940
+ //# sourceMappingURL=infrastructure.js.map