@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,97 @@
|
|
|
1
|
+
import type { IPipelineContext } from '@cqrs-ddd/pipeline';
|
|
2
|
+
import type { IdempotencyRequestKind } from './idempotency-record.interface.js';
|
|
3
|
+
/**
|
|
4
|
+
* Derives the digest of the authorization scope a request runs under.
|
|
5
|
+
*
|
|
6
|
+
* Returned value is compared, not interpreted: any deterministic string works,
|
|
7
|
+
* and it should cover every dimension whose change must stop a stored response
|
|
8
|
+
* from being replayed — the effective permission rules and the trusted context
|
|
9
|
+
* their conditions resolve against.
|
|
10
|
+
*
|
|
11
|
+
* Throw when the required context is missing rather than returning a placeholder;
|
|
12
|
+
* the behavior calls this **before** claiming the key, so a throw prevents the
|
|
13
|
+
* operation from being claimed at all. Returning `undefined` means "this request
|
|
14
|
+
* has no scope", which cannot be replayed against a record that has one.
|
|
15
|
+
*/
|
|
16
|
+
export type IdempotencyReplayScopeFactory = (context: IPipelineContext) => string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Derives the idempotency key for a request from the pipeline context. A common
|
|
19
|
+
* HTTP pattern is to copy the `Idempotency-Key` header into the CQRS command at
|
|
20
|
+
* the controller boundary and read it from `context.request`. An upstream
|
|
21
|
+
* behavior may alternatively place application metadata in `context.items`.
|
|
22
|
+
* Return `undefined` to skip deduplication for this request.
|
|
23
|
+
*/
|
|
24
|
+
export type IdempotencyKeyFactory = (context: IPipelineContext) => string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Per-handler idempotency options, shallow-merged over the constructor defaults.
|
|
27
|
+
*
|
|
28
|
+
* @example Tenant/principal-scoped command idempotency
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Fails closed when the tenant or principal is missing and escapes every
|
|
31
|
+
* // segment; never build a key with a template string.
|
|
32
|
+
* const createOrderKey = createPartitionedIdempotencyKeyFactory({
|
|
33
|
+
* action: 'order.create',
|
|
34
|
+
* principal: (ctx) => ['user', ctx.items.get(CURRENT_USER_ID) as string],
|
|
35
|
+
* operation: (ctx) => (ctx.request as CreateOrderCommand).idempotencyKey,
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* class CreateOrderHandler {
|
|
39
|
+
* @pipeline.wrap({ kind: 'command' }, [IdempotencyBehavior, {
|
|
40
|
+
* keyFactory: createOrderKey,
|
|
41
|
+
* ttl: 24 * 60 * 60 * 1000,
|
|
42
|
+
* }])
|
|
43
|
+
* async handle(command: CreateOrderCommand) {}
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export interface IdempotencyBehaviorOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Derives the idempotency key from the request/context. **Required** for the
|
|
50
|
+
* behavior to do anything — without a key (or when it returns `undefined`)
|
|
51
|
+
* the handler runs normally. Include tenant/principal ownership whenever a
|
|
52
|
+
* replay would otherwise bypass handler-level authorization.
|
|
53
|
+
*/
|
|
54
|
+
keyFactory?: IdempotencyKeyFactory;
|
|
55
|
+
/**
|
|
56
|
+
* How long a key is remembered, in milliseconds. After this window the key
|
|
57
|
+
* may be reused and a fresh execution occurs. Successful completion restarts
|
|
58
|
+
* the TTL for the replay record. Must be a positive safe integer. Default
|
|
59
|
+
* `86_400_000` (24h).
|
|
60
|
+
*/
|
|
61
|
+
ttl?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Which request kinds this policy applies to. Default `['command']` — queries
|
|
64
|
+
* are naturally idempotent and usually want `@cqrs-ddd/pipeline-cache`
|
|
65
|
+
* instead.
|
|
66
|
+
*/
|
|
67
|
+
scope?: IdempotencyRequestKind[];
|
|
68
|
+
/**
|
|
69
|
+
* Hash the request payload and reject a later call that reuses the same key
|
|
70
|
+
* with a *different* body (`422`). Default `true`. Disable if your key already
|
|
71
|
+
* fully identifies the payload.
|
|
72
|
+
*/
|
|
73
|
+
fingerprint?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Binds replay to the caller's authorization scope while keeping the operation
|
|
76
|
+
* key stable.
|
|
77
|
+
*
|
|
78
|
+
* The digest is captured when the key is claimed and stored on the record. A
|
|
79
|
+
* later duplicate may only replay the stored response when its digest matches;
|
|
80
|
+
* a mismatch, or a record stored without one, raises
|
|
81
|
+
* `IdempotencyConflictError` with reason `replay_scope` (`409`). The record is
|
|
82
|
+
* neither deleted nor re-executed, so a permission change can never cause the
|
|
83
|
+
* side effect to run twice.
|
|
84
|
+
*
|
|
85
|
+
* Configure it for any operation whose response or effect depends on the
|
|
86
|
+
* caller's permissions. Without it, replay is bound only by the key and the
|
|
87
|
+
* payload fingerprint.
|
|
88
|
+
*/
|
|
89
|
+
replayScopeFactory?: IdempotencyReplayScopeFactory;
|
|
90
|
+
/**
|
|
91
|
+
* When the handler throws, release the key so the client can safely retry
|
|
92
|
+
* (`true`, default). Set `false` to keep the key claimed and surface a
|
|
93
|
+
* conflict on retry (favors strict at-most-once over retryability).
|
|
94
|
+
*/
|
|
95
|
+
releaseOnError?: boolean;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=idempotency-options.interface.d.ts.map
|
|
@@ -0,0 +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;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency-options.interface.js","sourceRoot":"","sources":["../../src/interfaces/idempotency-options.interface.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Pipeline request kinds an idempotency policy can apply to. */
|
|
2
|
+
export type IdempotencyRequestKind = 'command' | 'query' | 'event' | 'unknown';
|
|
3
|
+
/** Lifecycle state of an idempotency key. */
|
|
4
|
+
export type IdempotencyStatus = 'in_progress' | 'completed';
|
|
5
|
+
/** Values that have one portable representation across all bundled stores. */
|
|
6
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
7
|
+
[key: string]: JsonValue;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The durable record stored under an idempotency key. While a handler runs the
|
|
11
|
+
* record is `in_progress`; once it succeeds the record flips to `completed` and
|
|
12
|
+
* carries the captured {@link response} for replay.
|
|
13
|
+
*/
|
|
14
|
+
export interface IdempotencyRecord {
|
|
15
|
+
/** The idempotency key this record is stored under. */
|
|
16
|
+
key: string;
|
|
17
|
+
/** Whether the original request is still running or has completed. */
|
|
18
|
+
status: IdempotencyStatus;
|
|
19
|
+
/** The request type name, e.g. `CreateUserCommand`. */
|
|
20
|
+
requestName: string;
|
|
21
|
+
/**
|
|
22
|
+
* Unique owner token for the execution that claimed this record. Every claim
|
|
23
|
+
* `IdempotencyBehavior` makes sets it, and `completeIfOwned` and
|
|
24
|
+
* `deleteIfOwned` compare against it. It may be absent from a record written
|
|
25
|
+
* through the unconditional `set` or claimed outside the behavior; such a
|
|
26
|
+
* record can still be read and replayed, but no owner check matches it.
|
|
27
|
+
*/
|
|
28
|
+
claimId?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Stable hash of the original request payload. Used to detect a key being
|
|
31
|
+
* reused with a *different* body (a client bug or replay attack).
|
|
32
|
+
*/
|
|
33
|
+
fingerprint?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Digest of the authorization scope the original execution ran under, when the
|
|
36
|
+
* behavior is configured with a `replayScopeFactory`.
|
|
37
|
+
*
|
|
38
|
+
* Replay compares this against the current caller's scope and refuses on a
|
|
39
|
+
* mismatch, so a response authorized under one set of permissions is never
|
|
40
|
+
* returned to a caller who no longer holds them. It is deliberately **not**
|
|
41
|
+
* part of the key: a key that changed with permissions would let the same
|
|
42
|
+
* side effect execute again.
|
|
43
|
+
*/
|
|
44
|
+
replayScope?: string;
|
|
45
|
+
/** JSON snapshot of the handler response, captured once completed. */
|
|
46
|
+
response?: JsonValue;
|
|
47
|
+
/** ISO-8601 timestamp the key was first claimed. */
|
|
48
|
+
createdAt: string;
|
|
49
|
+
/** ISO-8601 timestamp the original request completed, when applicable. */
|
|
50
|
+
completedAt?: string;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=idempotency-record.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency-record.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/idempotency-record.interface.ts"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/E,6CAA6C;AAC7C,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,WAAW,CAAC;AAE5D,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GACjB,IAAI,GACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,MAAM,EAAE,iBAAiB,CAAC;IAC1B,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency-record.interface.js","sourceRoot":"","sources":["../../src/interfaces/idempotency-record.interface.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { IdempotencyRecord } from './idempotency-record.interface.js';
|
|
2
|
+
/** A value that may be returned synchronously or as a promise. */
|
|
3
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Backend-agnostic store for idempotency records — the single seam every
|
|
6
|
+
* storage backend implements. Because {@link IdempotencyBehavior} depends only
|
|
7
|
+
* on this interface, swapping the backend (memory, Redis, Postgres, your own)
|
|
8
|
+
* is a one-line change of the store passed to `IdempotencyBehavior`; handlers never
|
|
9
|
+
* change.
|
|
10
|
+
*
|
|
11
|
+
* Concurrent duplicate exclusion hinges on {@link setIfAbsent} being **atomic**
|
|
12
|
+
* — it must claim the key only if no live record exists (e.g. Redis `SET NX PX`,
|
|
13
|
+
* or a Postgres conditional upsert that replaces only expired conflicts).
|
|
14
|
+
* Atomic claiming prevents two live duplicates from both acquiring the same
|
|
15
|
+
* key; it does not by itself imply exactly-once execution across failures/retries.
|
|
16
|
+
*
|
|
17
|
+
* Ownership-sensitive transitions use `claimId`. A stale execution whose claim
|
|
18
|
+
* expired must never complete or delete a newer execution's record. Built-in
|
|
19
|
+
* stores implement this with an atomic compare-and-set / compare-and-delete.
|
|
20
|
+
*/
|
|
21
|
+
export interface IdempotencyStore {
|
|
22
|
+
/**
|
|
23
|
+
* Returns the live (non-expired) record for `key`, or `undefined` if there is
|
|
24
|
+
* none.
|
|
25
|
+
*/
|
|
26
|
+
get(key: string): MaybePromise<IdempotencyRecord | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* Atomically stores `record` **only if** no live record exists for `key`.
|
|
29
|
+
*
|
|
30
|
+
* @returns `true` if the key was claimed by this call, `false` if a live
|
|
31
|
+
* record already existed.
|
|
32
|
+
*/
|
|
33
|
+
setIfAbsent(key: string, record: IdempotencyRecord, ttlMs: number): MaybePromise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Atomically replaces the live `in_progress` record with `record` only when
|
|
36
|
+
* the existing record is still owned by `claimId`.
|
|
37
|
+
*
|
|
38
|
+
* @returns `true` when this claim completed its own record; `false` when the
|
|
39
|
+
* claim expired, was replaced, or was otherwise no longer owned by the caller.
|
|
40
|
+
*/
|
|
41
|
+
completeIfOwned(key: string, claimId: string, record: IdempotencyRecord, ttlMs: number): MaybePromise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Atomically removes the live record only when it is still owned by
|
|
44
|
+
* `claimId`. Used by `releaseOnError` so a stale failure cannot delete a
|
|
45
|
+
* newer execution's claim.
|
|
46
|
+
*/
|
|
47
|
+
deleteIfOwned(key: string, claimId: string): MaybePromise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Unconditional overwrite for administrative or custom use. Pipeline
|
|
50
|
+
* execution completion uses {@link completeIfOwned}, not this method.
|
|
51
|
+
*/
|
|
52
|
+
set(key: string, record: IdempotencyRecord, ttlMs: number): MaybePromise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Unconditional delete for administrative or custom use. Pipeline
|
|
55
|
+
* failure release uses {@link deleteIfOwned}, not this method.
|
|
56
|
+
*/
|
|
57
|
+
delete(key: string): MaybePromise<void>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=idempotency-store.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency-store.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/idempotency-store.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,kEAAkE;AAClE,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IAE9D;;;;;OAKG;IACH,WAAW,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,YAAY,CAAC,OAAO,CAAC,CAAC;IAEzB;;;;;;OAMG;IACH,eAAe,CACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,YAAY,CAAC,OAAO,CAAC,CAAC;IAEzB;;;;OAIG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAEnE;;;OAGG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,YAAY,CAAC,IAAI,CAAC,CAAC;IAEtB;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;CACzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency-store.interface.js","sourceRoot":"","sources":["../../src/interfaces/idempotency-store.interface.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { IdempotencyRecord } from '../interfaces/idempotency-record.interface.js';
|
|
2
|
+
import type { IdempotencyStore } from '../interfaces/idempotency-store.interface.js';
|
|
3
|
+
export interface MemoryIdempotencyStoreOptions {
|
|
4
|
+
/** Maximum number of entries allowed in the store. Defaults to 10,000. */
|
|
5
|
+
maxEntries?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Interval in milliseconds for periodic background cleanup of expired entries.
|
|
8
|
+
* Defaults to 30,000 ms. If 0 or negative, periodic cleanup timer is disabled.
|
|
9
|
+
*/
|
|
10
|
+
cleanupIntervalMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Zero-dependency, in-process {@link IdempotencyStore} backed by a `Map` with
|
|
14
|
+
* per-entry expiry and bounded memory capacity. The **default** store — ideal
|
|
15
|
+
* for a single instance, tests, or local development.
|
|
16
|
+
*
|
|
17
|
+
* Because Node runs JavaScript on a single thread, {@link setIfAbsent},
|
|
18
|
+
* {@link completeIfOwned}, and {@link deleteIfOwned} are effectively atomic
|
|
19
|
+
* here. State is **not** shared across processes, so for a multi-instance
|
|
20
|
+
* deployment use {@link RedisIdempotencyStore} or
|
|
21
|
+
* {@link PostgresIdempotencyStore}.
|
|
22
|
+
*/
|
|
23
|
+
export declare class MemoryIdempotencyStore implements IdempotencyStore {
|
|
24
|
+
private readonly entries;
|
|
25
|
+
private readonly maxEntries;
|
|
26
|
+
private readonly cleanupTimer?;
|
|
27
|
+
constructor(options?: MemoryIdempotencyStoreOptions);
|
|
28
|
+
/** Current number of active (stored) entries. */
|
|
29
|
+
get size(): number;
|
|
30
|
+
/**
|
|
31
|
+
* Iterates through all entries and removes any whose TTL has elapsed.
|
|
32
|
+
* Returns the count of deleted entries.
|
|
33
|
+
*/
|
|
34
|
+
cleanupExpired(): number;
|
|
35
|
+
/** Stops the periodic cleanup timer and releases timer resources. */
|
|
36
|
+
destroy(): void;
|
|
37
|
+
private ensureCapacity;
|
|
38
|
+
get(key: string): IdempotencyRecord | undefined;
|
|
39
|
+
setIfAbsent(key: string, record: IdempotencyRecord, ttlMs: number): boolean;
|
|
40
|
+
completeIfOwned(key: string, claimId: string, record: IdempotencyRecord, ttlMs: number): boolean;
|
|
41
|
+
deleteIfOwned(key: string, claimId: string): boolean;
|
|
42
|
+
set(key: string, record: IdempotencyRecord, ttlMs: number): void;
|
|
43
|
+
delete(key: string): void;
|
|
44
|
+
private write;
|
|
45
|
+
/** Returns the entry for `key` if present and not expired, pruning if it is. */
|
|
46
|
+
private live;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=memory.store.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;GAUG;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,CAO1E;IAED,eAAe,CACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,OAAO,CAWT;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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { cloneIdempotencyRecord } from '../helpers/json-snapshot.js';
|
|
3
|
+
/**
|
|
4
|
+
* Zero-dependency, in-process {@link IdempotencyStore} backed by a `Map` with
|
|
5
|
+
* per-entry expiry and bounded memory capacity. The **default** store — ideal
|
|
6
|
+
* for a single instance, tests, or local development.
|
|
7
|
+
*
|
|
8
|
+
* Because Node runs JavaScript on a single thread, {@link setIfAbsent},
|
|
9
|
+
* {@link completeIfOwned}, and {@link deleteIfOwned} are effectively atomic
|
|
10
|
+
* here. State is **not** shared across processes, so for a multi-instance
|
|
11
|
+
* deployment use {@link RedisIdempotencyStore} or
|
|
12
|
+
* {@link PostgresIdempotencyStore}.
|
|
13
|
+
*/
|
|
14
|
+
export class MemoryIdempotencyStore {
|
|
15
|
+
entries = new Map();
|
|
16
|
+
maxEntries;
|
|
17
|
+
cleanupTimer;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.maxEntries = options?.maxEntries ?? 10_000;
|
|
20
|
+
const interval = options?.cleanupIntervalMs ?? 30_000;
|
|
21
|
+
if (interval > 0) {
|
|
22
|
+
this.cleanupTimer = setInterval(() => {
|
|
23
|
+
this.cleanupExpired();
|
|
24
|
+
}, interval);
|
|
25
|
+
if (typeof this.cleanupTimer?.unref === 'function') {
|
|
26
|
+
this.cleanupTimer.unref();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Current number of active (stored) entries. */
|
|
31
|
+
get size() {
|
|
32
|
+
return this.entries.size;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Iterates through all entries and removes any whose TTL has elapsed.
|
|
36
|
+
* Returns the count of deleted entries.
|
|
37
|
+
*/
|
|
38
|
+
cleanupExpired() {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
let count = 0;
|
|
41
|
+
for (const [key, entry] of this.entries) {
|
|
42
|
+
if (entry.expiresAt <= now) {
|
|
43
|
+
this.entries.delete(key);
|
|
44
|
+
count++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
/** Stops the periodic cleanup timer and releases timer resources. */
|
|
50
|
+
destroy() {
|
|
51
|
+
if (this.cleanupTimer) {
|
|
52
|
+
clearInterval(this.cleanupTimer);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
ensureCapacity() {
|
|
56
|
+
if (this.entries.size < this.maxEntries) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.cleanupExpired();
|
|
60
|
+
if (this.entries.size >= this.maxEntries) {
|
|
61
|
+
throw new Error(`MemoryIdempotencyStore capacity (${this.maxEntries}) reached: cannot evict active or unexpired claims.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
get(key) {
|
|
65
|
+
const record = this.live(key)?.record;
|
|
66
|
+
return record ? cloneIdempotencyRecord(record) : undefined;
|
|
67
|
+
}
|
|
68
|
+
setIfAbsent(key, record, ttlMs) {
|
|
69
|
+
if (this.live(key)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
this.ensureCapacity();
|
|
73
|
+
this.write(key, record, ttlMs);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
completeIfOwned(key, claimId, record, ttlMs) {
|
|
77
|
+
const current = this.live(key);
|
|
78
|
+
if (current?.record.status !== 'in_progress' ||
|
|
79
|
+
current.record.claimId !== claimId) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
this.write(key, record, ttlMs);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
deleteIfOwned(key, claimId) {
|
|
86
|
+
const current = this.live(key);
|
|
87
|
+
if (!current || current.record.claimId !== claimId) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return this.entries.delete(key);
|
|
91
|
+
}
|
|
92
|
+
set(key, record, ttlMs) {
|
|
93
|
+
if (!this.entries.has(key)) {
|
|
94
|
+
this.ensureCapacity();
|
|
95
|
+
}
|
|
96
|
+
this.write(key, record, ttlMs);
|
|
97
|
+
}
|
|
98
|
+
delete(key) {
|
|
99
|
+
this.entries.delete(key);
|
|
100
|
+
}
|
|
101
|
+
write(key, record, ttlMs) {
|
|
102
|
+
this.entries.set(key, {
|
|
103
|
+
record: cloneIdempotencyRecord(record),
|
|
104
|
+
expiresAt: Date.now() + ttlMs,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Returns the entry for `key` if present and not expired, pruning if it is. */
|
|
108
|
+
live(key) {
|
|
109
|
+
const entry = this.entries.get(key);
|
|
110
|
+
if (!entry)
|
|
111
|
+
return undefined;
|
|
112
|
+
if (entry.expiresAt <= Date.now()) {
|
|
113
|
+
this.entries.delete(key);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=memory.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.store.js","sourceRoot":"","sources":["../../src/stores/memory.store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { IdempotencyRecord } from '../interfaces/idempotency-record.interface.js';
|
|
2
|
+
import type { IdempotencyStore } from '../interfaces/idempotency-store.interface.js';
|
|
3
|
+
/** A single returned row, keyed by column name. */
|
|
4
|
+
export interface PostgresRowLike {
|
|
5
|
+
[column: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** Minimal structural shape of a `pg` query result. */
|
|
8
|
+
export interface PostgresQueryResultLike {
|
|
9
|
+
rows: PostgresRowLike[];
|
|
10
|
+
rowCount?: number | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Minimal structural shape of a `pg` `Pool` / `Client`. Declared locally so this
|
|
14
|
+
* package does not hard-depend on `pg` — a real `Pool` or `Client` satisfies it.
|
|
15
|
+
* Add it in your app: `pnpm add pg`.
|
|
16
|
+
*/
|
|
17
|
+
export interface PostgresQueryableLike {
|
|
18
|
+
query(text: string, values?: unknown[]): Promise<PostgresQueryResultLike>;
|
|
19
|
+
}
|
|
20
|
+
/** Options for {@link PostgresIdempotencyStore}. */
|
|
21
|
+
export interface PostgresIdempotencyStoreOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Destination table, optionally schema-qualified (e.g. `app.idempotency_keys`).
|
|
24
|
+
* Default `'idempotency_keys'`. Validated as a safe SQL identifier.
|
|
25
|
+
*/
|
|
26
|
+
table?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* SQL that creates the idempotency table and its `expires_at` index. Run it
|
|
30
|
+
* from an application migration, not at request time.
|
|
31
|
+
*
|
|
32
|
+
* `response` holds the response as JSON text rather than `jsonb`: `jsonb`
|
|
33
|
+
* rejects the `\u0000` and unpaired-surrogate escapes `JSON.stringify` writes,
|
|
34
|
+
* and a completed response must always be storable and replay exactly.
|
|
35
|
+
*
|
|
36
|
+
* @param table - Table name (validated). Default `'idempotency_keys'`.
|
|
37
|
+
* @returns SQL statements required by {@link PostgresIdempotencyStore}.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createIdempotencyTableSql(table?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* {@link IdempotencyStore} backed by **Postgres** (`pg`) — a drop-in
|
|
42
|
+
* replacement that shares state across instances without a separate Redis.
|
|
43
|
+
*
|
|
44
|
+
* Create the table once with {@link createIdempotencyTableSql}. Atomicity of
|
|
45
|
+
* {@link setIfAbsent} comes from a conditional `INSERT … ON CONFLICT … DO
|
|
46
|
+
* UPDATE`: an absent key is inserted, an expired key is atomically replaced,
|
|
47
|
+
* and a live conflict is left untouched. Completion/release also compare
|
|
48
|
+
* `claim_id` in the same SQL statement, so a stale execution cannot mutate a
|
|
49
|
+
* newer claim. The table name is validated as a plain SQL identifier (it is
|
|
50
|
+
* interpolated, not parameterized); all values are passed as bound parameters.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { Pool } from 'pg';
|
|
55
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
56
|
+
* await pool.query(createIdempotencyTableSql());
|
|
57
|
+
* const store = new PostgresIdempotencyStore(pool);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare class PostgresIdempotencyStore implements IdempotencyStore {
|
|
61
|
+
private readonly db;
|
|
62
|
+
private readonly table;
|
|
63
|
+
constructor(db: PostgresQueryableLike, options?: PostgresIdempotencyStoreOptions);
|
|
64
|
+
get(key: string): Promise<IdempotencyRecord | undefined>;
|
|
65
|
+
setIfAbsent(key: string, record: IdempotencyRecord, ttlMs: number): Promise<boolean>;
|
|
66
|
+
completeIfOwned(key: string, claimId: string, record: IdempotencyRecord, ttlMs: number): Promise<boolean>;
|
|
67
|
+
deleteIfOwned(key: string, claimId: string): Promise<boolean>;
|
|
68
|
+
set(key: string, record: IdempotencyRecord, ttlMs: number): Promise<void>;
|
|
69
|
+
delete(key: string): Promise<void>;
|
|
70
|
+
private toValues;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=postgres.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.store.d.ts","sourceRoot":"","sources":["../../src/stores/postgres.store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAErF,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC3E;AAED,oDAAoD;AACpD,MAAM,WAAW,+BAA+B;IAC9C;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,SAAqB,GAAG,MAAM,CAe5E;AA6CD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAI7D,OAAO,CAAC,QAAQ,CAAC,EAAE;IAHrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,YACmB,EAAE,EAAE,qBAAqB,EAC1C,OAAO,GAAE,+BAAoC,EAG9C;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAW7D;IAEK,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAoBlB;IAEK,eAAe,CACnB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAoBlB;IAEK,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUlE;IAEK,GAAG,CACP,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiBf;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvC;IAED,OAAO,CAAC,QAAQ;CAajB"}
|