@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/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: 1,
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: "void",
2149
- params: [{ name: "ms", type: "number", description: "Milliseconds to wait" }],
2150
- example: '["async/delay", 2000] // Wait 2 seconds'
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) => {