forked from theblockcade/stellarcade
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathidempotency.ts
More file actions
199 lines (175 loc) · 6.44 KB
/
Copy pathidempotency.ts
File metadata and controls
199 lines (175 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/**
* Idempotency handling types for transaction request correlation.
*
* Provides types for generating idempotency keys, tracking request state,
* and managing duplicate submission detection across the transaction lifecycle.
*/
import type { AppError } from './errors';
// ── Idempotency Key Types ──────────────────────────────────────────────────
/**
* Unique identifier for correlating transaction requests.
* Format: `{operation}_{timestamp}_{randomId}`
*
* @example "coinFlip_1708531200000_a3f5c1d2"
*/
export type IdempotencyKey = string;
/**
* Parameters for generating an idempotency key.
*/
export interface IdempotencyKeyParams {
/** Operation identifier (e.g., 'coinFlip', 'prizePool_reserve'). */
operation: string;
/** Optional user-provided context to include in the key. */
userContext?: string;
/** Optional timestamp override (defaults to Date.now()). */
timestamp?: number;
}
// ── Request State Types ────────────────────────────────────────────────────
/**
* Lifecycle states for an idempotent transaction request.
*/
export enum IdempotencyRequestState {
/** Request is queued but not yet submitted to the network. */
PENDING = 'PENDING',
/** Request has been submitted to wallet/RPC and is awaiting confirmation. */
IN_FLIGHT = 'IN_FLIGHT',
/** Request completed successfully with a confirmed transaction hash. */
COMPLETED = 'COMPLETED',
/** Request failed with a terminal error (will not be retried). */
FAILED = 'FAILED',
/** Request outcome is unknown (wallet closed, network timeout, etc.). */
UNKNOWN = 'UNKNOWN',
}
/**
* Stored metadata for an idempotent transaction request.
*/
export interface IdempotencyRequest {
/** Unique idempotency key for this request. */
key: IdempotencyKey;
/** Current lifecycle state. */
state: IdempotencyRequestState;
/** Operation identifier (e.g., 'coinFlip'). */
operation: string;
/** Timestamp when the request was created (ms since epoch). */
createdAt: number;
/** Timestamp of the last state transition (ms since epoch). */
updatedAt: number;
/** Transaction hash — present only when state is COMPLETED. */
txHash?: string;
/** Ledger sequence number — present only when state is COMPLETED. */
ledger?: number;
/** Error details — present only when state is FAILED. */
error?: AppError;
/** Number of retry attempts made for this request. */
retryCount: number;
/** Maximum retry attempts allowed for retryable errors. */
maxRetries: number;
/** Caller-provided context for debugging and correlation. */
context?: Record<string, unknown>;
}
// ── Duplicate Detection Types ──────────────────────────────────────────────
/**
* Result of checking for duplicate submission.
*/
export interface DuplicateCheckResult {
/** True if this key has been seen before and is still active. */
isDuplicate: boolean;
/** The existing request, if found. */
existingRequest?: IdempotencyRequest;
/** Reason for duplicate detection (for telemetry/logging). */
reason?: string;
}
// ── Recovery Types ─────────────────────────────────────────────────────────
/**
* Options for recovering from unknown-outcome transactions.
*/
export interface RecoveryOptions {
/** Idempotency key of the request to recover. */
key: IdempotencyKey;
/** Maximum time to wait for recovery polling (ms). */
timeoutMs?: number;
/** Polling interval for checking transaction status (ms). */
pollIntervalMs?: number;
}
/**
* Result of a recovery attempt.
*/
export interface RecoveryResult {
/** True if recovery succeeded (transaction found on ledger). */
recovered: boolean;
/** Updated request state after recovery attempt. */
request: IdempotencyRequest;
/** Transaction hash if recovered successfully. */
txHash?: string;
/** Ledger sequence if recovered successfully. */
ledger?: number;
}
// ── Storage Types ──────────────────────────────────────────────────────────
/**
* Persistence strategy for idempotency request tracking.
*/
export enum StorageStrategy {
/** In-memory only (cleared on page reload). */
MEMORY = 'MEMORY',
/** Session storage (cleared when tab/window closes). */
SESSION = 'SESSION',
/** Local storage (persists across sessions). */
LOCAL = 'LOCAL',
}
/**
* Configuration for idempotency request storage.
*/
export interface StorageConfig {
/** Storage strategy to use. */
strategy: StorageStrategy;
/** Key prefix for storage entries (prevents collisions). */
keyPrefix?: string;
/** TTL for completed/failed requests (ms). Defaults to 1 hour. */
ttl?: number;
}
// ── Service Types ──────────────────────────────────────────────────────────
/**
* Core idempotency service interface.
*/
export interface IdempotencyService {
/**
* Generate a new idempotency key.
*/
generateKey(params: IdempotencyKeyParams): IdempotencyKey;
/**
* Check if a key represents a duplicate submission.
*/
checkDuplicate(key: IdempotencyKey): DuplicateCheckResult;
/**
* Register a new idempotent request (transitions to PENDING).
*/
registerRequest(
key: IdempotencyKey,
operation: string,
context?: Record<string, unknown>,
): IdempotencyRequest;
/**
* Update request state (e.g., PENDING → IN_FLIGHT → COMPLETED).
*/
updateState(
key: IdempotencyKey,
state: IdempotencyRequestState,
metadata?: Partial<Pick<IdempotencyRequest, 'txHash' | 'ledger' | 'error'>>,
): IdempotencyRequest;
/**
* Retrieve an existing request by key.
*/
getRequest(key: IdempotencyKey): IdempotencyRequest | null;
/**
* Attempt to recover a request with UNKNOWN outcome.
*/
recoverRequest(options: RecoveryOptions): Promise<RecoveryResult>;
/**
* Clear expired requests (completed/failed beyond TTL).
*/
clearExpired(): void;
/**
* Clear all requests (useful for testing or logout).
*/
clearAll(): void;
}