3.8 KiB
3.8 KiB
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
maxVersionscap resolve(key, input, fn?)returns whether to execute and which version to writeaddVersionEntryrecords a snapshot into the historygetHistory,getLatestVersion,clearHistoryfor inspection and cleanup- Generic
<T>parameter for typed version entries - Compatible with any
IdempotencyStoreAdapter
Usage: Basic Example
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
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<T>(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 invokefnshouldExecute: false— cached result can be returned
Throws: VersionConflictError when strategy is 'reject' and a conflict is detected.
addVersionEntry(key, entry): void
Appends a VersionEntry<T> to the in-process history. Trims to maxVersions if exceeded.
getHistory(key): VersionEntry<T>[]
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.
maxVersionsevicts the oldest entries from the left of the array, sogetLatestVersionalways reflects the most recent.- The
overwritestrategy callsstore.delete(key)before returning, ensuring the nextsetcreates a fresh record at version1. VersionConflictErrorcarries bothexistingFingerprintandnewFingerprintin its context for downstream diagnostics.