@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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/dist/chunk-5CXDW7W6.js +202 -0
- package/dist/chunk-5CXDW7W6.js.map +1 -0
- package/dist/circuit-breaker-BSkcV0W_.d.ts +296 -0
- package/dist/fetch.d.ts +58 -0
- package/dist/fetch.js +117 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1065 -0
- package/dist/index.js.map +1 -0
- package/dist/postgres.d.ts +28 -0
- package/dist/postgres.js +56 -0
- package/dist/postgres.js.map +1 -0
- package/dist/redis.d.ts +59 -0
- package/dist/redis.js +549 -0
- package/dist/redis.js.map +1 -0
- package/dist/retry-BFP_k3Hg.d.ts +26 -0
- package/dist/testing/index.d.ts +45 -0
- package/dist/testing/index.js +101 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-Tf9T76C7.d.ts +187 -0
- package/package.json +127 -0
- package/src/adapters/fetch/adapter.ts +122 -0
- package/src/adapters/fetch/index.ts +15 -0
- package/src/adapters/fetch/retry-after.ts +111 -0
- package/src/adapters/postgres/adapter.ts +102 -0
- package/src/adapters/postgres/index.ts +7 -0
- package/src/coordination/redis/bulkhead.ts +61 -0
- package/src/coordination/redis/circuit-breaker.ts +270 -0
- package/src/coordination/redis/client.ts +78 -0
- package/src/coordination/redis/eval-script.ts +71 -0
- package/src/coordination/redis/keys.ts +32 -0
- package/src/coordination/redis/leases.ts +44 -0
- package/src/coordination/redis/scripts.ts +314 -0
- package/src/core/bulkhead.ts +336 -0
- package/src/core/circuit-breaker.ts +1066 -0
- package/src/core/index.ts +36 -0
- package/src/core/operation.ts +174 -0
- package/src/core/retry.ts +204 -0
- package/src/core/runtime.ts +123 -0
- package/src/core/scope-state-cache.ts +50 -0
- package/src/core/timeout.ts +73 -0
- package/src/core/types.ts +230 -0
- package/src/fetch.ts +17 -0
- package/src/index.ts +49 -0
- package/src/postgres.ts +9 -0
- 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"
|
package/src/postgres.ts
ADDED
|
@@ -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"
|