# `key-versioning` _(no submodules)_ ## Purpose Provides multi-version idempotency record management for scenarios where the same key may legitimately be reused with different input payloads over its lifetime. `KeyVersioning` wraps the idempotency store and enforces a configurable `ConflictResolutionStrategy`, maintaining an in-process version history for audit, rollback, and debugging. ## Features - Three conflict strategies: `reject` (default), `overwrite`, `version` - In-process version history with configurable `maxVersions` cap - `resolve(key, input, fn?)` returns whether to execute and which version to write - `addVersionEntry` records a snapshot into the history - `getHistory`, `getLatestVersion`, `clearHistory` for inspection and cleanup - Generic `` parameter for typed version entries - Compatible with any `IdempotencyStoreAdapter` ## Usage: Basic Example ```ts import { KeyVersioning } from "@suaveplan/idempotency/versioning"; import { MemoryIdempotencyStore } from "@suaveplan/idempotency/store"; const versioning = new KeyVersioning({ store: new MemoryIdempotencyStore(), strategy: "version", maxVersions: 5, }); const { version, shouldExecute } = await versioning.resolve("order-42", payload); if (shouldExecute) { const result = await chargeCard(payload); versioning.addVersionEntry("order-42", { version, fingerprint: fingerprintPayload(payload), response: result, status: "completed", createdAt: Date.now(), }); } ``` ## Usage: Advanced Example ```ts import { KeyVersioning, VersionConflictError } from "@suaveplan/idempotency/versioning"; const versioning = new KeyVersioning({ store, strategy: "reject" }); try { const { shouldExecute } = await versioning.resolve("key", differentPayload); } catch (err) { if (err instanceof VersionConflictError) { // Log fingerprint conflict console.warn("Version conflict for key:", err.key); } } // Inspect history const history = versioning.getHistory("order-42"); console.log("Versions:", history.map((e) => e.version)); ``` ## API Reference ### `new KeyVersioning(config: KeyVersioningConfig)` | Option | Type | Default | Description | |--------|------|---------|-------------| | `store` | `IdempotencyStoreAdapter` | required | Record persistence | | `strategy` | `ConflictResolutionStrategy` | `"reject"` | Conflict strategy | | `maxVersions` | `number` | `10` | Max history entries per key | ### `resolve(key, input, fingerprintFn?): Promise<{ version: number; shouldExecute: boolean }>` Checks the store and applies the conflict strategy. Returns: - `shouldExecute: true` — caller should invoke `fn` - `shouldExecute: false` — cached result can be returned **Throws:** `VersionConflictError` when strategy is `'reject'` and a conflict is detected. ### `addVersionEntry(key, entry): void` Appends a `VersionEntry` to the in-process history. Trims to `maxVersions` if exceeded. ### `getHistory(key): VersionEntry[]` Returns all recorded version entries for the key, oldest first. ### `getLatestVersion(key): number` Returns the latest version number, or `0` if no history exists. ### `clearHistory(key): void` Removes all in-process history for the key. ## Implementation Notes - Version history is in-process only; it is not persisted to the store. On process restart, history is empty even if the store contains records with version numbers. - `maxVersions` evicts the oldest entries from the left of the array, so `getLatestVersion` always reflects the most recent. - The `overwrite` strategy calls `store.delete(key)` before returning, ensuring the next `set` creates a fresh record at version `1`. - `VersionConflictError` carries both `existingFingerprint` and `newFingerprint` in its context for downstream diagnostics.