@almadar/std 1.0.15 → 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/LICENSE +21 -72
- package/README.md +25 -0
- package/dist/behaviors/index.d.ts +1 -0
- package/dist/behaviors/index.js +940 -2
- package/dist/behaviors/index.js.map +1 -1
- package/dist/behaviors/infrastructure.d.ts +21 -0
- package/dist/behaviors/infrastructure.js +940 -0
- package/dist/behaviors/infrastructure.js.map +1 -0
- package/dist/behaviors/registry.js +939 -1
- package/dist/behaviors/registry.js.map +1 -1
- package/dist/index.js +961 -6
- package/dist/index.js.map +1 -1
- package/dist/modules/async.js +22 -5
- package/dist/modules/async.js.map +1 -1
- package/dist/modules/index.js +22 -5
- package/dist/modules/index.js.map +1 -1
- package/dist/registry.js +22 -5
- package/dist/registry.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -2142,12 +2142,29 @@ var ASYNC_OPERATORS = {
|
|
|
2142
2142
|
module: "async",
|
|
2143
2143
|
category: "std-async",
|
|
2144
2144
|
minArity: 1,
|
|
2145
|
-
maxArity:
|
|
2146
|
-
description: "Wait for specified milliseconds",
|
|
2145
|
+
maxArity: 2,
|
|
2146
|
+
description: "Wait for specified milliseconds, optionally execute an effect after",
|
|
2147
2147
|
hasSideEffects: true,
|
|
2148
|
-
returnType: "
|
|
2149
|
-
params: [
|
|
2150
|
-
|
|
2148
|
+
returnType: "any",
|
|
2149
|
+
params: [
|
|
2150
|
+
{ name: "ms", type: "number", description: "Milliseconds to wait" },
|
|
2151
|
+
{ name: "effect", type: "expression", description: "Optional effect to execute after delay" }
|
|
2152
|
+
],
|
|
2153
|
+
example: '["async/delay", 2000, ["emit", "RETRY"]] // Wait 2s then emit'
|
|
2154
|
+
},
|
|
2155
|
+
"async/interval": {
|
|
2156
|
+
module: "async",
|
|
2157
|
+
category: "std-async",
|
|
2158
|
+
minArity: 2,
|
|
2159
|
+
maxArity: 2,
|
|
2160
|
+
description: "Execute an effect periodically at a fixed interval",
|
|
2161
|
+
hasSideEffects: true,
|
|
2162
|
+
returnType: "string",
|
|
2163
|
+
params: [
|
|
2164
|
+
{ name: "ms", type: "number", description: "Interval in milliseconds" },
|
|
2165
|
+
{ name: "effect", type: "expression", description: "Effect to execute each interval" }
|
|
2166
|
+
],
|
|
2167
|
+
example: '["async/interval", 5000, ["emit", "POLL_TICK"]] // Emit every 5s'
|
|
2151
2168
|
},
|
|
2152
2169
|
"async/timeout": {
|
|
2153
2170
|
module: "async",
|
|
@@ -7696,6 +7713,943 @@ var GAME_UI_BEHAVIORS = [
|
|
|
7696
7713
|
LEVEL_PROGRESS_BEHAVIOR
|
|
7697
7714
|
];
|
|
7698
7715
|
|
|
7716
|
+
// behaviors/infrastructure.ts
|
|
7717
|
+
var CIRCUIT_BREAKER_BEHAVIOR = {
|
|
7718
|
+
name: "std-circuit-breaker",
|
|
7719
|
+
version: "1.0.0",
|
|
7720
|
+
description: "Circuit breaker pattern with automatic recovery",
|
|
7721
|
+
orbitals: [
|
|
7722
|
+
{
|
|
7723
|
+
name: "CircuitBreakerOrbital",
|
|
7724
|
+
entity: {
|
|
7725
|
+
name: "CircuitBreakerState",
|
|
7726
|
+
persistence: "runtime",
|
|
7727
|
+
fields: [
|
|
7728
|
+
{ name: "id", type: "string", required: true },
|
|
7729
|
+
{ name: "circuitState", type: "string", default: "closed" },
|
|
7730
|
+
{ name: "errorCount", type: "number", default: 0 },
|
|
7731
|
+
{ name: "errorRate", type: "number", default: 0 },
|
|
7732
|
+
{ name: "successCount", type: "number", default: 0 },
|
|
7733
|
+
{ name: "totalCount", type: "number", default: 0 },
|
|
7734
|
+
{ name: "lastFailure", type: "number", default: null },
|
|
7735
|
+
{ name: "lastSuccess", type: "number", default: null },
|
|
7736
|
+
{ name: "errorThreshold", type: "number", default: 5 },
|
|
7737
|
+
{ name: "errorRateThreshold", type: "number", default: 0.5 },
|
|
7738
|
+
{ name: "resetAfterMs", type: "number", default: 6e4 },
|
|
7739
|
+
{ name: "halfOpenMaxAttempts", type: "number", default: 3 },
|
|
7740
|
+
{ name: "halfOpenAttempts", type: "number", default: 0 }
|
|
7741
|
+
]
|
|
7742
|
+
},
|
|
7743
|
+
traits: [
|
|
7744
|
+
{
|
|
7745
|
+
name: "CircuitBreaker",
|
|
7746
|
+
linkedEntity: "CircuitBreakerState",
|
|
7747
|
+
category: "lifecycle",
|
|
7748
|
+
emits: [
|
|
7749
|
+
{ event: "CIRCUIT_OPENED", scope: "external" },
|
|
7750
|
+
{ event: "CIRCUIT_CLOSED", scope: "external" },
|
|
7751
|
+
{ event: "CIRCUIT_HALF_OPEN", scope: "external" }
|
|
7752
|
+
],
|
|
7753
|
+
stateMachine: {
|
|
7754
|
+
states: [
|
|
7755
|
+
{ name: "Closed", isInitial: true },
|
|
7756
|
+
{ name: "Open" },
|
|
7757
|
+
{ name: "HalfOpen" }
|
|
7758
|
+
],
|
|
7759
|
+
events: [
|
|
7760
|
+
{ key: "RECORD_SUCCESS", name: "Record Success" },
|
|
7761
|
+
{ key: "RECORD_FAILURE", name: "Record Failure" },
|
|
7762
|
+
{ key: "PROBE", name: "Probe" },
|
|
7763
|
+
{ key: "RESET", name: "Reset" }
|
|
7764
|
+
],
|
|
7765
|
+
transitions: [
|
|
7766
|
+
// Closed: record success
|
|
7767
|
+
{
|
|
7768
|
+
from: "Closed",
|
|
7769
|
+
to: "Closed",
|
|
7770
|
+
event: "RECORD_SUCCESS",
|
|
7771
|
+
effects: [
|
|
7772
|
+
["set", "@entity.successCount", ["+", "@entity.successCount", 1]],
|
|
7773
|
+
["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
|
|
7774
|
+
["set", "@entity.lastSuccess", ["time/now"]],
|
|
7775
|
+
["set", "@entity.errorRate", ["/", "@entity.errorCount", ["math/max", "@entity.totalCount", 1]]]
|
|
7776
|
+
]
|
|
7777
|
+
},
|
|
7778
|
+
// Closed: record failure, stay closed if under threshold
|
|
7779
|
+
{
|
|
7780
|
+
from: "Closed",
|
|
7781
|
+
to: "Closed",
|
|
7782
|
+
event: "RECORD_FAILURE",
|
|
7783
|
+
guard: ["<", ["+", "@entity.errorCount", 1], "@entity.errorThreshold"],
|
|
7784
|
+
effects: [
|
|
7785
|
+
["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
|
|
7786
|
+
["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
|
|
7787
|
+
["set", "@entity.lastFailure", ["time/now"]],
|
|
7788
|
+
["set", "@entity.errorRate", ["/", ["+", "@entity.errorCount", 1], ["math/max", "@entity.totalCount", 1]]]
|
|
7789
|
+
]
|
|
7790
|
+
},
|
|
7791
|
+
// Closed -> Open: threshold exceeded
|
|
7792
|
+
{
|
|
7793
|
+
from: "Closed",
|
|
7794
|
+
to: "Open",
|
|
7795
|
+
event: "RECORD_FAILURE",
|
|
7796
|
+
guard: [">=", ["+", "@entity.errorCount", 1], "@entity.errorThreshold"],
|
|
7797
|
+
effects: [
|
|
7798
|
+
["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
|
|
7799
|
+
["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
|
|
7800
|
+
["set", "@entity.lastFailure", ["time/now"]],
|
|
7801
|
+
["set", "@entity.errorRate", ["/", ["+", "@entity.errorCount", 1], ["math/max", "@entity.totalCount", 1]]],
|
|
7802
|
+
["emit", "CIRCUIT_OPENED", { errorCount: "@entity.errorCount", errorRate: "@entity.errorRate" }]
|
|
7803
|
+
]
|
|
7804
|
+
},
|
|
7805
|
+
// Open -> HalfOpen: probe after reset timeout
|
|
7806
|
+
{
|
|
7807
|
+
from: "Open",
|
|
7808
|
+
to: "HalfOpen",
|
|
7809
|
+
event: "PROBE",
|
|
7810
|
+
guard: [">", ["-", ["time/now"], "@entity.lastFailure"], "@entity.resetAfterMs"],
|
|
7811
|
+
effects: [
|
|
7812
|
+
["set", "@entity.halfOpenAttempts", 0],
|
|
7813
|
+
["emit", "CIRCUIT_HALF_OPEN", {}]
|
|
7814
|
+
]
|
|
7815
|
+
},
|
|
7816
|
+
// HalfOpen: success -> close
|
|
7817
|
+
{
|
|
7818
|
+
from: "HalfOpen",
|
|
7819
|
+
to: "Closed",
|
|
7820
|
+
event: "RECORD_SUCCESS",
|
|
7821
|
+
effects: [
|
|
7822
|
+
["set", "@entity.errorCount", 0],
|
|
7823
|
+
["set", "@entity.errorRate", 0],
|
|
7824
|
+
["set", "@entity.halfOpenAttempts", 0],
|
|
7825
|
+
["set", "@entity.successCount", ["+", "@entity.successCount", 1]],
|
|
7826
|
+
["set", "@entity.lastSuccess", ["time/now"]],
|
|
7827
|
+
["emit", "CIRCUIT_CLOSED", {}]
|
|
7828
|
+
]
|
|
7829
|
+
},
|
|
7830
|
+
// HalfOpen: failure -> back to open
|
|
7831
|
+
{
|
|
7832
|
+
from: "HalfOpen",
|
|
7833
|
+
to: "Open",
|
|
7834
|
+
event: "RECORD_FAILURE",
|
|
7835
|
+
effects: [
|
|
7836
|
+
["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
|
|
7837
|
+
["set", "@entity.lastFailure", ["time/now"]],
|
|
7838
|
+
["emit", "CIRCUIT_OPENED", { errorCount: "@entity.errorCount" }]
|
|
7839
|
+
]
|
|
7840
|
+
},
|
|
7841
|
+
// Reset from any state
|
|
7842
|
+
{
|
|
7843
|
+
from: ["Closed", "Open", "HalfOpen"],
|
|
7844
|
+
to: "Closed",
|
|
7845
|
+
event: "RESET",
|
|
7846
|
+
effects: [
|
|
7847
|
+
["set", "@entity.errorCount", 0],
|
|
7848
|
+
["set", "@entity.successCount", 0],
|
|
7849
|
+
["set", "@entity.totalCount", 0],
|
|
7850
|
+
["set", "@entity.errorRate", 0],
|
|
7851
|
+
["set", "@entity.halfOpenAttempts", 0],
|
|
7852
|
+
["set", "@entity.circuitState", "closed"]
|
|
7853
|
+
]
|
|
7854
|
+
}
|
|
7855
|
+
]
|
|
7856
|
+
},
|
|
7857
|
+
ticks: [
|
|
7858
|
+
{
|
|
7859
|
+
name: "probe_half_open",
|
|
7860
|
+
interval: "30000",
|
|
7861
|
+
guard: ["=", "@entity.circuitState", "open"],
|
|
7862
|
+
effects: [["emit", "PROBE"]],
|
|
7863
|
+
description: "Periodically probe to transition from Open to HalfOpen"
|
|
7864
|
+
}
|
|
7865
|
+
]
|
|
7866
|
+
}
|
|
7867
|
+
],
|
|
7868
|
+
pages: []
|
|
7869
|
+
}
|
|
7870
|
+
]
|
|
7871
|
+
};
|
|
7872
|
+
var HEALTH_CHECK_BEHAVIOR = {
|
|
7873
|
+
name: "std-health-check",
|
|
7874
|
+
version: "1.0.0",
|
|
7875
|
+
description: "Tick-based health monitoring with degradation detection",
|
|
7876
|
+
orbitals: [
|
|
7877
|
+
{
|
|
7878
|
+
name: "HealthCheckOrbital",
|
|
7879
|
+
entity: {
|
|
7880
|
+
name: "HealthCheckState",
|
|
7881
|
+
persistence: "runtime",
|
|
7882
|
+
fields: [
|
|
7883
|
+
{ name: "id", type: "string", required: true },
|
|
7884
|
+
{ name: "healthStatus", type: "string", default: "unknown" },
|
|
7885
|
+
{ name: "lastCheck", type: "number", default: null },
|
|
7886
|
+
{ name: "lastHealthy", type: "number", default: null },
|
|
7887
|
+
{ name: "consecutiveFailures", type: "number", default: 0 },
|
|
7888
|
+
{ name: "consecutiveSuccesses", type: "number", default: 0 },
|
|
7889
|
+
{ name: "checkIntervalMs", type: "number", default: 3e4 },
|
|
7890
|
+
{ name: "degradedThreshold", type: "number", default: 2 },
|
|
7891
|
+
{ name: "unhealthyThreshold", type: "number", default: 5 },
|
|
7892
|
+
{ name: "recoveryThreshold", type: "number", default: 3 },
|
|
7893
|
+
{ name: "totalChecks", type: "number", default: 0 },
|
|
7894
|
+
{ name: "totalFailures", type: "number", default: 0 }
|
|
7895
|
+
]
|
|
7896
|
+
},
|
|
7897
|
+
traits: [
|
|
7898
|
+
{
|
|
7899
|
+
name: "HealthCheck",
|
|
7900
|
+
linkedEntity: "HealthCheckState",
|
|
7901
|
+
category: "lifecycle",
|
|
7902
|
+
emits: [
|
|
7903
|
+
{ event: "SERVICE_HEALTHY", scope: "external" },
|
|
7904
|
+
{ event: "SERVICE_DEGRADED", scope: "external" },
|
|
7905
|
+
{ event: "SERVICE_UNHEALTHY", scope: "external" }
|
|
7906
|
+
],
|
|
7907
|
+
stateMachine: {
|
|
7908
|
+
states: [
|
|
7909
|
+
{ name: "Unknown", isInitial: true },
|
|
7910
|
+
{ name: "Healthy" },
|
|
7911
|
+
{ name: "Degraded" },
|
|
7912
|
+
{ name: "Unhealthy" }
|
|
7913
|
+
],
|
|
7914
|
+
events: [
|
|
7915
|
+
{ key: "CHECK_SUCCESS", name: "Check Success" },
|
|
7916
|
+
{ key: "CHECK_FAILURE", name: "Check Failure" },
|
|
7917
|
+
{ key: "HEALTH_TICK", name: "Health Tick" },
|
|
7918
|
+
{ key: "RESET", name: "Reset" }
|
|
7919
|
+
],
|
|
7920
|
+
transitions: [
|
|
7921
|
+
// Unknown -> Healthy on first success
|
|
7922
|
+
{
|
|
7923
|
+
from: "Unknown",
|
|
7924
|
+
to: "Healthy",
|
|
7925
|
+
event: "CHECK_SUCCESS",
|
|
7926
|
+
effects: [
|
|
7927
|
+
["set", "@entity.healthStatus", "healthy"],
|
|
7928
|
+
["set", "@entity.consecutiveSuccesses", 1],
|
|
7929
|
+
["set", "@entity.consecutiveFailures", 0],
|
|
7930
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
7931
|
+
["set", "@entity.lastHealthy", ["time/now"]],
|
|
7932
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
7933
|
+
["emit", "SERVICE_HEALTHY", {}]
|
|
7934
|
+
]
|
|
7935
|
+
},
|
|
7936
|
+
// Unknown -> Degraded on first failure
|
|
7937
|
+
{
|
|
7938
|
+
from: "Unknown",
|
|
7939
|
+
to: "Degraded",
|
|
7940
|
+
event: "CHECK_FAILURE",
|
|
7941
|
+
effects: [
|
|
7942
|
+
["set", "@entity.healthStatus", "degraded"],
|
|
7943
|
+
["set", "@entity.consecutiveFailures", 1],
|
|
7944
|
+
["set", "@entity.consecutiveSuccesses", 0],
|
|
7945
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
7946
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
7947
|
+
["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
|
|
7948
|
+
["emit", "SERVICE_DEGRADED", { consecutiveFailures: 1 }]
|
|
7949
|
+
]
|
|
7950
|
+
},
|
|
7951
|
+
// Healthy: stay healthy on success
|
|
7952
|
+
{
|
|
7953
|
+
from: "Healthy",
|
|
7954
|
+
to: "Healthy",
|
|
7955
|
+
event: "CHECK_SUCCESS",
|
|
7956
|
+
effects: [
|
|
7957
|
+
["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
|
|
7958
|
+
["set", "@entity.consecutiveFailures", 0],
|
|
7959
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
7960
|
+
["set", "@entity.lastHealthy", ["time/now"]],
|
|
7961
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]]
|
|
7962
|
+
]
|
|
7963
|
+
},
|
|
7964
|
+
// Healthy -> Degraded on failure
|
|
7965
|
+
{
|
|
7966
|
+
from: "Healthy",
|
|
7967
|
+
to: "Degraded",
|
|
7968
|
+
event: "CHECK_FAILURE",
|
|
7969
|
+
effects: [
|
|
7970
|
+
["set", "@entity.healthStatus", "degraded"],
|
|
7971
|
+
["set", "@entity.consecutiveFailures", 1],
|
|
7972
|
+
["set", "@entity.consecutiveSuccesses", 0],
|
|
7973
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
7974
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
7975
|
+
["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
|
|
7976
|
+
["emit", "SERVICE_DEGRADED", { consecutiveFailures: 1 }]
|
|
7977
|
+
]
|
|
7978
|
+
},
|
|
7979
|
+
// Degraded: stay degraded on failure (below unhealthy threshold)
|
|
7980
|
+
{
|
|
7981
|
+
from: "Degraded",
|
|
7982
|
+
to: "Degraded",
|
|
7983
|
+
event: "CHECK_FAILURE",
|
|
7984
|
+
guard: ["<", ["+", "@entity.consecutiveFailures", 1], "@entity.unhealthyThreshold"],
|
|
7985
|
+
effects: [
|
|
7986
|
+
["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
|
|
7987
|
+
["set", "@entity.consecutiveSuccesses", 0],
|
|
7988
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
7989
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
7990
|
+
["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]]
|
|
7991
|
+
]
|
|
7992
|
+
},
|
|
7993
|
+
// Degraded -> Unhealthy when threshold exceeded
|
|
7994
|
+
{
|
|
7995
|
+
from: "Degraded",
|
|
7996
|
+
to: "Unhealthy",
|
|
7997
|
+
event: "CHECK_FAILURE",
|
|
7998
|
+
guard: [">=", ["+", "@entity.consecutiveFailures", 1], "@entity.unhealthyThreshold"],
|
|
7999
|
+
effects: [
|
|
8000
|
+
["set", "@entity.healthStatus", "unhealthy"],
|
|
8001
|
+
["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
|
|
8002
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
8003
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
8004
|
+
["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
|
|
8005
|
+
["emit", "SERVICE_UNHEALTHY", { consecutiveFailures: ["+", "@entity.consecutiveFailures", 1] }]
|
|
8006
|
+
]
|
|
8007
|
+
},
|
|
8008
|
+
// Degraded -> Healthy on enough successes
|
|
8009
|
+
{
|
|
8010
|
+
from: "Degraded",
|
|
8011
|
+
to: "Healthy",
|
|
8012
|
+
event: "CHECK_SUCCESS",
|
|
8013
|
+
guard: [">=", ["+", "@entity.consecutiveSuccesses", 1], "@entity.recoveryThreshold"],
|
|
8014
|
+
effects: [
|
|
8015
|
+
["set", "@entity.healthStatus", "healthy"],
|
|
8016
|
+
["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
|
|
8017
|
+
["set", "@entity.consecutiveFailures", 0],
|
|
8018
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
8019
|
+
["set", "@entity.lastHealthy", ["time/now"]],
|
|
8020
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
8021
|
+
["emit", "SERVICE_HEALTHY", {}]
|
|
8022
|
+
]
|
|
8023
|
+
},
|
|
8024
|
+
// Degraded: stay degraded on success (not enough to recover)
|
|
8025
|
+
{
|
|
8026
|
+
from: "Degraded",
|
|
8027
|
+
to: "Degraded",
|
|
8028
|
+
event: "CHECK_SUCCESS",
|
|
8029
|
+
guard: ["<", ["+", "@entity.consecutiveSuccesses", 1], "@entity.recoveryThreshold"],
|
|
8030
|
+
effects: [
|
|
8031
|
+
["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
|
|
8032
|
+
["set", "@entity.consecutiveFailures", 0],
|
|
8033
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
8034
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]]
|
|
8035
|
+
]
|
|
8036
|
+
},
|
|
8037
|
+
// Unhealthy: stay unhealthy on failure
|
|
8038
|
+
{
|
|
8039
|
+
from: "Unhealthy",
|
|
8040
|
+
to: "Unhealthy",
|
|
8041
|
+
event: "CHECK_FAILURE",
|
|
8042
|
+
effects: [
|
|
8043
|
+
["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
|
|
8044
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
8045
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
8046
|
+
["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]]
|
|
8047
|
+
]
|
|
8048
|
+
},
|
|
8049
|
+
// Unhealthy -> Degraded on first success (recovery begins)
|
|
8050
|
+
{
|
|
8051
|
+
from: "Unhealthy",
|
|
8052
|
+
to: "Degraded",
|
|
8053
|
+
event: "CHECK_SUCCESS",
|
|
8054
|
+
effects: [
|
|
8055
|
+
["set", "@entity.healthStatus", "degraded"],
|
|
8056
|
+
["set", "@entity.consecutiveSuccesses", 1],
|
|
8057
|
+
["set", "@entity.consecutiveFailures", 0],
|
|
8058
|
+
["set", "@entity.lastCheck", ["time/now"]],
|
|
8059
|
+
["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
|
|
8060
|
+
["emit", "SERVICE_DEGRADED", { recovering: true }]
|
|
8061
|
+
]
|
|
8062
|
+
},
|
|
8063
|
+
// Reset from any state
|
|
8064
|
+
{
|
|
8065
|
+
from: ["Unknown", "Healthy", "Degraded", "Unhealthy"],
|
|
8066
|
+
to: "Unknown",
|
|
8067
|
+
event: "RESET",
|
|
8068
|
+
effects: [
|
|
8069
|
+
["set", "@entity.healthStatus", "unknown"],
|
|
8070
|
+
["set", "@entity.consecutiveFailures", 0],
|
|
8071
|
+
["set", "@entity.consecutiveSuccesses", 0],
|
|
8072
|
+
["set", "@entity.totalChecks", 0],
|
|
8073
|
+
["set", "@entity.totalFailures", 0]
|
|
8074
|
+
]
|
|
8075
|
+
}
|
|
8076
|
+
]
|
|
8077
|
+
},
|
|
8078
|
+
ticks: [
|
|
8079
|
+
{
|
|
8080
|
+
name: "periodic_health_check",
|
|
8081
|
+
interval: "@entity.checkIntervalMs",
|
|
8082
|
+
effects: [["emit", "HEALTH_TICK"]],
|
|
8083
|
+
description: "Periodically trigger health check"
|
|
8084
|
+
}
|
|
8085
|
+
]
|
|
8086
|
+
}
|
|
8087
|
+
],
|
|
8088
|
+
pages: []
|
|
8089
|
+
}
|
|
8090
|
+
]
|
|
8091
|
+
};
|
|
8092
|
+
var RATE_LIMITER_BEHAVIOR = {
|
|
8093
|
+
name: "std-rate-limiter",
|
|
8094
|
+
version: "1.0.0",
|
|
8095
|
+
description: "Guard-based rate limiting with sliding window reset",
|
|
8096
|
+
orbitals: [
|
|
8097
|
+
{
|
|
8098
|
+
name: "RateLimiterOrbital",
|
|
8099
|
+
entity: {
|
|
8100
|
+
name: "RateLimiterState",
|
|
8101
|
+
persistence: "runtime",
|
|
8102
|
+
fields: [
|
|
8103
|
+
{ name: "id", type: "string", required: true },
|
|
8104
|
+
{ name: "requestCount", type: "number", default: 0 },
|
|
8105
|
+
{ name: "windowStart", type: "number", default: 0 },
|
|
8106
|
+
{ name: "rateLimit", type: "number", default: 60 },
|
|
8107
|
+
{ name: "windowMs", type: "number", default: 6e4 },
|
|
8108
|
+
{ name: "totalRequests", type: "number", default: 0 },
|
|
8109
|
+
{ name: "rejectedRequests", type: "number", default: 0 }
|
|
8110
|
+
]
|
|
8111
|
+
},
|
|
8112
|
+
traits: [
|
|
8113
|
+
{
|
|
8114
|
+
name: "RateLimiter",
|
|
8115
|
+
linkedEntity: "RateLimiterState",
|
|
8116
|
+
category: "lifecycle",
|
|
8117
|
+
emits: [
|
|
8118
|
+
{ event: "RATE_LIMIT_EXCEEDED", scope: "external" }
|
|
8119
|
+
],
|
|
8120
|
+
stateMachine: {
|
|
8121
|
+
states: [
|
|
8122
|
+
{ name: "Active", isInitial: true }
|
|
8123
|
+
],
|
|
8124
|
+
events: [
|
|
8125
|
+
{ key: "REQUEST", name: "Record Request" },
|
|
8126
|
+
{ key: "REQUEST_REJECTED", name: "Request Rejected" },
|
|
8127
|
+
{ key: "WINDOW_RESET", name: "Window Reset" },
|
|
8128
|
+
{ key: "RESET", name: "Full Reset" }
|
|
8129
|
+
],
|
|
8130
|
+
transitions: [
|
|
8131
|
+
// Request allowed
|
|
8132
|
+
{
|
|
8133
|
+
from: "Active",
|
|
8134
|
+
to: "Active",
|
|
8135
|
+
event: "REQUEST",
|
|
8136
|
+
guard: ["<", "@entity.requestCount", "@entity.rateLimit"],
|
|
8137
|
+
effects: [
|
|
8138
|
+
["set", "@entity.requestCount", ["+", "@entity.requestCount", 1]],
|
|
8139
|
+
["set", "@entity.totalRequests", ["+", "@entity.totalRequests", 1]]
|
|
8140
|
+
]
|
|
8141
|
+
},
|
|
8142
|
+
// Request rejected — over limit
|
|
8143
|
+
{
|
|
8144
|
+
from: "Active",
|
|
8145
|
+
to: "Active",
|
|
8146
|
+
event: "REQUEST",
|
|
8147
|
+
guard: [">=", "@entity.requestCount", "@entity.rateLimit"],
|
|
8148
|
+
effects: [
|
|
8149
|
+
["set", "@entity.rejectedRequests", ["+", "@entity.rejectedRequests", 1]],
|
|
8150
|
+
["emit", "RATE_LIMIT_EXCEEDED", {
|
|
8151
|
+
requestCount: "@entity.requestCount",
|
|
8152
|
+
rateLimit: "@entity.rateLimit"
|
|
8153
|
+
}]
|
|
8154
|
+
]
|
|
8155
|
+
},
|
|
8156
|
+
// Sliding window reset
|
|
8157
|
+
{
|
|
8158
|
+
from: "Active",
|
|
8159
|
+
to: "Active",
|
|
8160
|
+
event: "WINDOW_RESET",
|
|
8161
|
+
effects: [
|
|
8162
|
+
["set", "@entity.requestCount", 0],
|
|
8163
|
+
["set", "@entity.windowStart", ["time/now"]]
|
|
8164
|
+
]
|
|
8165
|
+
},
|
|
8166
|
+
// Full counter reset
|
|
8167
|
+
{
|
|
8168
|
+
from: "Active",
|
|
8169
|
+
to: "Active",
|
|
8170
|
+
event: "RESET",
|
|
8171
|
+
effects: [
|
|
8172
|
+
["set", "@entity.requestCount", 0],
|
|
8173
|
+
["set", "@entity.totalRequests", 0],
|
|
8174
|
+
["set", "@entity.rejectedRequests", 0],
|
|
8175
|
+
["set", "@entity.windowStart", ["time/now"]]
|
|
8176
|
+
]
|
|
8177
|
+
}
|
|
8178
|
+
]
|
|
8179
|
+
},
|
|
8180
|
+
ticks: [
|
|
8181
|
+
{
|
|
8182
|
+
name: "window_reset",
|
|
8183
|
+
interval: "@entity.windowMs",
|
|
8184
|
+
effects: [["emit", "WINDOW_RESET"]],
|
|
8185
|
+
description: "Reset request counter on sliding window expiry"
|
|
8186
|
+
}
|
|
8187
|
+
]
|
|
8188
|
+
}
|
|
8189
|
+
],
|
|
8190
|
+
pages: []
|
|
8191
|
+
}
|
|
8192
|
+
]
|
|
8193
|
+
};
|
|
8194
|
+
var CACHE_ASIDE_BEHAVIOR = {
|
|
8195
|
+
name: "std-cache-aside",
|
|
8196
|
+
version: "1.0.0",
|
|
8197
|
+
description: "Cache-aside pattern with TTL-based freshness and eviction",
|
|
8198
|
+
orbitals: [
|
|
8199
|
+
{
|
|
8200
|
+
name: "CacheAsideOrbital",
|
|
8201
|
+
entity: {
|
|
8202
|
+
name: "CacheEntry",
|
|
8203
|
+
persistence: "runtime",
|
|
8204
|
+
fields: [
|
|
8205
|
+
{ name: "id", type: "string", required: true },
|
|
8206
|
+
{ name: "cacheKey", type: "string", default: "" },
|
|
8207
|
+
{ name: "cachedValue", type: "object", default: null },
|
|
8208
|
+
{ name: "cachedAt", type: "number", default: 0 },
|
|
8209
|
+
{ name: "ttlMs", type: "number", default: 3e5 },
|
|
8210
|
+
{ name: "cacheHits", type: "number", default: 0 },
|
|
8211
|
+
{ name: "cacheMisses", type: "number", default: 0 },
|
|
8212
|
+
{ name: "isFresh", type: "boolean", default: false },
|
|
8213
|
+
{ name: "lastAccessed", type: "number", default: 0 }
|
|
8214
|
+
]
|
|
8215
|
+
},
|
|
8216
|
+
traits: [
|
|
8217
|
+
{
|
|
8218
|
+
name: "CacheAside",
|
|
8219
|
+
linkedEntity: "CacheEntry",
|
|
8220
|
+
category: "lifecycle",
|
|
8221
|
+
emits: [
|
|
8222
|
+
{ event: "CACHE_HIT", scope: "internal" },
|
|
8223
|
+
{ event: "CACHE_MISS", scope: "internal" },
|
|
8224
|
+
{ event: "CACHE_EVICTED", scope: "internal" }
|
|
8225
|
+
],
|
|
8226
|
+
stateMachine: {
|
|
8227
|
+
states: [
|
|
8228
|
+
{ name: "Empty", isInitial: true },
|
|
8229
|
+
{ name: "Fresh" },
|
|
8230
|
+
{ name: "Stale" }
|
|
8231
|
+
],
|
|
8232
|
+
events: [
|
|
8233
|
+
{ key: "LOOKUP", name: "Cache Lookup" },
|
|
8234
|
+
{ key: "POPULATE", name: "Populate Cache" },
|
|
8235
|
+
{ key: "INVALIDATE", name: "Invalidate" },
|
|
8236
|
+
{ key: "EVICT", name: "Evict" },
|
|
8237
|
+
{ key: "EVICTION_TICK", name: "Eviction Tick" }
|
|
8238
|
+
],
|
|
8239
|
+
transitions: [
|
|
8240
|
+
// Empty: lookup is a miss
|
|
8241
|
+
{
|
|
8242
|
+
from: "Empty",
|
|
8243
|
+
to: "Empty",
|
|
8244
|
+
event: "LOOKUP",
|
|
8245
|
+
effects: [
|
|
8246
|
+
["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
|
|
8247
|
+
["set", "@entity.lastAccessed", ["time/now"]],
|
|
8248
|
+
["emit", "CACHE_MISS", { key: "@entity.cacheKey" }]
|
|
8249
|
+
]
|
|
8250
|
+
},
|
|
8251
|
+
// Empty → Fresh: populate after fetch
|
|
8252
|
+
{
|
|
8253
|
+
from: "Empty",
|
|
8254
|
+
to: "Fresh",
|
|
8255
|
+
event: "POPULATE",
|
|
8256
|
+
effects: [
|
|
8257
|
+
["set", "@entity.cachedValue", "@payload.value"],
|
|
8258
|
+
["set", "@entity.cacheKey", "@payload.key"],
|
|
8259
|
+
["set", "@entity.cachedAt", ["time/now"]],
|
|
8260
|
+
["set", "@entity.isFresh", true]
|
|
8261
|
+
]
|
|
8262
|
+
},
|
|
8263
|
+
// Fresh: lookup is a hit
|
|
8264
|
+
{
|
|
8265
|
+
from: "Fresh",
|
|
8266
|
+
to: "Fresh",
|
|
8267
|
+
event: "LOOKUP",
|
|
8268
|
+
guard: ["<", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"],
|
|
8269
|
+
effects: [
|
|
8270
|
+
["set", "@entity.cacheHits", ["+", "@entity.cacheHits", 1]],
|
|
8271
|
+
["set", "@entity.lastAccessed", ["time/now"]],
|
|
8272
|
+
["emit", "CACHE_HIT", { key: "@entity.cacheKey" }]
|
|
8273
|
+
]
|
|
8274
|
+
},
|
|
8275
|
+
// Fresh → Stale: TTL expired on lookup
|
|
8276
|
+
{
|
|
8277
|
+
from: "Fresh",
|
|
8278
|
+
to: "Stale",
|
|
8279
|
+
event: "LOOKUP",
|
|
8280
|
+
guard: [">=", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"],
|
|
8281
|
+
effects: [
|
|
8282
|
+
["set", "@entity.isFresh", false],
|
|
8283
|
+
["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
|
|
8284
|
+
["set", "@entity.lastAccessed", ["time/now"]],
|
|
8285
|
+
["emit", "CACHE_MISS", { key: "@entity.cacheKey", reason: "ttl_expired" }]
|
|
8286
|
+
]
|
|
8287
|
+
},
|
|
8288
|
+
// Stale: lookup is a miss
|
|
8289
|
+
{
|
|
8290
|
+
from: "Stale",
|
|
8291
|
+
to: "Stale",
|
|
8292
|
+
event: "LOOKUP",
|
|
8293
|
+
effects: [
|
|
8294
|
+
["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
|
|
8295
|
+
["set", "@entity.lastAccessed", ["time/now"]],
|
|
8296
|
+
["emit", "CACHE_MISS", { key: "@entity.cacheKey", reason: "stale" }]
|
|
8297
|
+
]
|
|
8298
|
+
},
|
|
8299
|
+
// Stale → Fresh: re-populate
|
|
8300
|
+
{
|
|
8301
|
+
from: "Stale",
|
|
8302
|
+
to: "Fresh",
|
|
8303
|
+
event: "POPULATE",
|
|
8304
|
+
effects: [
|
|
8305
|
+
["set", "@entity.cachedValue", "@payload.value"],
|
|
8306
|
+
["set", "@entity.cachedAt", ["time/now"]],
|
|
8307
|
+
["set", "@entity.isFresh", true]
|
|
8308
|
+
]
|
|
8309
|
+
},
|
|
8310
|
+
// Fresh → Fresh: update cache
|
|
8311
|
+
{
|
|
8312
|
+
from: "Fresh",
|
|
8313
|
+
to: "Fresh",
|
|
8314
|
+
event: "POPULATE",
|
|
8315
|
+
effects: [
|
|
8316
|
+
["set", "@entity.cachedValue", "@payload.value"],
|
|
8317
|
+
["set", "@entity.cachedAt", ["time/now"]]
|
|
8318
|
+
]
|
|
8319
|
+
},
|
|
8320
|
+
// Invalidate from any cached state
|
|
8321
|
+
{
|
|
8322
|
+
from: ["Fresh", "Stale"],
|
|
8323
|
+
to: "Empty",
|
|
8324
|
+
event: "INVALIDATE",
|
|
8325
|
+
effects: [
|
|
8326
|
+
["set", "@entity.cachedValue", null],
|
|
8327
|
+
["set", "@entity.isFresh", false],
|
|
8328
|
+
["set", "@entity.cachedAt", 0]
|
|
8329
|
+
]
|
|
8330
|
+
},
|
|
8331
|
+
// Evict (with event)
|
|
8332
|
+
{
|
|
8333
|
+
from: ["Fresh", "Stale"],
|
|
8334
|
+
to: "Empty",
|
|
8335
|
+
event: "EVICT",
|
|
8336
|
+
effects: [
|
|
8337
|
+
["set", "@entity.cachedValue", null],
|
|
8338
|
+
["set", "@entity.isFresh", false],
|
|
8339
|
+
["set", "@entity.cachedAt", 0],
|
|
8340
|
+
["emit", "CACHE_EVICTED", { key: "@entity.cacheKey" }]
|
|
8341
|
+
]
|
|
8342
|
+
},
|
|
8343
|
+
// Eviction tick: evict if stale
|
|
8344
|
+
{
|
|
8345
|
+
from: "Stale",
|
|
8346
|
+
to: "Empty",
|
|
8347
|
+
event: "EVICTION_TICK",
|
|
8348
|
+
effects: [
|
|
8349
|
+
["set", "@entity.cachedValue", null],
|
|
8350
|
+
["set", "@entity.isFresh", false],
|
|
8351
|
+
["set", "@entity.cachedAt", 0],
|
|
8352
|
+
["emit", "CACHE_EVICTED", { key: "@entity.cacheKey", reason: "ttl_eviction" }]
|
|
8353
|
+
]
|
|
8354
|
+
}
|
|
8355
|
+
]
|
|
8356
|
+
},
|
|
8357
|
+
ticks: [
|
|
8358
|
+
{
|
|
8359
|
+
name: "eviction_sweep",
|
|
8360
|
+
interval: "60000",
|
|
8361
|
+
guard: ["and", ["!=", "@entity.cachedAt", 0], [">=", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"]],
|
|
8362
|
+
effects: [["emit", "EVICTION_TICK"]],
|
|
8363
|
+
description: "Periodically evict stale cache entries"
|
|
8364
|
+
}
|
|
8365
|
+
]
|
|
8366
|
+
}
|
|
8367
|
+
],
|
|
8368
|
+
pages: []
|
|
8369
|
+
}
|
|
8370
|
+
]
|
|
8371
|
+
};
|
|
8372
|
+
var SAGA_BEHAVIOR = {
|
|
8373
|
+
name: "std-saga",
|
|
8374
|
+
version: "1.0.0",
|
|
8375
|
+
description: "Saga pattern with step-by-step execution and reverse compensation on failure",
|
|
8376
|
+
orbitals: [
|
|
8377
|
+
{
|
|
8378
|
+
name: "SagaOrbital",
|
|
8379
|
+
entity: {
|
|
8380
|
+
name: "SagaState",
|
|
8381
|
+
persistence: "runtime",
|
|
8382
|
+
fields: [
|
|
8383
|
+
{ name: "id", type: "string", required: true },
|
|
8384
|
+
{ name: "sagaName", type: "string", default: "" },
|
|
8385
|
+
{ name: "currentStep", type: "number", default: 0 },
|
|
8386
|
+
{ name: "totalSteps", type: "number", default: 0 },
|
|
8387
|
+
{ name: "sagaStatus", type: "string", default: "idle" },
|
|
8388
|
+
{ name: "completedSteps", type: "array", default: [] },
|
|
8389
|
+
{ name: "compensatedSteps", type: "array", default: [] },
|
|
8390
|
+
{ name: "failedStep", type: "number", default: -1 },
|
|
8391
|
+
{ name: "failureReason", type: "string", default: "" },
|
|
8392
|
+
{ name: "startedAt", type: "number", default: 0 },
|
|
8393
|
+
{ name: "completedAt", type: "number", default: 0 }
|
|
8394
|
+
]
|
|
8395
|
+
},
|
|
8396
|
+
traits: [
|
|
8397
|
+
{
|
|
8398
|
+
name: "Saga",
|
|
8399
|
+
linkedEntity: "SagaState",
|
|
8400
|
+
category: "lifecycle",
|
|
8401
|
+
emits: [
|
|
8402
|
+
{ event: "SAGA_STARTED", scope: "external" },
|
|
8403
|
+
{ event: "SAGA_STEP_COMPLETED", scope: "external" },
|
|
8404
|
+
{ event: "SAGA_COMPLETED", scope: "external" },
|
|
8405
|
+
{ event: "SAGA_COMPENSATING", scope: "external" },
|
|
8406
|
+
{ event: "SAGA_COMPENSATION_DONE", scope: "external" },
|
|
8407
|
+
{ event: "SAGA_FAILED", scope: "external" }
|
|
8408
|
+
],
|
|
8409
|
+
stateMachine: {
|
|
8410
|
+
states: [
|
|
8411
|
+
{ name: "Idle", isInitial: true },
|
|
8412
|
+
{ name: "Running" },
|
|
8413
|
+
{ name: "Compensating" },
|
|
8414
|
+
{ name: "Completed" },
|
|
8415
|
+
{ name: "Failed" }
|
|
8416
|
+
],
|
|
8417
|
+
events: [
|
|
8418
|
+
{ key: "START_SAGA", name: "Start Saga" },
|
|
8419
|
+
{ key: "STEP_SUCCESS", name: "Step Success" },
|
|
8420
|
+
{ key: "STEP_FAILURE", name: "Step Failure" },
|
|
8421
|
+
{ key: "COMPENSATE_SUCCESS", name: "Compensate Success" },
|
|
8422
|
+
{ key: "COMPENSATE_FAILURE", name: "Compensate Failure" },
|
|
8423
|
+
{ key: "RESET", name: "Reset" }
|
|
8424
|
+
],
|
|
8425
|
+
transitions: [
|
|
8426
|
+
// Idle → Running: start the saga
|
|
8427
|
+
{
|
|
8428
|
+
from: "Idle",
|
|
8429
|
+
to: "Running",
|
|
8430
|
+
event: "START_SAGA",
|
|
8431
|
+
effects: [
|
|
8432
|
+
["set", "@entity.sagaStatus", "running"],
|
|
8433
|
+
["set", "@entity.currentStep", 0],
|
|
8434
|
+
["set", "@entity.completedSteps", []],
|
|
8435
|
+
["set", "@entity.compensatedSteps", []],
|
|
8436
|
+
["set", "@entity.failedStep", -1],
|
|
8437
|
+
["set", "@entity.failureReason", ""],
|
|
8438
|
+
["set", "@entity.startedAt", ["time/now"]],
|
|
8439
|
+
["emit", "SAGA_STARTED", { sagaName: "@entity.sagaName" }]
|
|
8440
|
+
]
|
|
8441
|
+
},
|
|
8442
|
+
// Running: step success, more steps remaining
|
|
8443
|
+
{
|
|
8444
|
+
from: "Running",
|
|
8445
|
+
to: "Running",
|
|
8446
|
+
event: "STEP_SUCCESS",
|
|
8447
|
+
guard: ["<", ["+", "@entity.currentStep", 1], "@entity.totalSteps"],
|
|
8448
|
+
effects: [
|
|
8449
|
+
["set", "@entity.currentStep", ["+", "@entity.currentStep", 1]],
|
|
8450
|
+
["emit", "SAGA_STEP_COMPLETED", {
|
|
8451
|
+
step: "@entity.currentStep",
|
|
8452
|
+
totalSteps: "@entity.totalSteps"
|
|
8453
|
+
}]
|
|
8454
|
+
]
|
|
8455
|
+
},
|
|
8456
|
+
// Running → Completed: last step succeeded
|
|
8457
|
+
{
|
|
8458
|
+
from: "Running",
|
|
8459
|
+
to: "Completed",
|
|
8460
|
+
event: "STEP_SUCCESS",
|
|
8461
|
+
guard: [">=", ["+", "@entity.currentStep", 1], "@entity.totalSteps"],
|
|
8462
|
+
effects: [
|
|
8463
|
+
["set", "@entity.sagaStatus", "completed"],
|
|
8464
|
+
["set", "@entity.completedAt", ["time/now"]],
|
|
8465
|
+
["emit", "SAGA_COMPLETED", { sagaName: "@entity.sagaName" }]
|
|
8466
|
+
]
|
|
8467
|
+
},
|
|
8468
|
+
// Running → Compensating: a step failed
|
|
8469
|
+
{
|
|
8470
|
+
from: "Running",
|
|
8471
|
+
to: "Compensating",
|
|
8472
|
+
event: "STEP_FAILURE",
|
|
8473
|
+
effects: [
|
|
8474
|
+
["set", "@entity.sagaStatus", "compensating"],
|
|
8475
|
+
["set", "@entity.failedStep", "@entity.currentStep"],
|
|
8476
|
+
["emit", "SAGA_COMPENSATING", {
|
|
8477
|
+
failedStep: "@entity.currentStep",
|
|
8478
|
+
sagaName: "@entity.sagaName"
|
|
8479
|
+
}]
|
|
8480
|
+
]
|
|
8481
|
+
},
|
|
8482
|
+
// Compensating: compensation step succeeded, more to undo
|
|
8483
|
+
{
|
|
8484
|
+
from: "Compensating",
|
|
8485
|
+
to: "Compensating",
|
|
8486
|
+
event: "COMPENSATE_SUCCESS",
|
|
8487
|
+
guard: [">", "@entity.currentStep", 0],
|
|
8488
|
+
effects: [
|
|
8489
|
+
["set", "@entity.currentStep", ["-", "@entity.currentStep", 1]]
|
|
8490
|
+
]
|
|
8491
|
+
},
|
|
8492
|
+
// Compensating → Failed: all compensations done (reached step 0)
|
|
8493
|
+
{
|
|
8494
|
+
from: "Compensating",
|
|
8495
|
+
to: "Failed",
|
|
8496
|
+
event: "COMPENSATE_SUCCESS",
|
|
8497
|
+
guard: ["<=", "@entity.currentStep", 0],
|
|
8498
|
+
effects: [
|
|
8499
|
+
["set", "@entity.sagaStatus", "failed"],
|
|
8500
|
+
["set", "@entity.completedAt", ["time/now"]],
|
|
8501
|
+
["emit", "SAGA_COMPENSATION_DONE", { sagaName: "@entity.sagaName" }]
|
|
8502
|
+
]
|
|
8503
|
+
},
|
|
8504
|
+
// Compensating → Failed: compensation itself failed
|
|
8505
|
+
{
|
|
8506
|
+
from: "Compensating",
|
|
8507
|
+
to: "Failed",
|
|
8508
|
+
event: "COMPENSATE_FAILURE",
|
|
8509
|
+
effects: [
|
|
8510
|
+
["set", "@entity.sagaStatus", "failed"],
|
|
8511
|
+
["set", "@entity.completedAt", ["time/now"]],
|
|
8512
|
+
["emit", "SAGA_FAILED", {
|
|
8513
|
+
sagaName: "@entity.sagaName",
|
|
8514
|
+
reason: "Compensation failed"
|
|
8515
|
+
}]
|
|
8516
|
+
]
|
|
8517
|
+
},
|
|
8518
|
+
// Reset from terminal states
|
|
8519
|
+
{
|
|
8520
|
+
from: ["Completed", "Failed"],
|
|
8521
|
+
to: "Idle",
|
|
8522
|
+
event: "RESET",
|
|
8523
|
+
effects: [
|
|
8524
|
+
["set", "@entity.sagaStatus", "idle"],
|
|
8525
|
+
["set", "@entity.currentStep", 0],
|
|
8526
|
+
["set", "@entity.completedSteps", []],
|
|
8527
|
+
["set", "@entity.compensatedSteps", []],
|
|
8528
|
+
["set", "@entity.failedStep", -1],
|
|
8529
|
+
["set", "@entity.failureReason", ""]
|
|
8530
|
+
]
|
|
8531
|
+
}
|
|
8532
|
+
]
|
|
8533
|
+
},
|
|
8534
|
+
ticks: []
|
|
8535
|
+
}
|
|
8536
|
+
],
|
|
8537
|
+
pages: []
|
|
8538
|
+
}
|
|
8539
|
+
]
|
|
8540
|
+
};
|
|
8541
|
+
var METRICS_COLLECTOR_BEHAVIOR = {
|
|
8542
|
+
name: "std-metrics-collector",
|
|
8543
|
+
version: "1.0.0",
|
|
8544
|
+
description: "Tick-based metrics aggregation with periodic flush and reporting",
|
|
8545
|
+
orbitals: [
|
|
8546
|
+
{
|
|
8547
|
+
name: "MetricsCollectorOrbital",
|
|
8548
|
+
entity: {
|
|
8549
|
+
name: "MetricsState",
|
|
8550
|
+
persistence: "runtime",
|
|
8551
|
+
fields: [
|
|
8552
|
+
{ name: "id", type: "string", required: true },
|
|
8553
|
+
{ name: "counters", type: "object", default: {} },
|
|
8554
|
+
{ name: "gauges", type: "object", default: {} },
|
|
8555
|
+
{ name: "lastFlush", type: "number", default: 0 },
|
|
8556
|
+
{ name: "flushIntervalMs", type: "number", default: 6e4 },
|
|
8557
|
+
{ name: "totalFlushes", type: "number", default: 0 },
|
|
8558
|
+
{ name: "totalRecorded", type: "number", default: 0 }
|
|
8559
|
+
]
|
|
8560
|
+
},
|
|
8561
|
+
traits: [
|
|
8562
|
+
{
|
|
8563
|
+
name: "MetricsCollector",
|
|
8564
|
+
linkedEntity: "MetricsState",
|
|
8565
|
+
category: "lifecycle",
|
|
8566
|
+
emits: [
|
|
8567
|
+
{ event: "METRICS_REPORT", scope: "external" }
|
|
8568
|
+
],
|
|
8569
|
+
stateMachine: {
|
|
8570
|
+
states: [
|
|
8571
|
+
{ name: "Collecting", isInitial: true }
|
|
8572
|
+
],
|
|
8573
|
+
events: [
|
|
8574
|
+
{ key: "RECORD_COUNTER", name: "Record Counter" },
|
|
8575
|
+
{ key: "RECORD_GAUGE", name: "Record Gauge" },
|
|
8576
|
+
{ key: "FLUSH", name: "Flush Metrics" },
|
|
8577
|
+
{ key: "RESET", name: "Reset All" }
|
|
8578
|
+
],
|
|
8579
|
+
transitions: [
|
|
8580
|
+
// Record a counter increment
|
|
8581
|
+
{
|
|
8582
|
+
from: "Collecting",
|
|
8583
|
+
to: "Collecting",
|
|
8584
|
+
event: "RECORD_COUNTER",
|
|
8585
|
+
effects: [
|
|
8586
|
+
["set", "@entity.totalRecorded", ["+", "@entity.totalRecorded", 1]]
|
|
8587
|
+
]
|
|
8588
|
+
},
|
|
8589
|
+
// Record a gauge value
|
|
8590
|
+
{
|
|
8591
|
+
from: "Collecting",
|
|
8592
|
+
to: "Collecting",
|
|
8593
|
+
event: "RECORD_GAUGE",
|
|
8594
|
+
effects: [
|
|
8595
|
+
["set", "@entity.totalRecorded", ["+", "@entity.totalRecorded", 1]]
|
|
8596
|
+
]
|
|
8597
|
+
},
|
|
8598
|
+
// Flush: emit report and reset counters
|
|
8599
|
+
{
|
|
8600
|
+
from: "Collecting",
|
|
8601
|
+
to: "Collecting",
|
|
8602
|
+
event: "FLUSH",
|
|
8603
|
+
effects: [
|
|
8604
|
+
["emit", "METRICS_REPORT", {
|
|
8605
|
+
counters: "@entity.counters",
|
|
8606
|
+
gauges: "@entity.gauges",
|
|
8607
|
+
totalRecorded: "@entity.totalRecorded"
|
|
8608
|
+
}],
|
|
8609
|
+
["set", "@entity.counters", {}],
|
|
8610
|
+
["set", "@entity.lastFlush", ["time/now"]],
|
|
8611
|
+
["set", "@entity.totalFlushes", ["+", "@entity.totalFlushes", 1]]
|
|
8612
|
+
]
|
|
8613
|
+
},
|
|
8614
|
+
// Full reset
|
|
8615
|
+
{
|
|
8616
|
+
from: "Collecting",
|
|
8617
|
+
to: "Collecting",
|
|
8618
|
+
event: "RESET",
|
|
8619
|
+
effects: [
|
|
8620
|
+
["set", "@entity.counters", {}],
|
|
8621
|
+
["set", "@entity.gauges", {}],
|
|
8622
|
+
["set", "@entity.totalRecorded", 0],
|
|
8623
|
+
["set", "@entity.totalFlushes", 0],
|
|
8624
|
+
["set", "@entity.lastFlush", 0]
|
|
8625
|
+
]
|
|
8626
|
+
}
|
|
8627
|
+
]
|
|
8628
|
+
},
|
|
8629
|
+
ticks: [
|
|
8630
|
+
{
|
|
8631
|
+
name: "periodic_flush",
|
|
8632
|
+
interval: "@entity.flushIntervalMs",
|
|
8633
|
+
guard: [">", "@entity.totalRecorded", 0],
|
|
8634
|
+
effects: [["emit", "FLUSH"]],
|
|
8635
|
+
description: "Periodically flush accumulated metrics"
|
|
8636
|
+
}
|
|
8637
|
+
]
|
|
8638
|
+
}
|
|
8639
|
+
],
|
|
8640
|
+
pages: []
|
|
8641
|
+
}
|
|
8642
|
+
]
|
|
8643
|
+
};
|
|
8644
|
+
var INFRASTRUCTURE_BEHAVIORS = [
|
|
8645
|
+
CIRCUIT_BREAKER_BEHAVIOR,
|
|
8646
|
+
HEALTH_CHECK_BEHAVIOR,
|
|
8647
|
+
RATE_LIMITER_BEHAVIOR,
|
|
8648
|
+
CACHE_ASIDE_BEHAVIOR,
|
|
8649
|
+
SAGA_BEHAVIOR,
|
|
8650
|
+
METRICS_COLLECTOR_BEHAVIOR
|
|
8651
|
+
];
|
|
8652
|
+
|
|
7699
8653
|
// behaviors/registry.ts
|
|
7700
8654
|
var STANDARD_BEHAVIORS = [
|
|
7701
8655
|
...UI_INTERACTION_BEHAVIORS,
|
|
@@ -7704,7 +8658,8 @@ var STANDARD_BEHAVIORS = [
|
|
|
7704
8658
|
...FEEDBACK_BEHAVIORS,
|
|
7705
8659
|
...GAME_CORE_BEHAVIORS,
|
|
7706
8660
|
...GAME_ENTITY_BEHAVIORS,
|
|
7707
|
-
...GAME_UI_BEHAVIORS
|
|
8661
|
+
...GAME_UI_BEHAVIORS,
|
|
8662
|
+
...INFRASTRUCTURE_BEHAVIORS
|
|
7708
8663
|
];
|
|
7709
8664
|
var BEHAVIOR_REGISTRY = STANDARD_BEHAVIORS.reduce(
|
|
7710
8665
|
(acc, behavior) => {
|