@cqrs-ddd/pipeline-resilience 0.5.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/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +36 -0
- package/dist/errors/resilience-configuration.error.d.ts +13 -0
- package/dist/errors/resilience-configuration.error.d.ts.map +1 -0
- package/dist/errors/resilience-configuration.error.js +19 -0
- package/dist/errors/resilience-configuration.error.js.map +1 -0
- package/dist/errors/resilience-policy-configuration.error.d.ts +12 -0
- package/dist/errors/resilience-policy-configuration.error.d.ts.map +1 -0
- package/dist/errors/resilience-policy-configuration.error.js +17 -0
- package/dist/errors/resilience-policy-configuration.error.js.map +1 -0
- package/dist/helpers/policy-factory.d.ts +34 -0
- package/dist/helpers/policy-factory.d.ts.map +1 -0
- package/dist/helpers/policy-factory.js +189 -0
- package/dist/helpers/policy-factory.js.map +1 -0
- package/dist/helpers/resilience-context.d.ts +38 -0
- package/dist/helpers/resilience-context.d.ts.map +1 -0
- package/dist/helpers/resilience-context.js +45 -0
- package/dist/helpers/resilience-context.js.map +1 -0
- package/dist/helpers/resilience.intent.d.ts +29 -0
- package/dist/helpers/resilience.intent.d.ts.map +1 -0
- package/dist/helpers/resilience.intent.js +21 -0
- package/dist/helpers/resilience.intent.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/resilience-options.interface.d.ts +326 -0
- package/dist/interfaces/resilience-options.interface.d.ts.map +1 -0
- package/dist/interfaces/resilience-options.interface.js +3 -0
- package/dist/interfaces/resilience-options.interface.js.map +1 -0
- package/dist/resilience-policies.d.ts +48 -0
- package/dist/resilience-policies.d.ts.map +1 -0
- package/dist/resilience-policies.js +75 -0
- package/dist/resilience-policies.js.map +1 -0
- package/dist/resilience.behavior.d.ts +64 -0
- package/dist/resilience.behavior.d.ts.map +1 -0
- package/dist/resilience.behavior.js +159 -0
- package/dist/resilience.behavior.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { PIPELINE_BEHAVIOR_CONTRACT, } from '@cqrs-ddd/pipeline';
|
|
3
|
+
import { ResilienceConfigurationError } from './errors/resilience-configuration.error.js';
|
|
4
|
+
import { buildResiliencePolicy, } from './helpers/policy-factory.js';
|
|
5
|
+
import { runWithResilienceAbortSignal, runWithResilienceRequest, } from './helpers/resilience-context.js';
|
|
6
|
+
function getResilienceSafetyIssues(options, requestKind) {
|
|
7
|
+
if (!options || options.policy)
|
|
8
|
+
return [];
|
|
9
|
+
const issues = [];
|
|
10
|
+
const dependencyLayers = options;
|
|
11
|
+
if (dependencyLayers.circuitBreaker !== undefined ||
|
|
12
|
+
dependencyLayers.fallback !== undefined) {
|
|
13
|
+
issues.push({
|
|
14
|
+
message: 'circuitBreaker and fallback are not applied around a whole handler; they belong to a named policy of an outbound dependency',
|
|
15
|
+
fix: 'Declare them as a named policy, new ResiliencePolicies({ <name>: { ... } }), and run the outbound call through it.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const classifiesErrors = typeof options.handle === 'function' || options.handleAllErrors === true;
|
|
19
|
+
if (options.retry && !classifiesErrors) {
|
|
20
|
+
issues.push({
|
|
21
|
+
message: 'retry requires handle(error) or explicit handleAllErrors: true',
|
|
22
|
+
fix: 'Specify handle: (err) => boolean or handleAllErrors: true in ResilienceBehavior options.',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (options.retry &&
|
|
26
|
+
requestKind !== 'query' &&
|
|
27
|
+
options.retry.replaySafe !== true) {
|
|
28
|
+
issues.push({
|
|
29
|
+
message: `retry on non-query handler (${requestKind}) replays downstream work; commands/events must set retry.replaySafe: true`,
|
|
30
|
+
fix: 'Set retry: { ...retry, replaySafe: true } after verifying handler side effects are idempotent or transactional.',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (options.timeout &&
|
|
34
|
+
requestKind !== 'query' &&
|
|
35
|
+
options.timeout.strategy !== 'cooperative' &&
|
|
36
|
+
options.timeout.replaySafe !== true) {
|
|
37
|
+
issues.push({
|
|
38
|
+
message: `aggressive timeout on non-query handler (${requestKind}) answers the caller while the handler keeps running, so a retry can overlap its side effects`,
|
|
39
|
+
fix: "Use timeout: { ...timeout, strategy: 'cooperative' } and pass getResilienceAbortSignal() to cancellable work, or set timeout.replaySafe: true after verifying overlapping executions are safe.",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return issues;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pipeline behavior that wraps each command / query / event handler in a
|
|
46
|
+
* cockatiel resilience policy — the layers that make sense around a whole
|
|
47
|
+
* handler: retry, timeout and bulkhead.
|
|
48
|
+
*
|
|
49
|
+
* A circuit breaker and a fallback belong to an outbound dependency, not to a
|
|
50
|
+
* handler: declare them on a named policy (`new ResiliencePolicies({ ... })`)
|
|
51
|
+
* and use it in the adapter through {@link ResiliencePolicies}. The behavior
|
|
52
|
+
* contract rejects them here.
|
|
53
|
+
*
|
|
54
|
+
* Resolution of the effective options for a handler where this behavior is
|
|
55
|
+
* attached:
|
|
56
|
+
* 1. Application-wide defaults, the first constructor argument.
|
|
57
|
+
* 2. Per-handler options from `pipeline.wrap(options, [ResilienceBehavior, { ... }])`,
|
|
58
|
+
* shallow-merged on top of the defaults (handler keys win).
|
|
59
|
+
*
|
|
60
|
+
* Policies are built **lazily on first invocation and cached per handler**, so
|
|
61
|
+
* a stateful layer (bulkhead) correctly shares state across
|
|
62
|
+
* every request to that handler. When no options resolve, the behavior caches
|
|
63
|
+
* that result and passes subsequent invocations directly to `next()` without
|
|
64
|
+
* constructing or executing a cockatiel policy.
|
|
65
|
+
*
|
|
66
|
+
* ### Replay / error-classification safety
|
|
67
|
+
*
|
|
68
|
+
* A handler-level retry calls `next()` again, which means the complete
|
|
69
|
+
* downstream pipeline and handler are replayed. To avoid accidental duplicate
|
|
70
|
+
* side effects, command/event retries must explicitly set
|
|
71
|
+
* `retry.replaySafe: true`. A retry must also define which errors are transient via `handle(error)`, unless the caller
|
|
72
|
+
* intentionally opts into `handleAllErrors: true`. An `aggressive` timeout
|
|
73
|
+
* (the default strategy) on a command/event answers the caller while the
|
|
74
|
+
* handler keeps running, so it requires `strategy: 'cooperative'` or
|
|
75
|
+
* `timeout.replaySafe: true`.
|
|
76
|
+
*
|
|
77
|
+
* Timeout and bulkhead-only policies do not require an error classifier because
|
|
78
|
+
* they do not decide which application errors are retryable.
|
|
79
|
+
* A custom pre-built Cockatiel `policy` also bypasses the declarative safety
|
|
80
|
+
* checks because the caller owns its semantics directly.
|
|
81
|
+
*/
|
|
82
|
+
export class ResilienceBehavior {
|
|
83
|
+
defaultOptions;
|
|
84
|
+
static [PIPELINE_BEHAVIOR_CONTRACT] = {
|
|
85
|
+
validate: (context) => {
|
|
86
|
+
const options = context.effectiveOptions;
|
|
87
|
+
const issues = getResilienceSafetyIssues(options, context.requestKind);
|
|
88
|
+
if (issues.length === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
return issues.map((issue) => ({
|
|
91
|
+
handlerName: context.handlerName,
|
|
92
|
+
behaviorName: ResilienceBehavior.name,
|
|
93
|
+
message: issue.message,
|
|
94
|
+
fix: issue.fix,
|
|
95
|
+
}));
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
logger;
|
|
99
|
+
/**
|
|
100
|
+
* Per-handler policy cache. `null` means "resolved, but nothing configured"
|
|
101
|
+
* (pass-through), distinct from `undefined` ("not yet resolved").
|
|
102
|
+
*/
|
|
103
|
+
policyCache = new Map();
|
|
104
|
+
constructor(defaultOptions, logger) {
|
|
105
|
+
this.defaultOptions = defaultOptions;
|
|
106
|
+
this.logger = logger ?? console;
|
|
107
|
+
}
|
|
108
|
+
async handle(context, next) {
|
|
109
|
+
const policy = this.resolvePolicy(context);
|
|
110
|
+
if (!policy)
|
|
111
|
+
return next();
|
|
112
|
+
// The policy is shared across every request that reaches this handler, so
|
|
113
|
+
// the request's own labels travel with the execution rather than being
|
|
114
|
+
// captured when the policy was built.
|
|
115
|
+
return runWithResilienceRequest({ requestName: context.requestName, handlerName: context.handlerName }, () => policy.execute((policyContext) => {
|
|
116
|
+
// Cockatiel supplies the effective AbortSignal (including timeout
|
|
117
|
+
// cancellation) to each execute callback. Bind it to this attempt's
|
|
118
|
+
// async execution so an aggressive timeout followed by a retry cannot
|
|
119
|
+
// replace the signal still observed by work from the timed-out attempt.
|
|
120
|
+
return runWithResilienceAbortSignal(policyContext.signal, next);
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
/** Resolves, validates, and caches the composed policy for the handler in `context`. */
|
|
124
|
+
resolvePolicy(context) {
|
|
125
|
+
const cached = this.policyCache.get(context.handlerType);
|
|
126
|
+
if (cached !== undefined)
|
|
127
|
+
return cached;
|
|
128
|
+
const handlerOptions = context.getBehaviorOptions(ResilienceBehavior);
|
|
129
|
+
const effective = this.resolveEffectiveOptions(handlerOptions);
|
|
130
|
+
this.assertSafeConfiguration(context, effective);
|
|
131
|
+
const policy = effective
|
|
132
|
+
? buildResiliencePolicy(effective, {
|
|
133
|
+
logger: this.logger,
|
|
134
|
+
requestName: context.requestName,
|
|
135
|
+
handlerName: context.handlerName,
|
|
136
|
+
})
|
|
137
|
+
: null;
|
|
138
|
+
this.policyCache.set(context.handlerType, policy);
|
|
139
|
+
return policy;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Rejects ambiguous whole-handler resilience configurations before Cockatiel
|
|
143
|
+
* policy creation. This turns replay/error-classification assumptions into an
|
|
144
|
+
* explicit application decision instead of a hidden default.
|
|
145
|
+
*/
|
|
146
|
+
assertSafeConfiguration(context, options) {
|
|
147
|
+
const issues = getResilienceSafetyIssues(options, context.requestKind);
|
|
148
|
+
if (issues.length > 0) {
|
|
149
|
+
throw new ResilienceConfigurationError(context.requestName, context.requestKind, issues[0].message);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Shallow-merges pipeline-level options over the constructor defaults. */
|
|
153
|
+
resolveEffectiveOptions(options) {
|
|
154
|
+
if (!this.defaultOptions && !options)
|
|
155
|
+
return undefined;
|
|
156
|
+
return { ...this.defaultOptions, ...options };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=resilience.behavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience.behavior.js","sourceRoot":"","sources":["../src/resilience.behavior.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cqrs-ddd/pipeline-resilience",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Retry, timeout, circuit breaker, bulkhead and fallback policies (cockatiel) for @cqrs-ddd/pipeline",
|
|
5
|
+
"author": "Aristotelis",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.12.0"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cqrs",
|
|
13
|
+
"pipeline",
|
|
14
|
+
"resilience",
|
|
15
|
+
"fault-tolerance",
|
|
16
|
+
"retry",
|
|
17
|
+
"circuit-breaker",
|
|
18
|
+
"timeout",
|
|
19
|
+
"bulkhead",
|
|
20
|
+
"fallback",
|
|
21
|
+
"cockatiel"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/aristoteliss/ddd-cqrs.git",
|
|
26
|
+
"directory": "packages/pipeline-resilience"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://aristoteliss.github.io/ddd-cqrs/packages/pipeline-resilience/",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/aristoteliss/ddd-cqrs/issues"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"main": "dist/index.js",
|
|
40
|
+
"types": "dist/index.d.ts",
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"COMMERCIAL_LICENSE.txt"
|
|
45
|
+
],
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"cockatiel": "^4.0.0",
|
|
51
|
+
"@cqrs-ddd/pipeline": "^0.5.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
55
|
+
"cockatiel": "^4.0.0",
|
|
56
|
+
"vitest": "^5.0.3",
|
|
57
|
+
"@cqrs-ddd/pipeline": "0.5.0"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.build.json",
|
|
61
|
+
"build:watch": "tsc -p tsconfig.build.json --watch",
|
|
62
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
63
|
+
"rebuild": "pnpm run clean && pnpm run build",
|
|
64
|
+
"lint": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest"
|
|
67
|
+
}
|
|
68
|
+
}
|