@gkoos/caracal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +311 -0
  4. package/dist/chunk-5CXDW7W6.js +202 -0
  5. package/dist/chunk-5CXDW7W6.js.map +1 -0
  6. package/dist/circuit-breaker-BSkcV0W_.d.ts +296 -0
  7. package/dist/fetch.d.ts +58 -0
  8. package/dist/fetch.js +117 -0
  9. package/dist/fetch.js.map +1 -0
  10. package/dist/index.d.ts +19 -0
  11. package/dist/index.js +1065 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/postgres.d.ts +28 -0
  14. package/dist/postgres.js +56 -0
  15. package/dist/postgres.js.map +1 -0
  16. package/dist/redis.d.ts +59 -0
  17. package/dist/redis.js +549 -0
  18. package/dist/redis.js.map +1 -0
  19. package/dist/retry-BFP_k3Hg.d.ts +26 -0
  20. package/dist/testing/index.d.ts +45 -0
  21. package/dist/testing/index.js +101 -0
  22. package/dist/testing/index.js.map +1 -0
  23. package/dist/types-Tf9T76C7.d.ts +187 -0
  24. package/package.json +127 -0
  25. package/src/adapters/fetch/adapter.ts +122 -0
  26. package/src/adapters/fetch/index.ts +15 -0
  27. package/src/adapters/fetch/retry-after.ts +111 -0
  28. package/src/adapters/postgres/adapter.ts +102 -0
  29. package/src/adapters/postgres/index.ts +7 -0
  30. package/src/coordination/redis/bulkhead.ts +61 -0
  31. package/src/coordination/redis/circuit-breaker.ts +270 -0
  32. package/src/coordination/redis/client.ts +78 -0
  33. package/src/coordination/redis/eval-script.ts +71 -0
  34. package/src/coordination/redis/keys.ts +32 -0
  35. package/src/coordination/redis/leases.ts +44 -0
  36. package/src/coordination/redis/scripts.ts +314 -0
  37. package/src/core/bulkhead.ts +336 -0
  38. package/src/core/circuit-breaker.ts +1066 -0
  39. package/src/core/index.ts +36 -0
  40. package/src/core/operation.ts +174 -0
  41. package/src/core/retry.ts +204 -0
  42. package/src/core/runtime.ts +123 -0
  43. package/src/core/scope-state-cache.ts +50 -0
  44. package/src/core/timeout.ts +73 -0
  45. package/src/core/types.ts +230 -0
  46. package/src/fetch.ts +17 -0
  47. package/src/index.ts +49 -0
  48. package/src/postgres.ts +9 -0
  49. package/src/redis.ts +8 -0
@@ -0,0 +1,230 @@
1
+ /** Traits that may vary for each invocation of an adapter. */
2
+ export type OperationCapabilities = Readonly<{
3
+ abort: "supported" | "unsupported"
4
+ replay: "safe" | "unsafe" | "unknown"
5
+ }>
6
+
7
+ export type Outcome<Result> =
8
+ | Readonly<{ status: "success"; value: Result }>
9
+ | Readonly<{ status: "failure"; error: unknown }>
10
+
11
+ /** An adapter's interpretation of an outcome for resilience policies. */
12
+ /** `retryable` is a failure eligible for the local retry policy. */
13
+ export type Classification = "success" | "failure" | "retryable" | "ignored"
14
+
15
+ export type OutcomeClassifier = (outcome: Outcome<unknown>) => Classification
16
+
17
+ export interface Adapter<Args, Result> {
18
+ execute(args: Args, context: ExecutionContext): Promise<Result>
19
+ capabilities(args: Args): OperationCapabilities
20
+ classify?(outcome: Outcome<Result>): Classification
21
+ }
22
+
23
+ export type ExecutionMetadata = Readonly<Record<string, unknown>>
24
+
25
+ /**
26
+ * Immutable state for one attempt. Retry derives later attempt contexts from
27
+ * this value; an operation starts at attempt 1.
28
+ */
29
+ export interface ExecutionContext {
30
+ readonly operationName: string
31
+ readonly executionId: string
32
+ readonly attempt: number
33
+ readonly signal: AbortSignal | undefined
34
+ readonly metadata: ExecutionMetadata
35
+ readonly capabilities: OperationCapabilities
36
+ readonly classify: OutcomeClassifier
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Circuit-breaker event types
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export type BreakerStateChangedEvent = Readonly<{
44
+ type: "breaker.state-changed"
45
+ at: number
46
+ context: ExecutionContext
47
+ coordination: "local" | "distributed"
48
+ policyName: string
49
+ scope: string
50
+ state: "open" | "half-open" | "closed"
51
+ previousState: "closed" | "open" | "half-open"
52
+ /** Present on distributed events; absent on local events. */
53
+ generation?: number
54
+ }>
55
+
56
+ export type BreakerRejectedEvent = Readonly<{
57
+ type: "breaker.rejected"
58
+ at: number
59
+ context: ExecutionContext
60
+ coordination: "local" | "distributed"
61
+ policyName: string
62
+ scope: string
63
+ state: "open" | "half-open"
64
+ generation?: number
65
+ }>
66
+
67
+ export type BreakerObservationEvent = Readonly<{
68
+ type: "breaker.observation"
69
+ at: number
70
+ context: ExecutionContext
71
+ coordination: "local" | "distributed"
72
+ policyName: string
73
+ scope: string
74
+ outcome: "success" | "failure"
75
+ generation?: number
76
+ }>
77
+
78
+ export type BreakerProbeStartedEvent = Readonly<{
79
+ type: "breaker.probe-started"
80
+ at: number
81
+ context: ExecutionContext
82
+ coordination: "local" | "distributed"
83
+ policyName: string
84
+ scope: string
85
+ generation?: number
86
+ }>
87
+
88
+ export type BreakerObservationStaleEvent = Readonly<{
89
+ type: "breaker.observation-stale"
90
+ at: number
91
+ context: ExecutionContext
92
+ coordination: "distributed"
93
+ policyName: string
94
+ scope: string
95
+ /** Generation of the attempt that was dropped. */
96
+ attemptGeneration: number
97
+ /** Current generation in the coordinator at the time the stale result arrived. */
98
+ currentGeneration: number
99
+ }>
100
+
101
+ export type BreakerCoordinatorErrorEvent = Readonly<{
102
+ type: "breaker.coordinator-error"
103
+ at: number
104
+ context: ExecutionContext
105
+ coordination: "distributed"
106
+ policyName: string
107
+ scope: string
108
+ operation: "admit" | "observe" | "settle-probe"
109
+ error: unknown
110
+ }>
111
+
112
+ export type BreakerDegradedEvent = Readonly<{
113
+ type: "breaker.degraded"
114
+ at: number
115
+ context: ExecutionContext
116
+ coordination: "distributed"
117
+ policyName: string
118
+ scope: string
119
+ reason: "coordinator-unavailable"
120
+ behavior: "fail-open" | "fail-closed"
121
+ }>
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Unified operation event union
125
+ // ---------------------------------------------------------------------------
126
+
127
+ export type OperationEvent =
128
+ | BreakerStateChangedEvent
129
+ | BreakerRejectedEvent
130
+ | BreakerObservationEvent
131
+ | BreakerProbeStartedEvent
132
+ | BreakerObservationStaleEvent
133
+ | BreakerCoordinatorErrorEvent
134
+ | BreakerDegradedEvent
135
+ | Readonly<{
136
+ type:
137
+ | "bulkhead.admitted"
138
+ | "bulkhead.rejected"
139
+ | "bulkhead.waited"
140
+ | "bulkhead.released"
141
+ | "bulkhead.lease-lost"
142
+ | "bulkhead.degraded"
143
+ at: number
144
+ context: ExecutionContext
145
+ coordination: "local" | "distributed"
146
+ policyName: string
147
+ scope: string
148
+ occupancy?: number
149
+ reason?: string
150
+ }>
151
+ | Readonly<{
152
+ type: "execution.started"
153
+ at: number
154
+ context: ExecutionContext
155
+ }>
156
+ | Readonly<{ type: "attempt.started"; at: number; context: ExecutionContext }>
157
+ | Readonly<{
158
+ type: "attempt.settled"
159
+ at: number
160
+ context: ExecutionContext
161
+ outcome: Outcome<undefined>
162
+ classification: Classification
163
+ }>
164
+ | Readonly<{
165
+ type: "execution.settled"
166
+ at: number
167
+ context: ExecutionContext
168
+ outcome: Outcome<undefined>
169
+ }>
170
+ | Readonly<{
171
+ type: "timeout.triggered"
172
+ at: number
173
+ context: ExecutionContext
174
+ timeoutMs: number
175
+ abortRequested: boolean
176
+ }>
177
+ | Readonly<{
178
+ type: "retry.scheduled"
179
+ at: number
180
+ context: ExecutionContext
181
+ nextAttempt: number
182
+ delayMs: number
183
+ outcome: Outcome<undefined>
184
+ classification: Classification
185
+ }>
186
+ | Readonly<{
187
+ type: "retry.exhausted"
188
+ at: number
189
+ context: ExecutionContext
190
+ outcome: Outcome<undefined>
191
+ classification: Classification
192
+ }>
193
+
194
+ /** Output-only observability contract. Sinks cannot alter policy execution. */
195
+ export interface EventSink {
196
+ emit(event: OperationEvent): void
197
+ }
198
+
199
+ export type EventSinks = EventSink | readonly EventSink[]
200
+
201
+ export type OperationExecuteOptions = Readonly<{
202
+ signal?: AbortSignal
203
+ metadata?: Readonly<Record<string, unknown>>
204
+ executionId?: string
205
+ }>
206
+
207
+ export type Next<Result> = (context: ExecutionContext) => Promise<Result>
208
+
209
+ /** A policy wraps execution; it is not a generic lifecycle hook system. */
210
+ export interface Policy {
211
+ /** Attempt-phase policies wrap each adapter call and must await underlying settlement. */
212
+ readonly phase?: "attempt"
213
+ readonly name: string
214
+ execute<Result>(
215
+ context: ExecutionContext,
216
+ next: Next<Result>,
217
+ ): Promise<Result>
218
+ }
219
+
220
+ export interface Operation<Args, Result> {
221
+ readonly name: string
222
+ execute(args: Args, options?: OperationExecuteOptions): Promise<Result>
223
+ }
224
+
225
+ export interface OperationOptions<Args, Result> {
226
+ readonly name: string
227
+ readonly adapter: Adapter<Args, Result>
228
+ readonly policies?: readonly Policy[]
229
+ readonly events?: EventSinks
230
+ }
package/src/fetch.ts ADDED
@@ -0,0 +1,17 @@
1
+ /** Fetch/HTTP adapter entry point. */
2
+
3
+ export type {
4
+ FetchAdapterOptions,
5
+ FetchOperationArgs,
6
+ FetchReplay,
7
+ } from "./adapters/fetch/index.js"
8
+ export {
9
+ createRetryAfterDelay,
10
+ fetchAdapter,
11
+ retryAfterDelay,
12
+ retryAfterMs,
13
+ } from "./adapters/fetch/index.js"
14
+ export type {
15
+ RetryAfterDelay,
16
+ RetryAfterDelayOptions,
17
+ } from "./adapters/fetch/index.js"
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ /** Caracal's protocol-agnostic runtime entry point. */
2
+
3
+ export type {
4
+ BulkheadCoordinator,
5
+ DistributedBulkheadOptions,
6
+ LocalBulkheadOptions,
7
+ } from "./core/bulkhead.js"
8
+ export { BulkheadRejectedError, bulkhead } from "./core/bulkhead.js"
9
+ export type {
10
+ AdmitProbeResult,
11
+ BreakerClassifier,
12
+ BreakerCoordinator,
13
+ BreakerIdentity,
14
+ BreakerOutcome,
15
+ BreakerSnapshot,
16
+ BreakerState,
17
+ DistributedBreakerOptions,
18
+ LocalBreakerOptions,
19
+ ObserveResult,
20
+ SettleProbeResult,
21
+ } from "./core/circuit-breaker.js"
22
+ export { CircuitOpenError, circuitBreaker } from "./core/circuit-breaker.js"
23
+ export type {
24
+ Adapter,
25
+ Classification,
26
+ EventSink,
27
+ EventSinks,
28
+ ExecutionContext,
29
+ ExecutionMetadata,
30
+ Next,
31
+ Operation,
32
+ OperationCapabilities,
33
+ OperationEvent,
34
+ OperationExecuteOptions,
35
+ OperationOptions,
36
+ Outcome,
37
+ OutcomeClassifier,
38
+ Policy,
39
+ RetryContext,
40
+ RetryDelay,
41
+ RetryOptions,
42
+ TimeoutOptions,
43
+ } from "./core/index.js"
44
+ export {
45
+ operation,
46
+ retry,
47
+ TimeoutError,
48
+ timeout,
49
+ } from "./core/index.js"
@@ -0,0 +1,9 @@
1
+ /** PostgreSQL adapter entry point for node-postgres (`pg`) 8.x clients/pools. */
2
+
3
+ export type {
4
+ PostgresAdapterOptions,
5
+ PostgresQueryArgs,
6
+ PostgresQueryable,
7
+ PostgresReplay,
8
+ } from "./adapters/postgres/index.js"
9
+ export { postgresAdapter } from "./adapters/postgres/index.js"
package/src/redis.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { redisCoordinator } from "./coordination/redis/bulkhead.js"
2
+ export { redisCircuitBreakerCoordinator } from "./coordination/redis/circuit-breaker.js"
3
+ export type { ClusterNode } from "./coordination/redis/client.js"
4
+ export {
5
+ CoordinatorUnavailableError,
6
+ createCoordinationClient,
7
+ createCoordinationClusterClient,
8
+ } from "./coordination/redis/client.js"