first commit
This commit is contained in:
99
docs/foundation/flow/idempotency/src/key/key-generator.md
Normal file
99
docs/foundation/flow/idempotency/src/key/key-generator.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# `key-generator`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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<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 `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.
|
||||
Reference in New Issue
Block a user