# `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/separator - `compositeKey(...parts)` — deterministic djb2 hash of ordered string parts; bounded-length output - `fingerprintPayload(payload)` — synchronous djb2 fingerprint with stable JSON serialization (key-order independent) - `fingerprintPayloadAsync(payload)` — SHA-256 fingerprint via `globalThis.crypto.subtle` for cryptographic strength - Recursive stable `stableStringify` ensuring `{ a: 1, b: 2 }` === `{ b: 2, a: 1 }` - Approved use of `globalThis.crypto.randomUUID` and `globalThis.crypto.subtle` (see `tests/no-disallowed.test.ts`) ## Usage: Basic Example ```ts 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 ```ts 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 string - `config.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` SHA-256 fingerprint using `globalThis.crypto.subtle.digest`. Requires an async context. **Returns:** `Promise` — 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 `fingerprintPayloadAsync` where cryptographic strength matters. - `stableStringify` sorts 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.subtle` is available in the same environments. It is **not** available in some older edge runtimes; fall back to `fingerprintPayload` if needed.