@oncely/client 0.2.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/README.md +132 -0
- package/dist/index.cjs +268 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +325 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.js +252 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key generation utilities for idempotency keys.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate a unique idempotency key.
|
|
6
|
+
*
|
|
7
|
+
* When called with no arguments, generates a random UUID v4.
|
|
8
|
+
* When called with components, generates a deterministic hash-based key.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Random key (UUID v4)
|
|
13
|
+
* const key = generateKey();
|
|
14
|
+
* // => "550e8400-e29b-41d4-a716-446655440000"
|
|
15
|
+
*
|
|
16
|
+
* // Deterministic key from components
|
|
17
|
+
* const key = generateKey('user-123', 'create-order', 1234567890);
|
|
18
|
+
* // => "f7c3bc1d..." (consistent for same inputs)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function generateKey(...components: (string | number | boolean)[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a prefixed idempotency key.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const key = generatePrefixedKey('ord');
|
|
28
|
+
* // => "ord_550e8400-e29b-41d4-a716-446655440000"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare function generatePrefixedKey(prefix: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Key generator interface for the namespace.
|
|
34
|
+
*/
|
|
35
|
+
interface KeyGenerator {
|
|
36
|
+
/**
|
|
37
|
+
* Generate a unique key, optionally from components.
|
|
38
|
+
*/
|
|
39
|
+
(...components: (string | number | boolean)[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Generate a prefixed key.
|
|
42
|
+
*/
|
|
43
|
+
prefixed(prefix: string): string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create the key generator function with attached methods.
|
|
47
|
+
*/
|
|
48
|
+
declare function createKeyGenerator(): KeyGenerator;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Key store for managing pending idempotency keys.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Options for creating a key store.
|
|
55
|
+
*/
|
|
56
|
+
interface StoreOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Storage backend (localStorage, sessionStorage, or custom).
|
|
59
|
+
* @default localStorage in browser, in-memory in Node.js
|
|
60
|
+
*/
|
|
61
|
+
storage?: Storage | MemoryStorage;
|
|
62
|
+
/**
|
|
63
|
+
* Key prefix for namespacing.
|
|
64
|
+
* @default 'oncely:'
|
|
65
|
+
*/
|
|
66
|
+
prefix?: string;
|
|
67
|
+
/**
|
|
68
|
+
* TTL for pending keys in milliseconds or string format.
|
|
69
|
+
* After this time, pending keys are considered expired.
|
|
70
|
+
* @default '5m'
|
|
71
|
+
*/
|
|
72
|
+
ttl?: number | string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Minimal storage interface compatible with localStorage/sessionStorage.
|
|
76
|
+
*/
|
|
77
|
+
interface Storage {
|
|
78
|
+
getItem(key: string): string | null;
|
|
79
|
+
setItem(key: string, value: string): void;
|
|
80
|
+
removeItem(key: string): void;
|
|
81
|
+
clear(): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Key store for managing pending idempotency keys.
|
|
85
|
+
*/
|
|
86
|
+
interface KeyStore {
|
|
87
|
+
/**
|
|
88
|
+
* Mark a key as pending (request in flight).
|
|
89
|
+
*/
|
|
90
|
+
pending(key: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* Check if a key is currently pending.
|
|
93
|
+
*/
|
|
94
|
+
isPending(key: string): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Clear a pending key.
|
|
97
|
+
*/
|
|
98
|
+
clear(key: string): void;
|
|
99
|
+
/**
|
|
100
|
+
* Clear all pending keys.
|
|
101
|
+
*/
|
|
102
|
+
clearAll(): void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* In-memory storage implementation.
|
|
106
|
+
*/
|
|
107
|
+
declare class MemoryStorage implements Storage {
|
|
108
|
+
private data;
|
|
109
|
+
getItem(key: string): string | null;
|
|
110
|
+
setItem(key: string, value: string): void;
|
|
111
|
+
removeItem(key: string): void;
|
|
112
|
+
clear(): void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a key store for managing pending idempotency keys.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const store = createStore();
|
|
120
|
+
*
|
|
121
|
+
* // Before making request
|
|
122
|
+
* if (store.isPending('key-123')) {
|
|
123
|
+
* throw new Error('Request already in progress');
|
|
124
|
+
* }
|
|
125
|
+
* store.pending('key-123');
|
|
126
|
+
*
|
|
127
|
+
* try {
|
|
128
|
+
* await fetch('/api/orders', { ... });
|
|
129
|
+
* } finally {
|
|
130
|
+
* store.clear('key-123');
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
declare function createStore(options?: StoreOptions): KeyStore;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Response helper utilities for detecting idempotency-related responses.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* RFC 7807 Problem Details response format.
|
|
141
|
+
* @see https://www.rfc-editor.org/rfc/rfc7807
|
|
142
|
+
*/
|
|
143
|
+
interface ProblemDetails {
|
|
144
|
+
/** URI reference identifying the problem type */
|
|
145
|
+
type: string;
|
|
146
|
+
/** Short human-readable summary */
|
|
147
|
+
title: string;
|
|
148
|
+
/** HTTP status code */
|
|
149
|
+
status: number;
|
|
150
|
+
/** Detailed human-readable explanation */
|
|
151
|
+
detail: string;
|
|
152
|
+
/** URI reference to the specific occurrence (optional) */
|
|
153
|
+
instance?: string;
|
|
154
|
+
/** Retry-After value in seconds (for conflict errors) */
|
|
155
|
+
retryAfter?: number;
|
|
156
|
+
/** Additional properties */
|
|
157
|
+
[key: string]: unknown;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Response-like object for compatibility with various fetch implementations.
|
|
161
|
+
*/
|
|
162
|
+
interface ResponseLike {
|
|
163
|
+
status: number;
|
|
164
|
+
headers: {
|
|
165
|
+
get(name: string): string | null;
|
|
166
|
+
};
|
|
167
|
+
json?(): Promise<unknown>;
|
|
168
|
+
clone?(): ResponseLike;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check if a response is a replay (cached response).
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const response = await fetch('/api/orders', { ... });
|
|
176
|
+
* if (isReplay(response)) {
|
|
177
|
+
* console.log('Response was served from cache');
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function isReplay(response: ResponseLike): boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Check if a response indicates a conflict (409).
|
|
184
|
+
* This means a request with the same key is already in progress.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* if (isConflict(response)) {
|
|
189
|
+
* const retryAfter = getRetryAfter(response);
|
|
190
|
+
* await delay(retryAfter * 1000);
|
|
191
|
+
* // Retry the request
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
declare function isConflict(response: ResponseLike): boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Check if a response indicates a mismatch (422).
|
|
198
|
+
* This means the idempotency key was reused with a different request payload.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* if (isMismatch(response)) {
|
|
203
|
+
* // Generate a new key and retry
|
|
204
|
+
* const newKey = oncely.key();
|
|
205
|
+
* // Retry with new key
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
declare function isMismatch(response: ResponseLike): boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Check if a response indicates a missing key error (400).
|
|
212
|
+
* This means the idempotency key header was required but not provided.
|
|
213
|
+
*/
|
|
214
|
+
declare function isMissingKey(response: ResponseLike): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Get the Retry-After value from a response.
|
|
217
|
+
* Returns the value in seconds, or the default if not present.
|
|
218
|
+
*
|
|
219
|
+
* @param response - The response to check
|
|
220
|
+
* @param defaultValue - Default value if header is not present (default: 1)
|
|
221
|
+
* @returns Retry delay in seconds
|
|
222
|
+
*/
|
|
223
|
+
declare function getRetryAfter(response: ResponseLike, defaultValue?: number): number;
|
|
224
|
+
/**
|
|
225
|
+
* Parse RFC 7807 Problem Details from an error response.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* if (!response.ok) {
|
|
230
|
+
* const problem = await getProblem(response);
|
|
231
|
+
* console.error(`${problem.title}: ${problem.detail}`);
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
declare function getProblem(response: ResponseLike): Promise<ProblemDetails | null>;
|
|
236
|
+
/**
|
|
237
|
+
* Check if a response is an idempotency error (400, 409, or 422).
|
|
238
|
+
*/
|
|
239
|
+
declare function isIdempotencyError(response: ResponseLike): boolean;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.
|
|
243
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header
|
|
244
|
+
*/
|
|
245
|
+
/** Standard header name for idempotency keys */
|
|
246
|
+
declare const HEADER = "Idempotency-Key";
|
|
247
|
+
/** Header indicating a response was replayed from cache */
|
|
248
|
+
declare const HEADER_REPLAY = "Idempotency-Replay";
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Oncely client namespace.
|
|
252
|
+
* All client-side functionality is accessed through this namespace.
|
|
253
|
+
*/
|
|
254
|
+
declare const oncely: {
|
|
255
|
+
/**
|
|
256
|
+
* Generate a unique idempotency key.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```typescript
|
|
260
|
+
* // Random UUID key
|
|
261
|
+
* const key = oncely.key();
|
|
262
|
+
*
|
|
263
|
+
* // Deterministic key from components
|
|
264
|
+
* const key = oncely.key('user-123', 'action', timestamp);
|
|
265
|
+
*
|
|
266
|
+
* // Prefixed key
|
|
267
|
+
* const key = oncely.key.prefixed('ord');
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
readonly key: KeyGenerator;
|
|
271
|
+
/**
|
|
272
|
+
* Create a key store for managing pending requests.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const store = oncely.store();
|
|
277
|
+
*
|
|
278
|
+
* if (store.isPending(key)) {
|
|
279
|
+
* throw new Error('Request in progress');
|
|
280
|
+
* }
|
|
281
|
+
* store.pending(key);
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
readonly store: typeof createStore;
|
|
285
|
+
/**
|
|
286
|
+
* Check if response is a replay (served from cache).
|
|
287
|
+
*/
|
|
288
|
+
readonly isReplay: typeof isReplay;
|
|
289
|
+
/**
|
|
290
|
+
* Check if response is a conflict (409).
|
|
291
|
+
*/
|
|
292
|
+
readonly isConflict: typeof isConflict;
|
|
293
|
+
/**
|
|
294
|
+
* Check if response is a mismatch (422).
|
|
295
|
+
*/
|
|
296
|
+
readonly isMismatch: typeof isMismatch;
|
|
297
|
+
/**
|
|
298
|
+
* Check if response is a missing key error (400).
|
|
299
|
+
*/
|
|
300
|
+
readonly isMissingKey: typeof isMissingKey;
|
|
301
|
+
/**
|
|
302
|
+
* Check if response is any idempotency error (400, 409, 422).
|
|
303
|
+
*/
|
|
304
|
+
readonly isIdempotencyError: typeof isIdempotencyError;
|
|
305
|
+
/**
|
|
306
|
+
* Get Retry-After value from response.
|
|
307
|
+
*/
|
|
308
|
+
readonly getRetryAfter: typeof getRetryAfter;
|
|
309
|
+
/**
|
|
310
|
+
* Parse RFC 7807 Problem Details from response.
|
|
311
|
+
*/
|
|
312
|
+
readonly getProblem: typeof getProblem;
|
|
313
|
+
/**
|
|
314
|
+
* Standard header name for idempotency keys.
|
|
315
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header
|
|
316
|
+
*/
|
|
317
|
+
readonly HEADER: "Idempotency-Key";
|
|
318
|
+
/**
|
|
319
|
+
* Header indicating a response was replayed from cache.
|
|
320
|
+
*/
|
|
321
|
+
readonly HEADER_REPLAY: "Idempotency-Replay";
|
|
322
|
+
};
|
|
323
|
+
type OncelyClient = typeof oncely;
|
|
324
|
+
|
|
325
|
+
export { HEADER, HEADER_REPLAY, type KeyGenerator, type KeyStore, MemoryStorage, type OncelyClient, type ProblemDetails, type ResponseLike, type Storage, type StoreOptions, createKeyGenerator, createStore, generateKey, generatePrefixedKey, getProblem, getRetryAfter, isConflict, isIdempotencyError, isMismatch, isMissingKey, isReplay, oncely };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key generation utilities for idempotency keys.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate a unique idempotency key.
|
|
6
|
+
*
|
|
7
|
+
* When called with no arguments, generates a random UUID v4.
|
|
8
|
+
* When called with components, generates a deterministic hash-based key.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Random key (UUID v4)
|
|
13
|
+
* const key = generateKey();
|
|
14
|
+
* // => "550e8400-e29b-41d4-a716-446655440000"
|
|
15
|
+
*
|
|
16
|
+
* // Deterministic key from components
|
|
17
|
+
* const key = generateKey('user-123', 'create-order', 1234567890);
|
|
18
|
+
* // => "f7c3bc1d..." (consistent for same inputs)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function generateKey(...components: (string | number | boolean)[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a prefixed idempotency key.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const key = generatePrefixedKey('ord');
|
|
28
|
+
* // => "ord_550e8400-e29b-41d4-a716-446655440000"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare function generatePrefixedKey(prefix: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Key generator interface for the namespace.
|
|
34
|
+
*/
|
|
35
|
+
interface KeyGenerator {
|
|
36
|
+
/**
|
|
37
|
+
* Generate a unique key, optionally from components.
|
|
38
|
+
*/
|
|
39
|
+
(...components: (string | number | boolean)[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Generate a prefixed key.
|
|
42
|
+
*/
|
|
43
|
+
prefixed(prefix: string): string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create the key generator function with attached methods.
|
|
47
|
+
*/
|
|
48
|
+
declare function createKeyGenerator(): KeyGenerator;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Key store for managing pending idempotency keys.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Options for creating a key store.
|
|
55
|
+
*/
|
|
56
|
+
interface StoreOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Storage backend (localStorage, sessionStorage, or custom).
|
|
59
|
+
* @default localStorage in browser, in-memory in Node.js
|
|
60
|
+
*/
|
|
61
|
+
storage?: Storage | MemoryStorage;
|
|
62
|
+
/**
|
|
63
|
+
* Key prefix for namespacing.
|
|
64
|
+
* @default 'oncely:'
|
|
65
|
+
*/
|
|
66
|
+
prefix?: string;
|
|
67
|
+
/**
|
|
68
|
+
* TTL for pending keys in milliseconds or string format.
|
|
69
|
+
* After this time, pending keys are considered expired.
|
|
70
|
+
* @default '5m'
|
|
71
|
+
*/
|
|
72
|
+
ttl?: number | string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Minimal storage interface compatible with localStorage/sessionStorage.
|
|
76
|
+
*/
|
|
77
|
+
interface Storage {
|
|
78
|
+
getItem(key: string): string | null;
|
|
79
|
+
setItem(key: string, value: string): void;
|
|
80
|
+
removeItem(key: string): void;
|
|
81
|
+
clear(): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Key store for managing pending idempotency keys.
|
|
85
|
+
*/
|
|
86
|
+
interface KeyStore {
|
|
87
|
+
/**
|
|
88
|
+
* Mark a key as pending (request in flight).
|
|
89
|
+
*/
|
|
90
|
+
pending(key: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* Check if a key is currently pending.
|
|
93
|
+
*/
|
|
94
|
+
isPending(key: string): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Clear a pending key.
|
|
97
|
+
*/
|
|
98
|
+
clear(key: string): void;
|
|
99
|
+
/**
|
|
100
|
+
* Clear all pending keys.
|
|
101
|
+
*/
|
|
102
|
+
clearAll(): void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* In-memory storage implementation.
|
|
106
|
+
*/
|
|
107
|
+
declare class MemoryStorage implements Storage {
|
|
108
|
+
private data;
|
|
109
|
+
getItem(key: string): string | null;
|
|
110
|
+
setItem(key: string, value: string): void;
|
|
111
|
+
removeItem(key: string): void;
|
|
112
|
+
clear(): void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a key store for managing pending idempotency keys.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const store = createStore();
|
|
120
|
+
*
|
|
121
|
+
* // Before making request
|
|
122
|
+
* if (store.isPending('key-123')) {
|
|
123
|
+
* throw new Error('Request already in progress');
|
|
124
|
+
* }
|
|
125
|
+
* store.pending('key-123');
|
|
126
|
+
*
|
|
127
|
+
* try {
|
|
128
|
+
* await fetch('/api/orders', { ... });
|
|
129
|
+
* } finally {
|
|
130
|
+
* store.clear('key-123');
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
declare function createStore(options?: StoreOptions): KeyStore;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Response helper utilities for detecting idempotency-related responses.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* RFC 7807 Problem Details response format.
|
|
141
|
+
* @see https://www.rfc-editor.org/rfc/rfc7807
|
|
142
|
+
*/
|
|
143
|
+
interface ProblemDetails {
|
|
144
|
+
/** URI reference identifying the problem type */
|
|
145
|
+
type: string;
|
|
146
|
+
/** Short human-readable summary */
|
|
147
|
+
title: string;
|
|
148
|
+
/** HTTP status code */
|
|
149
|
+
status: number;
|
|
150
|
+
/** Detailed human-readable explanation */
|
|
151
|
+
detail: string;
|
|
152
|
+
/** URI reference to the specific occurrence (optional) */
|
|
153
|
+
instance?: string;
|
|
154
|
+
/** Retry-After value in seconds (for conflict errors) */
|
|
155
|
+
retryAfter?: number;
|
|
156
|
+
/** Additional properties */
|
|
157
|
+
[key: string]: unknown;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Response-like object for compatibility with various fetch implementations.
|
|
161
|
+
*/
|
|
162
|
+
interface ResponseLike {
|
|
163
|
+
status: number;
|
|
164
|
+
headers: {
|
|
165
|
+
get(name: string): string | null;
|
|
166
|
+
};
|
|
167
|
+
json?(): Promise<unknown>;
|
|
168
|
+
clone?(): ResponseLike;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check if a response is a replay (cached response).
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const response = await fetch('/api/orders', { ... });
|
|
176
|
+
* if (isReplay(response)) {
|
|
177
|
+
* console.log('Response was served from cache');
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function isReplay(response: ResponseLike): boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Check if a response indicates a conflict (409).
|
|
184
|
+
* This means a request with the same key is already in progress.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* if (isConflict(response)) {
|
|
189
|
+
* const retryAfter = getRetryAfter(response);
|
|
190
|
+
* await delay(retryAfter * 1000);
|
|
191
|
+
* // Retry the request
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
declare function isConflict(response: ResponseLike): boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Check if a response indicates a mismatch (422).
|
|
198
|
+
* This means the idempotency key was reused with a different request payload.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* if (isMismatch(response)) {
|
|
203
|
+
* // Generate a new key and retry
|
|
204
|
+
* const newKey = oncely.key();
|
|
205
|
+
* // Retry with new key
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
declare function isMismatch(response: ResponseLike): boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Check if a response indicates a missing key error (400).
|
|
212
|
+
* This means the idempotency key header was required but not provided.
|
|
213
|
+
*/
|
|
214
|
+
declare function isMissingKey(response: ResponseLike): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Get the Retry-After value from a response.
|
|
217
|
+
* Returns the value in seconds, or the default if not present.
|
|
218
|
+
*
|
|
219
|
+
* @param response - The response to check
|
|
220
|
+
* @param defaultValue - Default value if header is not present (default: 1)
|
|
221
|
+
* @returns Retry delay in seconds
|
|
222
|
+
*/
|
|
223
|
+
declare function getRetryAfter(response: ResponseLike, defaultValue?: number): number;
|
|
224
|
+
/**
|
|
225
|
+
* Parse RFC 7807 Problem Details from an error response.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* if (!response.ok) {
|
|
230
|
+
* const problem = await getProblem(response);
|
|
231
|
+
* console.error(`${problem.title}: ${problem.detail}`);
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
declare function getProblem(response: ResponseLike): Promise<ProblemDetails | null>;
|
|
236
|
+
/**
|
|
237
|
+
* Check if a response is an idempotency error (400, 409, or 422).
|
|
238
|
+
*/
|
|
239
|
+
declare function isIdempotencyError(response: ResponseLike): boolean;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.
|
|
243
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header
|
|
244
|
+
*/
|
|
245
|
+
/** Standard header name for idempotency keys */
|
|
246
|
+
declare const HEADER = "Idempotency-Key";
|
|
247
|
+
/** Header indicating a response was replayed from cache */
|
|
248
|
+
declare const HEADER_REPLAY = "Idempotency-Replay";
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Oncely client namespace.
|
|
252
|
+
* All client-side functionality is accessed through this namespace.
|
|
253
|
+
*/
|
|
254
|
+
declare const oncely: {
|
|
255
|
+
/**
|
|
256
|
+
* Generate a unique idempotency key.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```typescript
|
|
260
|
+
* // Random UUID key
|
|
261
|
+
* const key = oncely.key();
|
|
262
|
+
*
|
|
263
|
+
* // Deterministic key from components
|
|
264
|
+
* const key = oncely.key('user-123', 'action', timestamp);
|
|
265
|
+
*
|
|
266
|
+
* // Prefixed key
|
|
267
|
+
* const key = oncely.key.prefixed('ord');
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
readonly key: KeyGenerator;
|
|
271
|
+
/**
|
|
272
|
+
* Create a key store for managing pending requests.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const store = oncely.store();
|
|
277
|
+
*
|
|
278
|
+
* if (store.isPending(key)) {
|
|
279
|
+
* throw new Error('Request in progress');
|
|
280
|
+
* }
|
|
281
|
+
* store.pending(key);
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
readonly store: typeof createStore;
|
|
285
|
+
/**
|
|
286
|
+
* Check if response is a replay (served from cache).
|
|
287
|
+
*/
|
|
288
|
+
readonly isReplay: typeof isReplay;
|
|
289
|
+
/**
|
|
290
|
+
* Check if response is a conflict (409).
|
|
291
|
+
*/
|
|
292
|
+
readonly isConflict: typeof isConflict;
|
|
293
|
+
/**
|
|
294
|
+
* Check if response is a mismatch (422).
|
|
295
|
+
*/
|
|
296
|
+
readonly isMismatch: typeof isMismatch;
|
|
297
|
+
/**
|
|
298
|
+
* Check if response is a missing key error (400).
|
|
299
|
+
*/
|
|
300
|
+
readonly isMissingKey: typeof isMissingKey;
|
|
301
|
+
/**
|
|
302
|
+
* Check if response is any idempotency error (400, 409, 422).
|
|
303
|
+
*/
|
|
304
|
+
readonly isIdempotencyError: typeof isIdempotencyError;
|
|
305
|
+
/**
|
|
306
|
+
* Get Retry-After value from response.
|
|
307
|
+
*/
|
|
308
|
+
readonly getRetryAfter: typeof getRetryAfter;
|
|
309
|
+
/**
|
|
310
|
+
* Parse RFC 7807 Problem Details from response.
|
|
311
|
+
*/
|
|
312
|
+
readonly getProblem: typeof getProblem;
|
|
313
|
+
/**
|
|
314
|
+
* Standard header name for idempotency keys.
|
|
315
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header
|
|
316
|
+
*/
|
|
317
|
+
readonly HEADER: "Idempotency-Key";
|
|
318
|
+
/**
|
|
319
|
+
* Header indicating a response was replayed from cache.
|
|
320
|
+
*/
|
|
321
|
+
readonly HEADER_REPLAY: "Idempotency-Replay";
|
|
322
|
+
};
|
|
323
|
+
type OncelyClient = typeof oncely;
|
|
324
|
+
|
|
325
|
+
export { HEADER, HEADER_REPLAY, type KeyGenerator, type KeyStore, MemoryStorage, type OncelyClient, type ProblemDetails, type ResponseLike, type Storage, type StoreOptions, createKeyGenerator, createStore, generateKey, generatePrefixedKey, getProblem, getRetryAfter, isConflict, isIdempotencyError, isMismatch, isMissingKey, isReplay, oncely };
|