@cqrs-ddd/pipeline-idempotency 0.5.0 → 0.5.2
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 +14 -10
- package/README.md +8 -5
- package/dist/helpers/partitioned-key.js +2 -1
- package/dist/helpers/partitioned-key.js.map +1 -1
- package/dist/idempotency.behavior.d.ts +23 -2
- package/dist/idempotency.behavior.d.ts.map +1 -1
- package/dist/idempotency.behavior.js +86 -52
- package/dist/idempotency.behavior.js.map +1 -1
- package/dist/interfaces/idempotency-options.interface.d.ts +4 -3
- package/dist/interfaces/idempotency-options.interface.d.ts.map +1 -1
- package/dist/stores/memory.store.d.ts +3 -2
- package/dist/stores/memory.store.d.ts.map +1 -1
- package/dist/stores/memory.store.js +3 -2
- package/dist/stores/memory.store.js.map +1 -1
- package/package.json +7 -7
package/COMMERCIAL_LICENSE.txt
CHANGED
|
@@ -9,21 +9,25 @@ This Commercial License Agreement ("Agreement") is a legal agreement between You
|
|
|
9
9
|
Upon acceptance of this Agreement and compliance with the Payment Terms in Section 2, the Licensor grants You a non-exclusive, worldwide, non-transferable license to use, modify, integrate, and distribute the Software in commercial applications WITHOUT the copyleft obligations of the GNU Affero General Public License v3 (AGPLv3). Under this Agreement, You are NOT required to open-source Your proprietary code that integrates with the Software.
|
|
10
10
|
|
|
11
11
|
2. PAYMENT TERMS AND REVENUE TIERS
|
|
12
|
-
This license requires an annual fee based on Your (or Your organization's) total gross global revenue for the most recently completed fiscal year.
|
|
12
|
+
This license requires an annual fee based on Your (or Your organization's) total gross global revenue for the most recently completed fiscal year.
|
|
13
13
|
|
|
14
|
-
The annual fee is calculated using the following
|
|
14
|
+
The annual fee is calculated using the following marginal brackets. Each rate applies only to the portion of gross revenue that falls within its bracket, and the annual fee is the sum of the amounts for all brackets:
|
|
15
15
|
|
|
16
|
-
a) Tier 1:
|
|
17
|
-
|
|
16
|
+
a) Tier 1: The portion of gross revenue up to $500,000 USD
|
|
17
|
+
Rate: 0% (Free Commercial Use). If gross revenue does not exceed $500,000 USD, the fee is $0.
|
|
18
18
|
|
|
19
|
-
b) Tier 2:
|
|
20
|
-
|
|
19
|
+
b) Tier 2: The portion of gross revenue above $500,000 USD and up to $10,000,000 USD
|
|
20
|
+
Rate: 0.1% of that portion.
|
|
21
21
|
|
|
22
|
-
c) Tier 3:
|
|
23
|
-
|
|
22
|
+
c) Tier 3: The portion of gross revenue above $10,000,000 USD and up to $50,000,000 USD
|
|
23
|
+
Rate: 0.05% of that portion.
|
|
24
24
|
|
|
25
|
-
d) Tier 4:
|
|
26
|
-
|
|
25
|
+
d) Tier 4: The portion of gross revenue above $50,000,000 USD
|
|
26
|
+
Rate: 0.01% of that portion.
|
|
27
|
+
|
|
28
|
+
The total annual fee never exceeds a maximum hard cap of $50,000 USD per year.
|
|
29
|
+
|
|
30
|
+
Example: for gross revenue of $20,000,000 USD, the annual fee is 0.1% of $9,500,000 USD ($9,500) plus 0.05% of $10,000,000 USD ($5,000), a total of $14,500 USD.
|
|
27
31
|
|
|
28
32
|
3. REPORTING AND REMITTANCE
|
|
29
33
|
- Fees are calculated annually and must be remitted to the Licensor within ninety (90) days following the close of Your fiscal year.
|
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# @cqrs-ddd/pipeline-idempotency
|
|
2
2
|
|
|
3
|
-
Idempotency for `@cqrs-ddd/pipeline`:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Idempotency for `@cqrs-ddd/pipeline`: calls that share an operation key are deduplicated
|
|
4
|
+
while its record is live. A duplicate of a completed call gets the stored response, a
|
|
5
|
+
duplicate of a running one is refused, a key reused with another payload is refused, and
|
|
6
|
+
with a replay scope a response is replayed only to a caller authorized under the same
|
|
7
|
+
scope. This is deduplication within a record's lifetime, not exactly-once execution; the
|
|
8
|
+
guide lists when a handler can run again for a key. Memory, Redis and PostgreSQL stores
|
|
9
|
+
are included; `toHttpResponse()` from `@cqrs-ddd/pipeline-idempotency/http` turns a
|
|
10
|
+
conflict into a 409 or 422 answer.
|
|
8
11
|
|
|
9
12
|
**Documentation:** [guide](https://aristoteliss.github.io/ddd-cqrs/packages/pipeline-idempotency/) · [API reference](https://aristoteliss.github.io/ddd-cqrs/api/cqrs-ddd/pipeline-idempotency/) · [all packages](https://aristoteliss.github.io/ddd-cqrs/)
|
|
10
13
|
|
|
@@ -4,7 +4,7 @@ import { joinKeySegments } from '@cqrs-ddd/safe-stringify';
|
|
|
4
4
|
import { MissingIdempotencyPartitionError } from '../errors/missing-partition.error.js';
|
|
5
5
|
export function createPartitionedIdempotencyKeyFactory(options) {
|
|
6
6
|
const onMissingOperation = options.onMissingOperation ?? 'throw';
|
|
7
|
-
|
|
7
|
+
const keyFor = (context) => {
|
|
8
8
|
const tenant = tenantSegments(context, options, MissingIdempotencyPartitionError);
|
|
9
9
|
const principal = principalSegments(options.principal(context));
|
|
10
10
|
if (!principal) {
|
|
@@ -25,6 +25,7 @@ export function createPartitionedIdempotencyKeyFactory(options) {
|
|
|
25
25
|
operation,
|
|
26
26
|
]);
|
|
27
27
|
};
|
|
28
|
+
return keyFor;
|
|
28
29
|
}
|
|
29
30
|
/** Normalizes a principal to non-empty trimmed segments, or `undefined`. */
|
|
30
31
|
function principalSegments(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partitioned-key.js","sourceRoot":"","sources":["../../src/helpers/partitioned-key.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAGL,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,gCAAgC,EAAE,MAAM,sCAAsC,CAAC;AA8GxF,MAAM,UAAU,sCAAsC,CACpD,OAAyC;IAEzC,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC;IAEjE,
|
|
1
|
+
{"version":3,"file":"partitioned-key.js","sourceRoot":"","sources":["../../src/helpers/partitioned-key.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAGL,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,gCAAgC,EAAE,MAAM,sCAAsC,CAAC;AA8GxF,MAAM,UAAU,sCAAsC,CACpD,OAAyC;IAEzC,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC;IAEjE,MAAM,MAAM,GAA0B,CAAC,OAAO,EAAE,EAAE;QAChD,MAAM,MAAM,GAAG,cAAc,CAC3B,OAAO,EACP,OAAO,EACP,gCAAgC,CACjC,CAAC;QAEF,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,gCAAgC,CACxC,OAAO,CAAC,WAAW,EACnB,WAAW,EACX,uEAAuE;gBACrE,sEAAsE,CACzE,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,IAAI,kBAAkB,KAAK,MAAM;gBAAE,OAAO,SAAS,CAAC;YACpD,MAAM,IAAI,gCAAgC,CACxC,OAAO,CAAC,WAAW,EACnB,WAAW,EACX,0EAA0E,CAC3E,CAAC;QACJ,CAAC;QAED,OAAO,eAAe,CAAC;YACrB,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,GAAG,MAAM;YACT,GAAG,SAAS;YACZ,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,WAAW;YACrC,SAAS;SACV,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4EAA4E;AAC5E,SAAS,iBAAiB,CACxB,KAA6C;IAE7C,MAAM,QAAQ,GAAG,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CACxE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACjE,CAAC;IACF,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC;QACvE,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
|
|
@@ -73,8 +73,8 @@ export declare const IDEMPOTENCY_OWNERSHIP_LOST_ITEM_TOKEN: PipelineItemToken<bo
|
|
|
73
73
|
* later retry may execute the handler again. Set it to `false` when retaining
|
|
74
74
|
* the claim after a failure is preferable to retryability.
|
|
75
75
|
*
|
|
76
|
-
* Store-agnostic by design: memory
|
|
77
|
-
*
|
|
76
|
+
* Store-agnostic by design: memory, Redis, Postgres, or your own are one-line
|
|
77
|
+
* swaps of the store passed to the constructor, which requires one. When no key is produced,
|
|
78
78
|
* the handler runs normally.
|
|
79
79
|
*
|
|
80
80
|
* @example Per-handler idempotency
|
|
@@ -100,12 +100,33 @@ export declare class IdempotencyBehavior implements IPipelineBehavior, IPipeline
|
|
|
100
100
|
private readonly defaults;
|
|
101
101
|
constructor(store: IdempotencyStore, defaults?: IdempotencyBehaviorOptions, logger?: PipelineLogger);
|
|
102
102
|
handle(context: IPipelineContext, next: NextDelegate): Promise<unknown>;
|
|
103
|
+
/**
|
|
104
|
+
* Builds this execution's in-progress claim. Every refusal of the options
|
|
105
|
+
* and the request happens here, before the key is claimed.
|
|
106
|
+
*/
|
|
107
|
+
private prepareClaim;
|
|
108
|
+
/**
|
|
109
|
+
* The first claim failed. Replay or refuse against the record found; when
|
|
110
|
+
* that record disappeared or expired meanwhile, claim the key once more.
|
|
111
|
+
*/
|
|
112
|
+
private replayOrReclaim;
|
|
113
|
+
/** Runs the handler under this execution's claim, then completes the claim. */
|
|
114
|
+
private run;
|
|
115
|
+
/** The replayable JSON snapshot of a successful handler's response. */
|
|
116
|
+
private snapshot;
|
|
117
|
+
/**
|
|
118
|
+
* Stores the completed record while this execution still owns the claim, and
|
|
119
|
+
* flags the context when a newer claim replaced it.
|
|
120
|
+
*/
|
|
121
|
+
private complete;
|
|
103
122
|
/**
|
|
104
123
|
* The key was already claimed. Replay a completed response, or surface a
|
|
105
124
|
* conflict for an in-progress duplicate, a key reused with a new payload, or a
|
|
106
125
|
* completed record whose replay scope does not match the caller's.
|
|
107
126
|
*/
|
|
108
127
|
private replayOrConflict;
|
|
128
|
+
/** Throws the conflict that forbids replaying `existing` to this caller. */
|
|
129
|
+
private refuseReplay;
|
|
109
130
|
/**
|
|
110
131
|
* Release a failed execution's claim only if it still owns the key; never let
|
|
111
132
|
* cleanup break propagation of the original handler error.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency.behavior.d.ts","sourceRoot":"","sources":["../src/idempotency.behavior.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,0BAA0B,EAI1B,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"idempotency.behavior.d.ts","sourceRoot":"","sources":["../src/idempotency.behavior.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,0BAA0B,EAI1B,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAMhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAEpF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,eAAiC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,iBAAiB,CAAC,MAAM,CACS,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,eAAsC,CAAC;AAE7E;;;GAGG;AACH,eAAO,MAAM,+BAA+B,EAAE,iBAAiB,CAAC,OAAO,CAIpE,CAAC;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,eAE3C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qCAAqC,EAAE,iBAAiB,CAAC,OAAO,CAI1E,CAAC;AA6BJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,mBACX,YACE,iBAAiB,EACjB,gCAAgC,CAAC,0BAA0B,CAAC;IA+D5D,OAAO,CAAC,QAAQ,CAAC,KAAK;IA7DxB,MAAM,CAAC,QAAQ,CAAC,CAAC,0BAA0B,CAAC,EAAE,yBAAyB,CAuDrE;IAEF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IAEtD,YACmB,KAAK,EAAE,gBAAgB,EACxC,QAAQ,CAAC,EAAE,0BAA0B,EACrC,MAAM,CAAC,EAAE,cAAc,EAexB;IAEK,MAAM,CACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC,CA6BlB;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY;IA4CpB;;;OAGG;YACW,eAAe;IAwB7B,+EAA+E;YACjE,GAAG;IAqBjB,uEAAuE;IACvE,OAAO,CAAC,QAAQ;IAsBhB;;;OAGG;YACW,QAAQ;IA4CtB;;;;OAIG;YACW,gBAAgB;IAiB9B,4EAA4E;IAC5E,OAAO,CAAC,YAAY;IA4CpB;;;OAGG;YACW,OAAO;IAYrB,2EAA2E;IAC3E,uBAAuB,CACrB,OAAO,CAAC,EAAE,0BAA0B,GACnC,0BAA0B,CAG5B;CACF"}
|
|
@@ -4,7 +4,7 @@ import { createPipelineItem, PIPELINE_BEHAVIOR_CONTRACT, setPipelineItem, } from
|
|
|
4
4
|
import { untyped } from '@cqrs-ddd/untyped';
|
|
5
5
|
import { DEFAULT_IDEMPOTENCY_TTL_MS } from './constants/tokens.js';
|
|
6
6
|
import { IdempotencyCompletionError } from './errors/idempotency-completion.error.js';
|
|
7
|
-
import { IdempotencyConflictError } from './errors/idempotency-conflict.error.js';
|
|
7
|
+
import { IdempotencyConflictError, } from './errors/idempotency-conflict.error.js';
|
|
8
8
|
import { fingerprintValue } from './helpers/fingerprint.js';
|
|
9
9
|
import { toJsonSnapshot } from './helpers/json-snapshot.js';
|
|
10
10
|
/**
|
|
@@ -84,8 +84,8 @@ function inScope(options, requestKind) {
|
|
|
84
84
|
* later retry may execute the handler again. Set it to `false` when retaining
|
|
85
85
|
* the claim after a failure is preferable to retryability.
|
|
86
86
|
*
|
|
87
|
-
* Store-agnostic by design: memory
|
|
88
|
-
*
|
|
87
|
+
* Store-agnostic by design: memory, Redis, Postgres, or your own are one-line
|
|
88
|
+
* swaps of the store passed to the constructor, which requires one. When no key is produced,
|
|
89
89
|
* the handler runs normally.
|
|
90
90
|
*
|
|
91
91
|
* @example Per-handler idempotency
|
|
@@ -171,6 +171,18 @@ export class IdempotencyBehavior {
|
|
|
171
171
|
return next();
|
|
172
172
|
}
|
|
173
173
|
setPipelineItem(context, IDEMPOTENCY_KEY_ITEM_TOKEN, key);
|
|
174
|
+
const attempt = this.prepareClaim(context, options, key);
|
|
175
|
+
const claimed = await this.store.setIfAbsent(key, attempt.claim, attempt.ttl);
|
|
176
|
+
if (!claimed) {
|
|
177
|
+
return this.replayOrReclaim(context, attempt, next);
|
|
178
|
+
}
|
|
179
|
+
return this.run(context, attempt, next);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Builds this execution's in-progress claim. Every refusal of the options
|
|
183
|
+
* and the request happens here, before the key is claimed.
|
|
184
|
+
*/
|
|
185
|
+
prepareClaim(context, options, key) {
|
|
174
186
|
const ttl = options.ttl ?? DEFAULT_IDEMPOTENCY_TTL_MS;
|
|
175
187
|
if (!Number.isSafeInteger(ttl) || ttl <= 0) {
|
|
176
188
|
throw new TypeError('Idempotency ttl must be a positive safe integer in milliseconds.');
|
|
@@ -192,36 +204,56 @@ export class IdempotencyBehavior {
|
|
|
192
204
|
replayScope,
|
|
193
205
|
createdAt: new Date().toISOString(),
|
|
194
206
|
};
|
|
195
|
-
|
|
207
|
+
return {
|
|
208
|
+
key,
|
|
209
|
+
claimId,
|
|
210
|
+
ttl,
|
|
211
|
+
fingerprint,
|
|
212
|
+
replayScope,
|
|
213
|
+
scopeRequired,
|
|
214
|
+
claim,
|
|
215
|
+
options,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The first claim failed. Replay or refuse against the record found; when
|
|
220
|
+
* that record disappeared or expired meanwhile, claim the key once more.
|
|
221
|
+
*/
|
|
222
|
+
async replayOrReclaim(context, attempt, next) {
|
|
223
|
+
const existing = await this.store.get(attempt.key);
|
|
224
|
+
if (existing) {
|
|
225
|
+
return this.replayOrConflict(context, attempt, existing);
|
|
226
|
+
}
|
|
227
|
+
// The record disappeared or expired after the failed claim. Give this
|
|
228
|
+
// request one bounded opportunity to claim the now-available key instead
|
|
229
|
+
// of reporting a false in-progress conflict.
|
|
230
|
+
const claimed = await this.store.setIfAbsent(attempt.key, attempt.claim, attempt.ttl);
|
|
196
231
|
if (!claimed) {
|
|
197
|
-
|
|
198
|
-
if (existing) {
|
|
199
|
-
return this.replayOrConflict(context, key, fingerprint, { replayScope, scopeRequired }, existing);
|
|
200
|
-
}
|
|
201
|
-
// The record disappeared or expired after the failed claim. Give this
|
|
202
|
-
// request one bounded opportunity to claim the now-available key instead
|
|
203
|
-
// of reporting a false in-progress conflict.
|
|
204
|
-
claimed = await this.store.setIfAbsent(key, claim, ttl);
|
|
205
|
-
if (!claimed) {
|
|
206
|
-
return this.replayOrConflict(context, key, fingerprint, {
|
|
207
|
-
replayScope,
|
|
208
|
-
scopeRequired,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
232
|
+
return this.replayOrConflict(context, attempt);
|
|
211
233
|
}
|
|
234
|
+
return this.run(context, attempt, next);
|
|
235
|
+
}
|
|
236
|
+
/** Runs the handler under this execution's claim, then completes the claim. */
|
|
237
|
+
async run(context, attempt, next) {
|
|
212
238
|
let response;
|
|
213
239
|
try {
|
|
214
240
|
response = await next();
|
|
215
241
|
}
|
|
216
242
|
catch (error) {
|
|
217
|
-
if (options.releaseOnError ?? true) {
|
|
218
|
-
await this.release(key, claimId);
|
|
243
|
+
if (attempt.options.releaseOnError ?? true) {
|
|
244
|
+
await this.release(attempt.key, attempt.claimId);
|
|
219
245
|
}
|
|
220
246
|
throw error;
|
|
221
247
|
}
|
|
222
|
-
|
|
248
|
+
const responseSnapshot = this.snapshot(context, attempt, response);
|
|
249
|
+
await this.complete(context, attempt, responseSnapshot);
|
|
250
|
+
return response;
|
|
251
|
+
}
|
|
252
|
+
/** The replayable JSON snapshot of a successful handler's response. */
|
|
253
|
+
snapshot(context, attempt, response) {
|
|
254
|
+
const { key, claimId } = attempt;
|
|
223
255
|
try {
|
|
224
|
-
|
|
256
|
+
return toJsonSnapshot(response);
|
|
225
257
|
}
|
|
226
258
|
catch (cause) {
|
|
227
259
|
// The handler already succeeded, so retain the claim until expiry rather
|
|
@@ -231,17 +263,24 @@ export class IdempotencyBehavior {
|
|
|
231
263
|
'expires; retrying the business request may repeat side effects.', cause.stack, IdempotencyBehavior.name);
|
|
232
264
|
throw new IdempotencyCompletionError(key, claimId, cause, 'snapshot');
|
|
233
265
|
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Stores the completed record while this execution still owns the claim, and
|
|
269
|
+
* flags the context when a newer claim replaced it.
|
|
270
|
+
*/
|
|
271
|
+
async complete(context, attempt, responseSnapshot) {
|
|
272
|
+
const { key, claimId } = attempt;
|
|
234
273
|
// The handler has already succeeded. Completion must be conditional on
|
|
235
274
|
// still owning the claim; a stale execution must never overwrite a newer
|
|
236
275
|
// execution that reclaimed the key after this claim's TTL elapsed.
|
|
237
276
|
let completed;
|
|
238
277
|
try {
|
|
239
278
|
completed = await this.store.completeIfOwned(key, claimId, {
|
|
240
|
-
...claim,
|
|
279
|
+
...attempt.claim,
|
|
241
280
|
status: 'completed',
|
|
242
281
|
response: responseSnapshot,
|
|
243
282
|
completedAt: new Date().toISOString(),
|
|
244
|
-
}, ttl);
|
|
283
|
+
}, attempt.ttl);
|
|
245
284
|
}
|
|
246
285
|
catch (cause) {
|
|
247
286
|
this.logger.error?.(`Idempotency completion persistence failed after ${context.requestName} ` +
|
|
@@ -255,59 +294,47 @@ export class IdempotencyBehavior {
|
|
|
255
294
|
`completed successfully (key: ${key}). The successful result is being ` +
|
|
256
295
|
'returned, but this execution did not overwrite the newer claim.', IdempotencyBehavior.name);
|
|
257
296
|
}
|
|
258
|
-
return response;
|
|
259
297
|
}
|
|
260
298
|
/**
|
|
261
299
|
* The key was already claimed. Replay a completed response, or surface a
|
|
262
300
|
* conflict for an in-progress duplicate, a key reused with a new payload, or a
|
|
263
301
|
* completed record whose replay scope does not match the caller's.
|
|
264
302
|
*/
|
|
265
|
-
async replayOrConflict(context,
|
|
266
|
-
const existing = knownExisting ?? (await this.store.get(key));
|
|
303
|
+
async replayOrConflict(context, attempt, knownExisting) {
|
|
304
|
+
const existing = knownExisting ?? (await this.store.get(attempt.key));
|
|
305
|
+
this.refuseReplay(context, attempt, existing);
|
|
306
|
+
setPipelineItem(context, IDEMPOTENCY_REPLAYED_ITEM_TOKEN, true);
|
|
307
|
+
this.logger.debug?.(`Replaying idempotent response for ${context.requestName} (key: ${attempt.key})`, IdempotencyBehavior.name);
|
|
308
|
+
return existing.response;
|
|
309
|
+
}
|
|
310
|
+
/** Throws the conflict that forbids replaying `existing` to this caller. */
|
|
311
|
+
refuseReplay(context, attempt, existing) {
|
|
312
|
+
const { key, fingerprint } = attempt;
|
|
267
313
|
// A second failed claim followed by another disappearing record indicates
|
|
268
314
|
// repeated contention. The retry is deliberately bounded to avoid a hot
|
|
269
315
|
// loop if a backend is unstable or claims are being rapidly replaced.
|
|
270
316
|
if (!existing) {
|
|
271
|
-
throw
|
|
272
|
-
key,
|
|
273
|
-
requestName: context.requestName,
|
|
274
|
-
reason: 'in_progress',
|
|
275
|
-
});
|
|
317
|
+
throw conflict(context, key, 'in_progress');
|
|
276
318
|
}
|
|
277
319
|
// A fingerprinted request never replays a record that cannot prove payload identity.
|
|
278
320
|
if (existing.requestName !== context.requestName ||
|
|
279
321
|
(fingerprint && existing.fingerprint !== fingerprint)) {
|
|
280
|
-
throw
|
|
281
|
-
key,
|
|
282
|
-
requestName: context.requestName,
|
|
283
|
-
reason: 'key_reuse',
|
|
284
|
-
});
|
|
322
|
+
throw conflict(context, key, 'key_reuse');
|
|
285
323
|
}
|
|
286
324
|
if (existing.status === 'in_progress') {
|
|
287
|
-
throw
|
|
288
|
-
key,
|
|
289
|
-
requestName: context.requestName,
|
|
290
|
-
reason: 'in_progress',
|
|
291
|
-
});
|
|
325
|
+
throw conflict(context, key, 'in_progress');
|
|
292
326
|
}
|
|
293
327
|
// A completed record may only be replayed to a caller whose authorization
|
|
294
328
|
// scope matches the one it was produced under. A record stored without a
|
|
295
329
|
// scope cannot prove that, so it fails closed rather than replaying. The
|
|
296
330
|
// record is retained: re-executing a completed operation would duplicate it.
|
|
297
|
-
if (
|
|
331
|
+
if (attempt.scopeRequired &&
|
|
298
332
|
(existing.replayScope === undefined ||
|
|
299
|
-
existing.replayScope !==
|
|
333
|
+
existing.replayScope !== attempt.replayScope)) {
|
|
300
334
|
this.logger.warn?.(`Refusing to replay ${context.requestName} (key: ${key}): the stored ` +
|
|
301
335
|
'response was authorized under a different scope than this caller.', IdempotencyBehavior.name);
|
|
302
|
-
throw
|
|
303
|
-
key,
|
|
304
|
-
requestName: context.requestName,
|
|
305
|
-
reason: 'replay_scope',
|
|
306
|
-
});
|
|
336
|
+
throw conflict(context, key, 'replay_scope');
|
|
307
337
|
}
|
|
308
|
-
setPipelineItem(context, IDEMPOTENCY_REPLAYED_ITEM_TOKEN, true);
|
|
309
|
-
this.logger.debug?.(`Replaying idempotent response for ${context.requestName} (key: ${key})`, IdempotencyBehavior.name);
|
|
310
|
-
return existing.response;
|
|
311
338
|
}
|
|
312
339
|
/**
|
|
313
340
|
* Release a failed execution's claim only if it still owns the key; never let
|
|
@@ -329,4 +356,11 @@ export class IdempotencyBehavior {
|
|
|
329
356
|
return { ...this.defaults, ...options };
|
|
330
357
|
}
|
|
331
358
|
}
|
|
359
|
+
function conflict(context, key, reason) {
|
|
360
|
+
return new IdempotencyConflictError({
|
|
361
|
+
key,
|
|
362
|
+
requestName: context.requestName,
|
|
363
|
+
reason,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
332
366
|
//# sourceMappingURL=idempotency.behavior.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency.behavior.js","sourceRoot":"","sources":["../src/idempotency.behavior.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,kBAAkB,EAMlB,0BAA0B,EAM1B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAC;AACtF,OAAO,
|
|
1
|
+
{"version":3,"file":"idempotency.behavior.js","sourceRoot":"","sources":["../src/idempotency.behavior.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,kBAAkB,EAMlB,0BAA0B,EAM1B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAC;AACtF,OAAO,EACL,wBAAwB,GAEzB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAS5D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GACrC,kBAAkB,CAAS,sBAAsB,EAAE,oBAAoB,CAAC,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAE7E;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAC1C,kBAAkB,CAChB,2BAA2B,EAC3B,yBAAyB,CAC1B,CAAC;AAEJ;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,MAAM,CACnD,iCAAiC,CAClC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,qCAAqC,GAChD,kBAAkB,CAChB,iCAAiC,EACjC,+BAA+B,CAChC,CAAC;AAEJ,MAAM,aAAa,GAA6B,CAAC,SAAS,CAAC,CAAC;AAE5D,+DAA+D;AAC/D,SAAS,OAAO,CACd,OAA+C,EAC/C,WAA4C;IAE5C,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,aAAa,CAAC,CAAC,QAAQ,CAC/C,WAAqC,CACtC,CAAC;AACJ,CAAC;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,OAAO,mBAAmB;IAkEX,KAAK;IA7DxB,MAAM,CAAU,CAAC,0BAA0B,CAAC,GAA8B;QACxE,KAAK,EAAE,CACL,OAA0C,EACH,EAAE;YACzC,MAAM,OAAO,GAAG,OAAO,CAAC,gBAEX,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3C,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,OAAO;gBACL,KAAK,EAAE,CAAC,sCAAsC,EAAE,cAAc,CAAC;aAChE,CAAC;QACJ,CAAC;QACD,QAAQ,EAAE,CACR,OAA0C,EACA,EAAE;YAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,gBAEX,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3C,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,IACE,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS;gBACtC,OAAO,CAAC,iBAAiB,KAAK,MAAM,CAAC;gBACvC,CAAC,OAAO,EAAE,UAAU,EACpB,CAAC;gBACD,OAAO;oBACL;wBACE,WAAW,EAAE,OAAO,CAAC,WAAW;wBAChC,YAAY,EAAE,mBAAmB,CAAC,IAAI;wBACtC,OAAO,EACL,yFAAyF;wBAC3F,GAAG,EACD,6DAA6D;4BAC7D,qDAAqD;qBACxD;iBACF,CAAC;YACJ,CAAC;YAED,IAAI,OAAO,EAAE,UAAU,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;gBACpE,OAAO;oBACL;wBACE,WAAW,EAAE,OAAO,CAAC,WAAW;wBAChC,YAAY,EAAE,mBAAmB,CAAC,IAAI;wBACtC,OAAO,EAAE,wEAAwE,OAAO,OAAO,CAAC,UAAU,EAAE;wBAC5G,GAAG,EAAE,yGAAyG;qBAC/G;iBACF,CAAC;YACJ,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;IAEe,MAAM,CAAiB;IACvB,QAAQ,CAA6B;IAEtD,YACmB,KAAuB,EACxC,QAAqC,EACrC,MAAuB;qBAFN,KAAK;QAItB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5C,IACE,OAAO,SAAS,CAAC,eAAe,KAAK,UAAU;YAC/C,OAAO,SAAS,CAAC,aAAa,KAAK,UAAU,EAC7C,CAAC;YACD,MAAM,IAAI,SAAS,CACjB,+DAA+D;gBAC7D,oFAAoF,CACvF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,MAAM,CACV,OAAyB,EACzB,IAAkB;QAElB,MAAM,OAAO,GAAG,IAAI,CAAC,uBAAuB,CAC1C,OAAO,CAAC,kBAAkB,CACxB,mBAAmB,CACpB,CACF,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3C,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,wDAAwD;YACxD,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,eAAe,CAAC,OAAO,EAAE,0BAA0B,EAAE,GAAG,CAAC,CAAC;QAE1D,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAC1C,GAAG,EACH,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,GAAG,CACZ,CAAC;QACF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACK,YAAY,CAClB,OAAyB,EACzB,OAAmC,EACnC,GAAW;QAEX,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,0BAA0B,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,SAAS,CACjB,kEAAkE,CACnE,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GACf,CAAC,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;YAC3B,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC;YACnC,CAAC,CAAC,SAAS,CAAC;QAChB,6EAA6E;QAC7E,uCAAuC;QACvC,MAAM,WAAW,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,aAAa,GAAG,OAAO,CAAC,kBAAkB,KAAK,SAAS,CAAC;QAC/D,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAE7B,MAAM,KAAK,GAAsB;YAC/B,GAAG;YACH,MAAM,EAAE,aAAa;YACrB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,OAAO;YACP,WAAW;YACX,WAAW;YACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,OAAO;YACL,GAAG;YACH,OAAO;YACP,GAAG;YACH,WAAW;YACX,WAAW;YACX,aAAa;YACb,KAAK;YACL,OAAO;SACR,CAAC;IACJ,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,eAAe,CAC3B,OAAyB,EACzB,OAAqB,EACrB,IAAkB;QAElB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC3D,CAAC;QAED,sEAAsE;QACtE,yEAAyE;QACzE,6CAA6C;QAC7C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAC1C,OAAO,CAAC,GAAG,EACX,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,GAAG,CACZ,CAAC;QACF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,+EAA+E;IACvE,KAAK,CAAC,GAAG,CACf,OAAyB,EACzB,OAAqB,EACrB,IAAkB;QAElB,IAAI,QAAiB,CAAC;QACtB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,CAAC,OAAO,CAAC,cAAc,IAAI,IAAI,EAAE,CAAC;gBAC3C,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACnD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACnE,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAExD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,uEAAuE;IAC/D,QAAQ,CACd,OAAyB,EACzB,OAAqB,EACrB,QAAiB;QAEjB,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QACjC,IAAI,CAAC;YACH,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,qEAAqE;YACrE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CACjB,8CAA8C,OAAO,CAAC,WAAW,GAAG;gBAClE,+BAA+B,GAAG,oCAAoC;gBACtE,iEAAiE,EAClE,KAAmB,CAAC,KAAK,EAC1B,mBAAmB,CAAC,IAAI,CACzB,CAAC;YACF,MAAM,IAAI,0BAA0B,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,QAAQ,CACpB,OAAyB,EACzB,OAAqB,EACrB,gBAAuC;QAEvC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QACjC,uEAAuE;QACvE,yEAAyE;QACzE,mEAAmE;QACnE,IAAI,SAAkB,CAAC;QACvB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,eAAe,CAC1C,GAAG,EACH,OAAO,EACP;gBACE,GAAG,OAAO,CAAC,KAAK;gBAChB,MAAM,EAAE,WAAW;gBACnB,QAAQ,EAAE,gBAAgB;gBAC1B,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACtC,EACD,OAAO,CAAC,GAAG,CACZ,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CACjB,mDAAmD,OAAO,CAAC,WAAW,GAAG;gBACvE,+BAA+B,GAAG,uCAAuC;gBACzE,sBAAsB,EACxB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EAChD,mBAAmB,CAAC,IAAI,CACzB,CAAC;YACF,MAAM,IAAI,0BAA0B,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,eAAe,CAAC,OAAO,EAAE,qCAAqC,EAAE,IAAI,CAAC,CAAC;YACtE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CACjB,8CAA8C,OAAO,CAAC,WAAW,GAAG;gBAClE,gCAAgC,GAAG,oCAAoC;gBACvE,iEAAiE,EACnE,mBAAmB,CAAC,IAAI,CACzB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,gBAAgB,CAC5B,OAAyB,EACzB,OAAqB,EACrB,aAAiC;QAEjC,MAAM,QAAQ,GAAG,aAAa,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAE9C,eAAe,CAAC,OAAO,EAAE,+BAA+B,EAAE,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CACjB,qCAAqC,OAAO,CAAC,WAAW,UAAU,OAAO,CAAC,GAAG,GAAG,EAChF,mBAAmB,CAAC,IAAI,CACzB,CAAC;QAEF,OAAO,QAAQ,CAAC,QAAQ,CAAC;IAC3B,CAAC;IAED,4EAA4E;IACpE,YAAY,CAClB,OAAyB,EACzB,OAAqB,EACrB,QAAuC;QAEvC,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAErC,0EAA0E;QAC1E,wEAAwE;QACxE,sEAAsE;QACtE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;QAC9C,CAAC;QAED,qFAAqF;QACrF,IACE,QAAQ,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW;YAC5C,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,KAAK,WAAW,CAAC,EACrD,CAAC;YACD,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YACtC,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;QAC9C,CAAC;QAED,0EAA0E;QAC1E,yEAAyE;QACzE,yEAAyE;QACzE,6EAA6E;QAC7E,IACE,OAAO,CAAC,aAAa;YACrB,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS;gBACjC,QAAQ,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,EAC/C,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAChB,sBAAsB,OAAO,CAAC,WAAW,UAAU,GAAG,gBAAgB;gBACpE,mEAAmE,EACrE,mBAAmB,CAAC,IAAI,CACzB,CAAC;YACF,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,OAAe;QAChD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAChB,sCAAsC,GAAG,KAAK;gBAC5C,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EACrD,mBAAmB,CAAC,IAAI,CACzB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,uBAAuB,CACrB,OAAoC;QAEpC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAC;QACnC,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IAC1C,CAAC;CACF;AAED,SAAS,QAAQ,CACf,OAAyB,EACzB,GAAW,EACX,MAAiC;IAEjC,OAAO,IAAI,wBAAwB,CAAC;QAClC,GAAG;QACH,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -88,9 +88,10 @@ export interface IdempotencyBehaviorOptions {
|
|
|
88
88
|
*/
|
|
89
89
|
replayScopeFactory?: IdempotencyReplayScopeFactory;
|
|
90
90
|
/**
|
|
91
|
-
* When the handler throws, release the key so
|
|
92
|
-
* (`true`, default)
|
|
93
|
-
*
|
|
91
|
+
* When the handler throws, release the key so a retry runs the handler again
|
|
92
|
+
* (`true`, default), including any effect the failed attempt had before it
|
|
93
|
+
* threw. Set `false` to keep the key claimed until it expires and surface a
|
|
94
|
+
* conflict on retry (favors at-most-once over retryability).
|
|
94
95
|
*/
|
|
95
96
|
releaseOnError?: boolean;
|
|
96
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency-options.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/idempotency-options.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC1C,OAAO,EAAE,gBAAgB,KACtB,MAAM,GAAG,SAAS,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,OAAO,EAAE,gBAAgB,KACtB,MAAM,GAAG,SAAS,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAEnC;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,KAAK,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IAEnD
|
|
1
|
+
{"version":3,"file":"idempotency-options.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/idempotency-options.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC1C,OAAO,EAAE,gBAAgB,KACtB,MAAM,GAAG,SAAS,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,OAAO,EAAE,gBAAgB,KACtB,MAAM,GAAG,SAAS,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAEnC;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,KAAK,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IAEnD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B"}
|
|
@@ -11,8 +11,9 @@ export interface MemoryIdempotencyStoreOptions {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Zero-dependency, in-process {@link IdempotencyStore} backed by a `Map` with
|
|
14
|
-
* per-entry expiry and bounded memory capacity
|
|
15
|
-
*
|
|
14
|
+
* per-entry expiry and bounded memory capacity, for a single instance, tests or
|
|
15
|
+
* local development. `IdempotencyBehavior` has no default store: pass this one, or a
|
|
16
|
+
* shared store, to its constructor.
|
|
16
17
|
*
|
|
17
18
|
* Because Node runs JavaScript on a single thread, {@link setIfAbsent},
|
|
18
19
|
* {@link completeIfOwned}, and {@link deleteIfOwned} are effectively atomic
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.store.d.ts","sourceRoot":"","sources":["../../src/stores/memory.store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+CAA+C,CAAC;AACvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAOrF,MAAM,WAAW,6BAA6B;IAC5C,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED
|
|
1
|
+
{"version":3,"file":"memory.store.d.ts","sourceRoot":"","sources":["../../src/stores/memory.store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+CAA+C,CAAC;AACvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAOrF,MAAM,WAAW,6BAA6B;IAC5C,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAiB;IAE/C,YAAY,OAAO,CAAC,EAAE,6BAA6B,EAWlD;IAED,iDAAiD;IACjD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,cAAc,IAAI,MAAM,CAUvB;IAED,qEAAqE;IACrE,OAAO,IAAI,IAAI,CAId;IAED,OAAO,CAAC,cAAc;IAYtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAG9C;IAED,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAS1E;IAED,eAAe,CACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,OAAO,CAYT;IAED,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAMnD;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAK/D;IAED,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,OAAO,CAAC,KAAK;IAOb,gFAAgF;IAChF,OAAO,CAAC,IAAI;CASb"}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { cloneIdempotencyRecord } from '../helpers/json-snapshot.js';
|
|
3
3
|
/**
|
|
4
4
|
* Zero-dependency, in-process {@link IdempotencyStore} backed by a `Map` with
|
|
5
|
-
* per-entry expiry and bounded memory capacity
|
|
6
|
-
*
|
|
5
|
+
* per-entry expiry and bounded memory capacity, for a single instance, tests or
|
|
6
|
+
* local development. `IdempotencyBehavior` has no default store: pass this one, or a
|
|
7
|
+
* shared store, to its constructor.
|
|
7
8
|
*
|
|
8
9
|
* Because Node runs JavaScript on a single thread, {@link setIfAbsent},
|
|
9
10
|
* {@link completeIfOwned}, and {@link deleteIfOwned} are effectively atomic
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.store.js","sourceRoot":"","sources":["../../src/stores/memory.store.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAmBrE
|
|
1
|
+
{"version":3,"file":"memory.store.js","sourceRoot":"","sources":["../../src/stores/memory.store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cqrs-ddd/pipeline-idempotency",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Idempotency for @cqrs-ddd/pipeline: deduplicates commands by an operation key and replays completed responses within the caller's scope",
|
|
5
5
|
"author": "Aristotelis",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@cqrs-ddd/pipeline": "^0.5.
|
|
51
|
+
"@cqrs-ddd/pipeline": "^0.5.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@
|
|
55
|
-
"vitest": "^5.0.
|
|
56
|
-
"
|
|
54
|
+
"@cqrs-ddd/pipeline": "0.5.2",
|
|
55
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
56
|
+
"vitest": "^5.0.5"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@cqrs-ddd/
|
|
60
|
-
"@cqrs-ddd/
|
|
59
|
+
"@cqrs-ddd/safe-stringify": "^0.5.2",
|
|
60
|
+
"@cqrs-ddd/untyped": "^0.5.2"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsc -p tsconfig.build.json",
|