@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.
@@ -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 };
@@ -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 };