3.9 KiB
key-generator
(no submodules)
Purpose
Provides idempotency key generation and payload fingerprinting utilities for @suaveplan/idempotency. Key generation uses globalThis.crypto.randomUUID() for random keys and a djb2 hash for deterministic composite keys. Fingerprinting enables mismatch detection: the same idempotency key reused with a different payload fingerprint triggers an IdempotencyMismatchError.
Features
generateIdempotencyKey(config?)— random UUID-based key with optional prefix/separatorcompositeKey(...parts)— deterministic djb2 hash of ordered string parts; bounded-length outputfingerprintPayload(payload)— synchronous djb2 fingerprint with stable JSON serialization (key-order independent)fingerprintPayloadAsync(payload)— SHA-256 fingerprint viaglobalThis.crypto.subtlefor cryptographic strength- Recursive stable
stableStringifyensuring{ a: 1, b: 2 }==={ b: 2, a: 1 } - Approved use of
globalThis.crypto.randomUUIDandglobalThis.crypto.subtle(seetests/no-disallowed.test.ts)
Usage: Basic Example
import {
generateIdempotencyKey,
compositeKey,
fingerprintPayload,
} from "@suaveplan/idempotency/key";
// Random key with optional prefix
const key = generateIdempotencyKey({ prefix: "order" });
// e.g. "order:550e8400-e29b-41d4-a716-446655440000"
// Deterministic key from known identifiers
const k = compositeKey("user-42", "order-99");
// djb2 hex of "user-42:order-99"
// Payload fingerprint for mismatch detection
const fp = fingerprintPayload({ amount: 100, currency: "USD" });
Usage: Advanced Example
import {
compositeKey,
fingerprintPayloadAsync,
generateIdempotencyKey,
} from "@suaveplan/idempotency/key";
// SHA-256 fingerprint for high-value operations
async function guardPayment(userId: string, body: PaymentBody) {
const key = compositeKey("payment", userId, body.orderId);
const fp = await fingerprintPayloadAsync(body);
return guard.execute(key, () => processPayment(body), body);
}
// Prefixed keys for multi-tenant systems
const tenantKey = generateIdempotencyKey({
prefix: `tenant:${tenantId}`,
separator: ":",
});
API Reference
generateIdempotencyKey(config?: KeyGeneratorConfig): string
Generates a random key using globalThis.crypto.randomUUID(). If config.prefix is set, it is prepended with the separator (default ':').
Params:
config.prefix?: string— optional prefix stringconfig.separator?: string— separator between parts (default':')
Returns: string — a unique key
compositeKey(...parts: string[]): string
Creates a deterministic key from one or more ordered string parts. Internally joins with ':' and applies djb2 hashing to produce a fixed-length hex string.
Throws: RangeError when called with zero parts.
fingerprintPayload(payload: unknown): string
Synchronous djb2 fingerprint of any JSON-serializable payload. Object key ordering is normalized before hashing.
Returns: string — 8-character hex fingerprint.
fingerprintPayloadAsync(payload: unknown): Promise<string>
SHA-256 fingerprint using globalThis.crypto.subtle.digest. Requires an async context.
Returns: Promise<string> — 64-character lower-case hex string.
Implementation Notes
- djb2 was chosen for synchronous fingerprinting due to its simplicity and zero-dependency nature. It is not collision-resistant for adversarial inputs; use
fingerprintPayloadAsyncwhere cryptographic strength matters. stableStringifysorts object keys recursively, making{ b: 2, a: 1 }and{ a: 1, b: 2 }produce identical digests. Arrays preserve order.globalThis.crypto.randomUUID()is available in Node 15+, Bun, Deno, and all modern browsers without any polyfill.globalThis.crypto.subtleis available in the same environments. It is not available in some older edge runtimes; fall back tofingerprintPayloadif needed.