Files
skills/docs/foundation/flow/idempotency/src/versioning/key-versioning.md
2026-05-29 08:20:56 +01:00

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 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 <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 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<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.
  • 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.