@idempotix/client
v1.0.0
Published
Client-side idempotency helpers for browser and frontend applications
Maintainers
Readme
@idempotix/client
Client-side utilities for idempotency key generation and response handling.
Installation
npm install @idempotix/clientQuick Start
import { generateKey } from '@idempotix/client';
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': generateKey(),
},
body: JSON.stringify(order),
});Key Generation
import { generateKey, generatePrefixedKey, createKeyGenerator } from '@idempotix/client';
// UUID v4
const key = generateKey();
// => '550e8400-e29b-41d4-a716-446655440000'
// Prefixed UUID
const key = generatePrefixedKey('ord');
// => 'ord_550e8400-e29b-41d4-a716-446655440000'
// Deterministic key from components
const key = generateKey('user', userId, 'create-order');
// => 'f8e9b4a2' (consistent hash for same inputs)
// Key generator namespace
const key = createKeyGenerator();
key(); // UUID
key('a', 'b', 'c'); // Deterministic hash
key.prefixed('txn'); // Prefixed UUIDKey Store
Track pending requests to prevent duplicate submissions:
import { createStore } from '@idempotix/client';
const store = createStore({
prefix: 'Idempotix:',
ttl: '5m',
storage: localStorage,
});
// Mark request as pending
store.pending('key-123');
// Check if request is pending
if (store.isPending('key-123')) {
throw new Error('Request already in progress');
}
// Mark as completed
store.complete('key-123');
// Clear a specific key
store.clear('key-123');
// Clear all Idempotix keys
store.clearAll();Response Helpers
import { isReplay, isConflict, isMismatch, getRetryAfter } from '@idempotix/client';
const response = await fetch('/api/orders', { ... });
// Check if response was served from cache
if (isReplay(response)) {
console.log('Cached response');
}
// Handle 409 Conflict
if (isConflict(response)) {
const seconds = getRetryAfter(response);
await delay(seconds * 1000);
// Retry with same key...
}
// Handle 422 Mismatch
if (isMismatch(response)) {
// Key was reused with different body - generate new key
}Usage with Fetch Libraries
ky
import ky from 'ky';
import { generateKey, IDEMPOTENCY_KEY_HEADER } from '@idempotix/client';
const api = ky.extend({
hooks: {
beforeRequest: [
(request) => {
if (['POST', 'PUT', 'PATCH'].includes(request.method)) {
request.headers.set(IDEMPOTENCY_KEY_HEADER, generateKey());
}
},
],
},
});axios
import axios from 'axios';
import { generateKey, IDEMPOTENCY_KEY_HEADER } from '@idempotix/client';
const api = axios.create();
api.interceptors.request.use((config) => {
if (['post', 'put', 'patch'].includes(config.method ?? '')) {
config.headers[IDEMPOTENCY_KEY_HEADER] = generateKey();
}
return config;
});Constants
import { IDEMPOTENCY_KEY_HEADER, IDEMPOTENCY_REPLAY_HEADER } from '@idempotix/client';
IDEMPOTENCY_KEY_HEADER; // 'Idempotency-Key'
IDEMPOTENCY_REPLAY_HEADER; // 'Idempotency-Replay'License
MIT
