@cqrs-ddd/pipeline-idempotency 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 +39 -0
- package/dist/constants/tokens.d.ts +3 -0
- package/dist/constants/tokens.d.ts.map +1 -0
- package/dist/constants/tokens.js +4 -0
- package/dist/constants/tokens.js.map +1 -0
- package/dist/errors/idempotency-completion.error.d.ts +32 -0
- package/dist/errors/idempotency-completion.error.d.ts.map +1 -0
- package/dist/errors/idempotency-completion.error.js +33 -0
- package/dist/errors/idempotency-completion.error.js.map +1 -0
- package/dist/errors/idempotency-conflict.error.d.ts +33 -0
- package/dist/errors/idempotency-conflict.error.d.ts.map +1 -0
- package/dist/errors/idempotency-conflict.error.js +48 -0
- package/dist/errors/idempotency-conflict.error.js.map +1 -0
- package/dist/errors/missing-partition.error.d.ts +17 -0
- package/dist/errors/missing-partition.error.d.ts.map +1 -0
- package/dist/errors/missing-partition.error.js +18 -0
- package/dist/errors/missing-partition.error.js.map +1 -0
- package/dist/helpers/build-attributes.d.ts +47 -0
- package/dist/helpers/build-attributes.d.ts.map +1 -0
- package/dist/helpers/build-attributes.js +59 -0
- package/dist/helpers/build-attributes.js.map +1 -0
- package/dist/helpers/fingerprint.d.ts +8 -0
- package/dist/helpers/fingerprint.d.ts.map +1 -0
- package/dist/helpers/fingerprint.js +13 -0
- package/dist/helpers/fingerprint.js.map +1 -0
- package/dist/helpers/idempotency.intent.d.ts +31 -0
- package/dist/helpers/idempotency.intent.d.ts.map +1 -0
- package/dist/helpers/idempotency.intent.js +26 -0
- package/dist/helpers/idempotency.intent.js.map +1 -0
- package/dist/helpers/json-snapshot.d.ts +6 -0
- package/dist/helpers/json-snapshot.d.ts.map +1 -0
- package/dist/helpers/json-snapshot.js +23 -0
- package/dist/helpers/json-snapshot.js.map +1 -0
- package/dist/helpers/partitioned-key.d.ts +96 -0
- package/dist/helpers/partitioned-key.d.ts.map +1 -0
- package/dist/helpers/partitioned-key.js +36 -0
- package/dist/helpers/partitioned-key.js.map +1 -0
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +26 -0
- package/dist/http.js.map +1 -0
- package/dist/idempotency.behavior.d.ts +117 -0
- package/dist/idempotency.behavior.d.ts.map +1 -0
- package/dist/idempotency.behavior.js +332 -0
- package/dist/idempotency.behavior.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/idempotency-options.interface.d.ts +97 -0
- package/dist/interfaces/idempotency-options.interface.d.ts.map +1 -0
- package/dist/interfaces/idempotency-options.interface.js +3 -0
- package/dist/interfaces/idempotency-options.interface.js.map +1 -0
- package/dist/interfaces/idempotency-record.interface.d.ts +52 -0
- package/dist/interfaces/idempotency-record.interface.d.ts.map +1 -0
- package/dist/interfaces/idempotency-record.interface.js +3 -0
- package/dist/interfaces/idempotency-record.interface.js.map +1 -0
- package/dist/interfaces/idempotency-store.interface.d.ts +59 -0
- package/dist/interfaces/idempotency-store.interface.d.ts.map +1 -0
- package/dist/interfaces/idempotency-store.interface.js +3 -0
- package/dist/interfaces/idempotency-store.interface.js.map +1 -0
- package/dist/stores/memory.store.d.ts +48 -0
- package/dist/stores/memory.store.d.ts.map +1 -0
- package/dist/stores/memory.store.js +119 -0
- package/dist/stores/memory.store.js.map +1 -0
- package/dist/stores/postgres.store.d.ts +72 -0
- package/dist/stores/postgres.store.d.ts.map +1 -0
- package/dist/stores/postgres.store.js +187 -0
- package/dist/stores/postgres.store.js.map +1 -0
- package/dist/stores/redis.store.d.ts +62 -0
- package/dist/stores/redis.store.d.ts.map +1 -0
- package/dist/stores/redis.store.js +79 -0
- package/dist/stores/redis.store.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { tenantSegments, } from '@cqrs-ddd/pipeline';
|
|
3
|
+
import { joinKeySegments } from '@cqrs-ddd/safe-stringify';
|
|
4
|
+
import { MissingIdempotencyPartitionError } from '../errors/missing-partition.error.js';
|
|
5
|
+
export function createPartitionedIdempotencyKeyFactory(options) {
|
|
6
|
+
const onMissingOperation = options.onMissingOperation ?? 'throw';
|
|
7
|
+
return (context) => {
|
|
8
|
+
const tenant = tenantSegments(context, options, MissingIdempotencyPartitionError);
|
|
9
|
+
const principal = principalSegments(options.principal(context));
|
|
10
|
+
if (!principal) {
|
|
11
|
+
throw new MissingIdempotencyPartitionError(context.requestName, 'principal', 'Resolve the principal from authenticated context. There is no shared ' +
|
|
12
|
+
'fallback: it would merge every unresolved caller into one namespace.');
|
|
13
|
+
}
|
|
14
|
+
const operation = options.operation(context)?.trim();
|
|
15
|
+
if (!operation) {
|
|
16
|
+
if (onMissingOperation === 'skip')
|
|
17
|
+
return undefined;
|
|
18
|
+
throw new MissingIdempotencyPartitionError(context.requestName, 'operation', "Return an operation identifier, or configure onMissingOperation: 'skip'.");
|
|
19
|
+
}
|
|
20
|
+
return joinKeySegments([
|
|
21
|
+
...(options.version ? [options.version] : []),
|
|
22
|
+
...tenant,
|
|
23
|
+
...principal,
|
|
24
|
+
options.action ?? context.requestName,
|
|
25
|
+
operation,
|
|
26
|
+
]);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Normalizes a principal to non-empty trimmed segments, or `undefined`. */
|
|
30
|
+
function principalSegments(value) {
|
|
31
|
+
const segments = (typeof value === 'string' ? [value] : (value ?? [])).map((segment) => (typeof segment === 'string' ? segment.trim() : ''));
|
|
32
|
+
return segments.length > 0 && segments.every((segment) => segment !== '')
|
|
33
|
+
? segments
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=partitioned-key.js.map
|
|
@@ -0,0 +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,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,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;AACJ,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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IdempotencyConflictError } from './errors/idempotency-conflict.error.js';
|
|
2
|
+
/** An HTTP answer for an error: status, JSON body and headers. */
|
|
3
|
+
export interface HttpResponse {
|
|
4
|
+
status: number;
|
|
5
|
+
body: Record<string, unknown>;
|
|
6
|
+
headers: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The HTTP answer for an {@link IdempotencyConflictError}: `409 Conflict` for an
|
|
10
|
+
* operation in progress or a different replay scope, `422 Unprocessable Entity` for a
|
|
11
|
+
* key reused with another payload.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const { status, body } = toHttpResponse(error);
|
|
16
|
+
* res.status(status).json(body);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function toHttpResponse(error: IdempotencyConflictError): HttpResponse;
|
|
20
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AAEvF,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,wBAAwB,GAAG,YAAY,CAY5E"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
/**
|
|
3
|
+
* The HTTP answer for an {@link IdempotencyConflictError}: `409 Conflict` for an
|
|
4
|
+
* operation in progress or a different replay scope, `422 Unprocessable Entity` for a
|
|
5
|
+
* key reused with another payload.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const { status, body } = toHttpResponse(error);
|
|
10
|
+
* res.status(status).json(body);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function toHttpResponse(error) {
|
|
14
|
+
return {
|
|
15
|
+
status: error.statusCode,
|
|
16
|
+
body: {
|
|
17
|
+
statusCode: error.statusCode,
|
|
18
|
+
error: error.statusCode === 409 ? 'Conflict' : 'Unprocessable Entity',
|
|
19
|
+
message: error.message,
|
|
20
|
+
idempotencyKey: error.key,
|
|
21
|
+
reason: error.reason,
|
|
22
|
+
},
|
|
23
|
+
headers: {},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAWtE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,KAA+B;IAC5D,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,UAAU;QACxB,IAAI,EAAE;YACJ,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,sBAAsB;YACrE,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,cAAc,EAAE,KAAK,CAAC,GAAG;YACzB,MAAM,EAAE,KAAK,CAAC,MAAM;SACrB;QACD,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type IPipelineBehavior, type IPipelineBehaviorContract, type IPipelineBehaviorOptionsResolver, type IPipelineContext, type NextDelegate, PIPELINE_BEHAVIOR_CONTRACT, type PipelineItemToken, type PipelineLogger } from '@cqrs-ddd/pipeline';
|
|
2
|
+
import type { IdempotencyBehaviorOptions } from './interfaces/idempotency-options.interface.js';
|
|
3
|
+
import type { IdempotencyStore } from './interfaces/idempotency-store.interface.js';
|
|
4
|
+
/**
|
|
5
|
+
* Unique symbol key set on `context.items` holding the active idempotency key string.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const key = context.items.get(IDEMPOTENCY_KEY_ITEM) as string | undefined;
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare const IDEMPOTENCY_KEY_ITEM: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* Typed token for {@link IDEMPOTENCY_KEY_ITEM}: the active idempotency key. Reads and writes the same
|
|
15
|
+
* `context.items` entry through `getPipelineItem` / `requirePipelineItem`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const IDEMPOTENCY_KEY_ITEM_TOKEN: PipelineItemToken<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Unique symbol key set on `context.items` to `true` when the response was replayed
|
|
20
|
+
* from a previously-stored record (the handler did not run this time).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const wasReplayed = context.items.get(IDEMPOTENCY_REPLAYED_ITEM) === true;
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const IDEMPOTENCY_REPLAYED_ITEM: unique symbol;
|
|
28
|
+
/**
|
|
29
|
+
* Typed token for {@link IDEMPOTENCY_REPLAYED_ITEM}: the idempotent replay flag. Reads and writes the same
|
|
30
|
+
* `context.items` entry through `getPipelineItem` / `requirePipelineItem`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const IDEMPOTENCY_REPLAYED_ITEM_TOKEN: PipelineItemToken<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Unique symbol key set on `context.items` to `true` when a handler completed after
|
|
35
|
+
* its claim had expired or been replaced. The successful handler result is returned,
|
|
36
|
+
* but this execution is not allowed to overwrite the newer owner's replay state.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const ownershipLost = context.items.get(IDEMPOTENCY_OWNERSHIP_LOST_ITEM) === true;
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare const IDEMPOTENCY_OWNERSHIP_LOST_ITEM: unique symbol;
|
|
44
|
+
/**
|
|
45
|
+
* Typed token for {@link IDEMPOTENCY_OWNERSHIP_LOST_ITEM}: the idempotency ownership-lost flag. Reads and writes the same
|
|
46
|
+
* `context.items` entry through `getPipelineItem` / `requirePipelineItem`.
|
|
47
|
+
*/
|
|
48
|
+
export declare const IDEMPOTENCY_OWNERSHIP_LOST_ITEM_TOKEN: PipelineItemToken<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Pipeline behavior that deduplicates concurrent requests sharing an
|
|
51
|
+
* idempotency key and replays the stored response after a successful execution.
|
|
52
|
+
*
|
|
53
|
+
* For each in-scope request it derives a key
|
|
54
|
+
* ({@link IdempotencyBehaviorOptions.keyFactory}), then atomically claims it in
|
|
55
|
+
* a pluggable {@link IdempotencyStore}:
|
|
56
|
+
*
|
|
57
|
+
* - **first claim** → run the handler and store the completed response;
|
|
58
|
+
* - **duplicate, completed** → return the stored response (no re-execution);
|
|
59
|
+
* - **duplicate, in progress** → throw {@link IdempotencyConflictError} (`409`);
|
|
60
|
+
* - **key reused with a different payload** → throw it as `key_reuse` (`422`);
|
|
61
|
+
* - **completed under a different authorization scope** → throw it as
|
|
62
|
+
* `replay_scope` (`409`), when a
|
|
63
|
+
* {@link IdempotencyBehaviorOptions.replayScopeFactory} is configured. The
|
|
64
|
+
* record is kept and the handler is not re-executed, so a permission change
|
|
65
|
+
* cannot duplicate the effect.
|
|
66
|
+
*
|
|
67
|
+
* Each claim carries a unique owner token. Completion and release compare that
|
|
68
|
+
* token atomically, so an execution that outlives its TTL cannot overwrite or
|
|
69
|
+
* delete a newer claim after the key is reclaimed.
|
|
70
|
+
*
|
|
71
|
+
* When the handler throws, `releaseOnError` controls whether the key remains
|
|
72
|
+
* claimed. It defaults to `true`, so failed executions release the key and a
|
|
73
|
+
* later retry may execute the handler again. Set it to `false` when retaining
|
|
74
|
+
* the claim after a failure is preferable to retryability.
|
|
75
|
+
*
|
|
76
|
+
* Store-agnostic by design: memory (default), Redis, Postgres, or your own are
|
|
77
|
+
* one-line swaps of the store passed to the constructor. When no key is produced,
|
|
78
|
+
* the handler runs normally.
|
|
79
|
+
*
|
|
80
|
+
* @example Per-handler idempotency
|
|
81
|
+
* Keyed off an optional `Idempotency-Key` header and partitioned by tenant and principal:
|
|
82
|
+
* ```ts
|
|
83
|
+
* class CreatePaymentHandler {
|
|
84
|
+
* @pipeline.wrap({ kind: 'command' }, [IdempotencyBehavior, {
|
|
85
|
+
* keyFactory: createPartitionedIdempotencyKeyFactory({
|
|
86
|
+
* principal: (c) => c.items.get('currentUserId') as string | undefined,
|
|
87
|
+
* operation: (c) => c.items.get('idempotencyKey') as string | undefined,
|
|
88
|
+
* onMissingOperation: 'skip',
|
|
89
|
+
* }),
|
|
90
|
+
* ttl: 86_400_000,
|
|
91
|
+
* }])
|
|
92
|
+
* async handle(command: CreatePaymentCommand) {}
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare class IdempotencyBehavior implements IPipelineBehavior, IPipelineBehaviorOptionsResolver<IdempotencyBehaviorOptions> {
|
|
97
|
+
private readonly store;
|
|
98
|
+
static readonly [PIPELINE_BEHAVIOR_CONTRACT]: IPipelineBehaviorContract;
|
|
99
|
+
private readonly logger;
|
|
100
|
+
private readonly defaults;
|
|
101
|
+
constructor(store: IdempotencyStore, defaults?: IdempotencyBehaviorOptions, logger?: PipelineLogger);
|
|
102
|
+
handle(context: IPipelineContext, next: NextDelegate): Promise<unknown>;
|
|
103
|
+
/**
|
|
104
|
+
* The key was already claimed. Replay a completed response, or surface a
|
|
105
|
+
* conflict for an in-progress duplicate, a key reused with a new payload, or a
|
|
106
|
+
* completed record whose replay scope does not match the caller's.
|
|
107
|
+
*/
|
|
108
|
+
private replayOrConflict;
|
|
109
|
+
/**
|
|
110
|
+
* Release a failed execution's claim only if it still owns the key; never let
|
|
111
|
+
* cleanup break propagation of the original handler error.
|
|
112
|
+
*/
|
|
113
|
+
private release;
|
|
114
|
+
/** Shallow-merges pipeline-level options over the constructor defaults. */
|
|
115
|
+
resolveEffectiveOptions(options?: IdempotencyBehaviorOptions): IdempotencyBehaviorOptions;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=idempotency.behavior.d.ts.map
|
|
@@ -0,0 +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;AAO5B,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;AAcJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAuIlB;IAED;;;;OAIG;YACW,gBAAgB;IAqE9B;;;OAGG;YACW,OAAO;IAYrB,2EAA2E;IAC3E,uBAAuB,CACrB,OAAO,CAAC,EAAE,0BAA0B,GACnC,0BAA0B,CAG5B;CACF"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createPipelineItem, PIPELINE_BEHAVIOR_CONTRACT, setPipelineItem, } from '@cqrs-ddd/pipeline';
|
|
4
|
+
import { untyped } from '@cqrs-ddd/untyped';
|
|
5
|
+
import { DEFAULT_IDEMPOTENCY_TTL_MS } from './constants/tokens.js';
|
|
6
|
+
import { IdempotencyCompletionError } from './errors/idempotency-completion.error.js';
|
|
7
|
+
import { IdempotencyConflictError } from './errors/idempotency-conflict.error.js';
|
|
8
|
+
import { fingerprintValue } from './helpers/fingerprint.js';
|
|
9
|
+
import { toJsonSnapshot } from './helpers/json-snapshot.js';
|
|
10
|
+
/**
|
|
11
|
+
* Unique symbol key set on `context.items` holding the active idempotency key string.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const key = context.items.get(IDEMPOTENCY_KEY_ITEM) as string | undefined;
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export const IDEMPOTENCY_KEY_ITEM = Symbol('IDEMPOTENCY_KEY_ITEM');
|
|
19
|
+
/**
|
|
20
|
+
* Typed token for {@link IDEMPOTENCY_KEY_ITEM}: the active idempotency key. Reads and writes the same
|
|
21
|
+
* `context.items` entry through `getPipelineItem` / `requirePipelineItem`.
|
|
22
|
+
*/
|
|
23
|
+
export const IDEMPOTENCY_KEY_ITEM_TOKEN = createPipelineItem('IDEMPOTENCY_KEY_ITEM', IDEMPOTENCY_KEY_ITEM);
|
|
24
|
+
/**
|
|
25
|
+
* Unique symbol key set on `context.items` to `true` when the response was replayed
|
|
26
|
+
* from a previously-stored record (the handler did not run this time).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const wasReplayed = context.items.get(IDEMPOTENCY_REPLAYED_ITEM) === true;
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const IDEMPOTENCY_REPLAYED_ITEM = Symbol('IDEMPOTENCY_REPLAYED_ITEM');
|
|
34
|
+
/**
|
|
35
|
+
* Typed token for {@link IDEMPOTENCY_REPLAYED_ITEM}: the idempotent replay flag. Reads and writes the same
|
|
36
|
+
* `context.items` entry through `getPipelineItem` / `requirePipelineItem`.
|
|
37
|
+
*/
|
|
38
|
+
export const IDEMPOTENCY_REPLAYED_ITEM_TOKEN = createPipelineItem('IDEMPOTENCY_REPLAYED_ITEM', IDEMPOTENCY_REPLAYED_ITEM);
|
|
39
|
+
/**
|
|
40
|
+
* Unique symbol key set on `context.items` to `true` when a handler completed after
|
|
41
|
+
* its claim had expired or been replaced. The successful handler result is returned,
|
|
42
|
+
* but this execution is not allowed to overwrite the newer owner's replay state.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const ownershipLost = context.items.get(IDEMPOTENCY_OWNERSHIP_LOST_ITEM) === true;
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export const IDEMPOTENCY_OWNERSHIP_LOST_ITEM = Symbol('IDEMPOTENCY_OWNERSHIP_LOST_ITEM');
|
|
50
|
+
/**
|
|
51
|
+
* Typed token for {@link IDEMPOTENCY_OWNERSHIP_LOST_ITEM}: the idempotency ownership-lost flag. Reads and writes the same
|
|
52
|
+
* `context.items` entry through `getPipelineItem` / `requirePipelineItem`.
|
|
53
|
+
*/
|
|
54
|
+
export const IDEMPOTENCY_OWNERSHIP_LOST_ITEM_TOKEN = createPipelineItem('IDEMPOTENCY_OWNERSHIP_LOST_ITEM', IDEMPOTENCY_OWNERSHIP_LOST_ITEM);
|
|
55
|
+
const DEFAULT_SCOPE = ['command'];
|
|
56
|
+
/** Whether this request kind is configured for idempotency. */
|
|
57
|
+
function inScope(options, requestKind) {
|
|
58
|
+
return (options?.scope ?? DEFAULT_SCOPE).includes(requestKind);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pipeline behavior that deduplicates concurrent requests sharing an
|
|
62
|
+
* idempotency key and replays the stored response after a successful execution.
|
|
63
|
+
*
|
|
64
|
+
* For each in-scope request it derives a key
|
|
65
|
+
* ({@link IdempotencyBehaviorOptions.keyFactory}), then atomically claims it in
|
|
66
|
+
* a pluggable {@link IdempotencyStore}:
|
|
67
|
+
*
|
|
68
|
+
* - **first claim** → run the handler and store the completed response;
|
|
69
|
+
* - **duplicate, completed** → return the stored response (no re-execution);
|
|
70
|
+
* - **duplicate, in progress** → throw {@link IdempotencyConflictError} (`409`);
|
|
71
|
+
* - **key reused with a different payload** → throw it as `key_reuse` (`422`);
|
|
72
|
+
* - **completed under a different authorization scope** → throw it as
|
|
73
|
+
* `replay_scope` (`409`), when a
|
|
74
|
+
* {@link IdempotencyBehaviorOptions.replayScopeFactory} is configured. The
|
|
75
|
+
* record is kept and the handler is not re-executed, so a permission change
|
|
76
|
+
* cannot duplicate the effect.
|
|
77
|
+
*
|
|
78
|
+
* Each claim carries a unique owner token. Completion and release compare that
|
|
79
|
+
* token atomically, so an execution that outlives its TTL cannot overwrite or
|
|
80
|
+
* delete a newer claim after the key is reclaimed.
|
|
81
|
+
*
|
|
82
|
+
* When the handler throws, `releaseOnError` controls whether the key remains
|
|
83
|
+
* claimed. It defaults to `true`, so failed executions release the key and a
|
|
84
|
+
* later retry may execute the handler again. Set it to `false` when retaining
|
|
85
|
+
* the claim after a failure is preferable to retryability.
|
|
86
|
+
*
|
|
87
|
+
* Store-agnostic by design: memory (default), Redis, Postgres, or your own are
|
|
88
|
+
* one-line swaps of the store passed to the constructor. When no key is produced,
|
|
89
|
+
* the handler runs normally.
|
|
90
|
+
*
|
|
91
|
+
* @example Per-handler idempotency
|
|
92
|
+
* Keyed off an optional `Idempotency-Key` header and partitioned by tenant and principal:
|
|
93
|
+
* ```ts
|
|
94
|
+
* class CreatePaymentHandler {
|
|
95
|
+
* @pipeline.wrap({ kind: 'command' }, [IdempotencyBehavior, {
|
|
96
|
+
* keyFactory: createPartitionedIdempotencyKeyFactory({
|
|
97
|
+
* principal: (c) => c.items.get('currentUserId') as string | undefined,
|
|
98
|
+
* operation: (c) => c.items.get('idempotencyKey') as string | undefined,
|
|
99
|
+
* onMissingOperation: 'skip',
|
|
100
|
+
* }),
|
|
101
|
+
* ttl: 86_400_000,
|
|
102
|
+
* }])
|
|
103
|
+
* async handle(command: CreatePaymentCommand) {}
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export class IdempotencyBehavior {
|
|
108
|
+
store;
|
|
109
|
+
static [PIPELINE_BEHAVIOR_CONTRACT] = {
|
|
110
|
+
order: (context) => {
|
|
111
|
+
const options = context.effectiveOptions;
|
|
112
|
+
if (!inScope(options, context.requestKind)) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
after: ['@cqrs-ddd/pipeline-casl:CaslBehavior', 'CaslBehavior'],
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
validate: (context) => {
|
|
120
|
+
const options = context.effectiveOptions;
|
|
121
|
+
if (!inScope(options, context.requestKind)) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if ((context.declarationSource === 'handler' ||
|
|
125
|
+
context.declarationSource === 'both') &&
|
|
126
|
+
!options?.keyFactory) {
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
handlerName: context.handlerName,
|
|
130
|
+
behaviorName: IdempotencyBehavior.name,
|
|
131
|
+
message: 'Explicit IdempotencyBehavior intent requires an explicit `keyFactory` for deduplication',
|
|
132
|
+
fix: 'Provide keyFactory in the idempotent({ keyFactory }) entry ' +
|
|
133
|
+
'or use createPartitionedIdempotencyKeyFactory(...).',
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
if (options?.keyFactory && typeof options.keyFactory !== 'function') {
|
|
138
|
+
return [
|
|
139
|
+
{
|
|
140
|
+
handlerName: context.handlerName,
|
|
141
|
+
behaviorName: IdempotencyBehavior.name,
|
|
142
|
+
message: `IdempotencyBehavior keyFactory must be a callable function, received ${typeof options.keyFactory}`,
|
|
143
|
+
fix: 'Pass a valid function (ctx) => string as the keyFactory option of the idempotent({ keyFactory }) entry.',
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
logger;
|
|
151
|
+
defaults;
|
|
152
|
+
constructor(store, defaults, logger) {
|
|
153
|
+
this.store = store;
|
|
154
|
+
const candidate = untyped(this.store ?? {});
|
|
155
|
+
if (typeof candidate.completeIfOwned !== 'function' ||
|
|
156
|
+
typeof candidate.deleteIfOwned !== 'function') {
|
|
157
|
+
throw new TypeError('The configured IdempotencyStore is missing required methods. ' +
|
|
158
|
+
'IdempotencyStore requires atomic completeIfOwned() and deleteIfOwned() operations.');
|
|
159
|
+
}
|
|
160
|
+
this.defaults = defaults ?? {};
|
|
161
|
+
this.logger = logger ?? console;
|
|
162
|
+
}
|
|
163
|
+
async handle(context, next) {
|
|
164
|
+
const options = this.resolveEffectiveOptions(context.getBehaviorOptions(IdempotencyBehavior));
|
|
165
|
+
if (!inScope(options, context.requestKind)) {
|
|
166
|
+
return next();
|
|
167
|
+
}
|
|
168
|
+
const key = options.keyFactory?.(context);
|
|
169
|
+
if (!key) {
|
|
170
|
+
// No idempotency key → nothing to dedupe; run normally.
|
|
171
|
+
return next();
|
|
172
|
+
}
|
|
173
|
+
setPipelineItem(context, IDEMPOTENCY_KEY_ITEM_TOKEN, key);
|
|
174
|
+
const ttl = options.ttl ?? DEFAULT_IDEMPOTENCY_TTL_MS;
|
|
175
|
+
if (!Number.isSafeInteger(ttl) || ttl <= 0) {
|
|
176
|
+
throw new TypeError('Idempotency ttl must be a positive safe integer in milliseconds.');
|
|
177
|
+
}
|
|
178
|
+
const fingerprint = (options.fingerprint ?? true)
|
|
179
|
+
? fingerprintValue(context.request)
|
|
180
|
+
: undefined;
|
|
181
|
+
// Resolved before the claim, so a replayScopeFactory that throws rejects the
|
|
182
|
+
// operation before any key is claimed.
|
|
183
|
+
const replayScope = options.replayScopeFactory?.(context);
|
|
184
|
+
const scopeRequired = options.replayScopeFactory !== undefined;
|
|
185
|
+
const claimId = randomUUID();
|
|
186
|
+
const claim = {
|
|
187
|
+
key,
|
|
188
|
+
status: 'in_progress',
|
|
189
|
+
requestName: context.requestName,
|
|
190
|
+
claimId,
|
|
191
|
+
fingerprint,
|
|
192
|
+
replayScope,
|
|
193
|
+
createdAt: new Date().toISOString(),
|
|
194
|
+
};
|
|
195
|
+
let claimed = await this.store.setIfAbsent(key, claim, ttl);
|
|
196
|
+
if (!claimed) {
|
|
197
|
+
const existing = await this.store.get(key);
|
|
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
|
+
}
|
|
211
|
+
}
|
|
212
|
+
let response;
|
|
213
|
+
try {
|
|
214
|
+
response = await next();
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (options.releaseOnError ?? true) {
|
|
218
|
+
await this.release(key, claimId);
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
let responseSnapshot;
|
|
223
|
+
try {
|
|
224
|
+
responseSnapshot = toJsonSnapshot(response);
|
|
225
|
+
}
|
|
226
|
+
catch (cause) {
|
|
227
|
+
// The handler already succeeded, so retain the claim until expiry rather
|
|
228
|
+
// than allowing an immediate retry to repeat completed side effects.
|
|
229
|
+
this.logger.error?.(`Idempotency response snapshot failed after ${context.requestName} ` +
|
|
230
|
+
`executed successfully (key: ${key}). The claim is retained until it ` +
|
|
231
|
+
'expires; retrying the business request may repeat side effects.', cause.stack, IdempotencyBehavior.name);
|
|
232
|
+
throw new IdempotencyCompletionError(key, claimId, cause, 'snapshot');
|
|
233
|
+
}
|
|
234
|
+
// The handler has already succeeded. Completion must be conditional on
|
|
235
|
+
// still owning the claim; a stale execution must never overwrite a newer
|
|
236
|
+
// execution that reclaimed the key after this claim's TTL elapsed.
|
|
237
|
+
let completed;
|
|
238
|
+
try {
|
|
239
|
+
completed = await this.store.completeIfOwned(key, claimId, {
|
|
240
|
+
...claim,
|
|
241
|
+
status: 'completed',
|
|
242
|
+
response: responseSnapshot,
|
|
243
|
+
completedAt: new Date().toISOString(),
|
|
244
|
+
}, ttl);
|
|
245
|
+
}
|
|
246
|
+
catch (cause) {
|
|
247
|
+
this.logger.error?.(`Idempotency completion persistence failed after ${context.requestName} ` +
|
|
248
|
+
`executed successfully (key: ${key}). Retrying the business request may ` +
|
|
249
|
+
'repeat side effects.', cause instanceof Error ? cause.stack : undefined, IdempotencyBehavior.name);
|
|
250
|
+
throw new IdempotencyCompletionError(key, claimId, cause, 'store');
|
|
251
|
+
}
|
|
252
|
+
if (!completed) {
|
|
253
|
+
setPipelineItem(context, IDEMPOTENCY_OWNERSHIP_LOST_ITEM_TOKEN, true);
|
|
254
|
+
this.logger.error?.(`Idempotency claim ownership was lost after ${context.requestName} ` +
|
|
255
|
+
`completed successfully (key: ${key}). The successful result is being ` +
|
|
256
|
+
'returned, but this execution did not overwrite the newer claim.', IdempotencyBehavior.name);
|
|
257
|
+
}
|
|
258
|
+
return response;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The key was already claimed. Replay a completed response, or surface a
|
|
262
|
+
* conflict for an in-progress duplicate, a key reused with a new payload, or a
|
|
263
|
+
* completed record whose replay scope does not match the caller's.
|
|
264
|
+
*/
|
|
265
|
+
async replayOrConflict(context, key, fingerprint, scope, knownExisting) {
|
|
266
|
+
const existing = knownExisting ?? (await this.store.get(key));
|
|
267
|
+
// A second failed claim followed by another disappearing record indicates
|
|
268
|
+
// repeated contention. The retry is deliberately bounded to avoid a hot
|
|
269
|
+
// loop if a backend is unstable or claims are being rapidly replaced.
|
|
270
|
+
if (!existing) {
|
|
271
|
+
throw new IdempotencyConflictError({
|
|
272
|
+
key,
|
|
273
|
+
requestName: context.requestName,
|
|
274
|
+
reason: 'in_progress',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// A fingerprinted request never replays a record that cannot prove payload identity.
|
|
278
|
+
if (existing.requestName !== context.requestName ||
|
|
279
|
+
(fingerprint && existing.fingerprint !== fingerprint)) {
|
|
280
|
+
throw new IdempotencyConflictError({
|
|
281
|
+
key,
|
|
282
|
+
requestName: context.requestName,
|
|
283
|
+
reason: 'key_reuse',
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (existing.status === 'in_progress') {
|
|
287
|
+
throw new IdempotencyConflictError({
|
|
288
|
+
key,
|
|
289
|
+
requestName: context.requestName,
|
|
290
|
+
reason: 'in_progress',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// A completed record may only be replayed to a caller whose authorization
|
|
294
|
+
// scope matches the one it was produced under. A record stored without a
|
|
295
|
+
// scope cannot prove that, so it fails closed rather than replaying. The
|
|
296
|
+
// record is retained: re-executing a completed operation would duplicate it.
|
|
297
|
+
if (scope.scopeRequired &&
|
|
298
|
+
(existing.replayScope === undefined ||
|
|
299
|
+
existing.replayScope !== scope.replayScope)) {
|
|
300
|
+
this.logger.warn?.(`Refusing to replay ${context.requestName} (key: ${key}): the stored ` +
|
|
301
|
+
'response was authorized under a different scope than this caller.', IdempotencyBehavior.name);
|
|
302
|
+
throw new IdempotencyConflictError({
|
|
303
|
+
key,
|
|
304
|
+
requestName: context.requestName,
|
|
305
|
+
reason: 'replay_scope',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
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
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Release a failed execution's claim only if it still owns the key; never let
|
|
314
|
+
* cleanup break propagation of the original handler error.
|
|
315
|
+
*/
|
|
316
|
+
async release(key, claimId) {
|
|
317
|
+
try {
|
|
318
|
+
await this.store.deleteIfOwned(key, claimId);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
this.logger.warn?.(`Failed to release idempotency key "${key}": ` +
|
|
322
|
+
`${error instanceof Error ? error.message : error}`, IdempotencyBehavior.name);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/** Shallow-merges pipeline-level options over the constructor defaults. */
|
|
326
|
+
resolveEffectiveOptions(options) {
|
|
327
|
+
if (!options)
|
|
328
|
+
return this.defaults;
|
|
329
|
+
return { ...this.defaults, ...options };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=idempotency.behavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.behavior.js","sourceRoot":"","sources":["../src/idempotency.behavior.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent commands: the behavior, its stores, key factories and `idempotent()`. The HTTP mapping of its errors is the `@cqrs-ddd/pipeline-idempotency/http` entry point.
|
|
3
|
+
*
|
|
4
|
+
* @module main
|
|
5
|
+
*/
|
|
6
|
+
export { DEFAULT_IDEMPOTENCY_TTL_MS } from './constants/tokens.js';
|
|
7
|
+
export { IdempotencyCompletionError, type IdempotencyFinalizationPhase, } from './errors/idempotency-completion.error.js';
|
|
8
|
+
export { IdempotencyConflictError, type IdempotencyConflictReason, } from './errors/idempotency-conflict.error.js';
|
|
9
|
+
export { type IdempotencyPartitionDimension, MissingIdempotencyPartitionError, } from './errors/missing-partition.error.js';
|
|
10
|
+
export { buildIdempotencyAttributes } from './helpers/build-attributes.js';
|
|
11
|
+
export { fingerprintValue } from './helpers/fingerprint.js';
|
|
12
|
+
export { type IdempotencyIntentOptions, idempotent, } from './helpers/idempotency.intent.js';
|
|
13
|
+
export { createPartitionedIdempotencyKeyFactory, type IdempotencyOperationFactory, type IdempotencyPrincipalFactory, type PartitionedIdempotencyKeyOptions, } from './helpers/partitioned-key.js';
|
|
14
|
+
export { IDEMPOTENCY_KEY_ITEM, IDEMPOTENCY_KEY_ITEM_TOKEN, IDEMPOTENCY_OWNERSHIP_LOST_ITEM, IDEMPOTENCY_OWNERSHIP_LOST_ITEM_TOKEN, IDEMPOTENCY_REPLAYED_ITEM, IDEMPOTENCY_REPLAYED_ITEM_TOKEN, IdempotencyBehavior, } from './idempotency.behavior.js';
|
|
15
|
+
export type { IdempotencyBehaviorOptions, IdempotencyKeyFactory, IdempotencyReplayScopeFactory, } from './interfaces/idempotency-options.interface.js';
|
|
16
|
+
export type { IdempotencyRecord, IdempotencyRequestKind, IdempotencyStatus, JsonValue, } from './interfaces/idempotency-record.interface.js';
|
|
17
|
+
export type { IdempotencyStore, MaybePromise, } from './interfaces/idempotency-store.interface.js';
|
|
18
|
+
export { MemoryIdempotencyStore, type MemoryIdempotencyStoreOptions, } from './stores/memory.store.js';
|
|
19
|
+
export { createIdempotencyTableSql, PostgresIdempotencyStore, type PostgresIdempotencyStoreOptions, type PostgresQueryableLike, type PostgresQueryResultLike, type PostgresRowLike, } from './stores/postgres.store.js';
|
|
20
|
+
export { type RedisClientLike, RedisIdempotencyStore, type RedisIdempotencyStoreOptions, } from './stores/redis.store.js';
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EACL,0BAA0B,EAC1B,KAAK,4BAA4B,GAClC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,wBAAwB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,KAAK,6BAA6B,EAClC,gCAAgC,GACjC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EACL,KAAK,wBAAwB,EAC7B,UAAU,GACX,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sCAAsC,EACtC,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,GACtC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,+BAA+B,EAC/B,qCAAqC,EACrC,yBAAyB,EACzB,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,0BAA0B,EAC1B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,+CAA+C,CAAC;AACvD,YAAY,EACV,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,SAAS,GACV,MAAM,8CAA8C,CAAC;AACtD,YAAY,EACV,gBAAgB,EAChB,YAAY,GACb,MAAM,6CAA6C,CAAC;AACrD,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
/**
|
|
3
|
+
* Idempotent commands: the behavior, its stores, key factories and `idempotent()`. The HTTP mapping of its errors is the `@cqrs-ddd/pipeline-idempotency/http` entry point.
|
|
4
|
+
*
|
|
5
|
+
* @module main
|
|
6
|
+
*/
|
|
7
|
+
export { DEFAULT_IDEMPOTENCY_TTL_MS } from './constants/tokens.js';
|
|
8
|
+
export { IdempotencyCompletionError, } from './errors/idempotency-completion.error.js';
|
|
9
|
+
export { IdempotencyConflictError, } from './errors/idempotency-conflict.error.js';
|
|
10
|
+
export { MissingIdempotencyPartitionError, } from './errors/missing-partition.error.js';
|
|
11
|
+
export { buildIdempotencyAttributes } from './helpers/build-attributes.js';
|
|
12
|
+
export { fingerprintValue } from './helpers/fingerprint.js';
|
|
13
|
+
export { idempotent, } from './helpers/idempotency.intent.js';
|
|
14
|
+
export { createPartitionedIdempotencyKeyFactory, } from './helpers/partitioned-key.js';
|
|
15
|
+
export { IDEMPOTENCY_KEY_ITEM, IDEMPOTENCY_KEY_ITEM_TOKEN, IDEMPOTENCY_OWNERSHIP_LOST_ITEM, IDEMPOTENCY_OWNERSHIP_LOST_ITEM_TOKEN, IDEMPOTENCY_REPLAYED_ITEM, IDEMPOTENCY_REPLAYED_ITEM_TOKEN, IdempotencyBehavior, } from './idempotency.behavior.js';
|
|
16
|
+
export { MemoryIdempotencyStore, } from './stores/memory.store.js';
|
|
17
|
+
export { createIdempotencyTableSql, PostgresIdempotencyStore, } from './stores/postgres.store.js';
|
|
18
|
+
export { RedisIdempotencyStore, } from './stores/redis.store.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE;;;;GAIG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EACL,0BAA0B,GAE3B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,wBAAwB,GAEzB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAEL,gCAAgC,GACjC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAEL,UAAU,GACX,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sCAAsC,GAIvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,+BAA+B,EAC/B,qCAAqC,EACrC,yBAAyB,EACzB,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AAgBnC,OAAO,EACL,sBAAsB,GAEvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GAKzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,qBAAqB,GAEtB,MAAM,yBAAyB,CAAC"}
|