Types
EntryKind = enum ekSnapshot, ekDiff, ekJson, ekJsonPatch
HistoryContent = object tsUnix*: int64 ## Unix timestamp (seconds) stamped when the entry was recorded case kind*: EntryKind of ekSnapshot: text*: string of ekDiff: diffOffset*: int diffDeleted*: int diffInserted*: string of ekJson: jsonData*: JsonNode of ekJsonPatch: patchForward*: seq[JsonOp] patchBackward*: seq[JsonOp]
HistoryEntry = object case kind*: EntryKind of ekSnapshot: content*: string of ekDiff: offset*: int deletedLen*: int inserted*: string of ekJson: data*: JsonNode of ekJsonPatch: fwdOps*: seq[JsonOp] ## Operations moving a document newer-ward (redo direction) bwdOps*: seq[JsonOp] ## Precomputed inverse operations (undo direction)
HistoryError = object of CatchableError
HistoryManager = ref object
JsonOp = object op*: JsonPatchOpKind path*: string value*: JsonNode ## Target value; unused for `jpoRemove`
- One RFC 6902-style operation addressed by an RFC 6901 JSON Pointer
JsonPatchOpKind = enum jpoAdd, jpoRemove, jpoReplace
NowFn = proc (): int64 {....gcsafe.}
- Millisecond clock used for idle-window auto-grouping; injectable for deterministic tests
Procs
proc beginGroup(h: HistoryManager) {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Opens a batching window; pushes inside it accumulate and are applied together when endGroup closes them as one undoable step
proc canRedo(h: HistoryManager): bool {....raises: [KeyError], tags: [], forbids: [].}
- True when stepping forward would reach recorded entries
proc canUndo(h: HistoryManager): bool {....raises: [], tags: [], forbids: [].}
- True when stepping back would leave the current step
proc close(h: HistoryManager) {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Flushes pending auto-grouped entries and releases the underlying store
proc compact(h: HistoryManager) {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError, Exception], tags: [ReadIOEffect, TimeEffect, WriteIOEffect, ReadDirEffect, WriteDirEffect], forbids: [].}
- Rewrites this history's WAL file keeping exactly the live entries of the active generation (timestamps preserved), then reopens it. Old generations and orphaned bytes are dropped from disk. No-op for in-memory histories. Raises inside an open group.
proc endGroup(h: HistoryManager) {....raises: [HistoryError, KeyError, IOError, WalError, LogStoreError, CatchableError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Closes the batching window and appends everything collected since beginGroup under a single step tag
proc jsonDiff(prevState, newState: JsonNode): tuple[fwd, bwd: seq[JsonOp]] {. ...raises: [KeyError], tags: [], forbids: [].}
- Computes forward and inverse operation sets transforming prevState into newState. Objects are walked recursively; anything else (scalars and arrays alike) diffs atomically as one replace.
proc newHistoryManager(path: string; historyId: string; maxVersions: int = 0; autoGroupMs: int = 0; autoGroupMax: int = 64; cacheCapacity: int = 1024): HistoryManager {....raises: [ HistoryError, LogStoreError, OSError, IOError, WalError, KeyError, CatchableError, JsonParsingError, ValueError], tags: [ReadDirEffect, WriteDirEffect, WriteIOEffect, ReadIOEffect], forbids: [].}
-
Opens or creates a disk-backed history under directory path.
maxVersions softly caps how many entries stay reachable. Once the active stream grows past twice the cap, it is rotated to keep only the most recent maxVersions entries (amortized O(1) per push). Pass 0 to keep unbounded history.
With autoGroupMs > 0, pushes arriving within that idle window are coalesced into a single undoable step (see setClock for test hooks).
proc newInMemoryHistoryManager(historyId: string; maxVersions: int = 0; autoGroupMs: int = 0; autoGroupMax: int = 64): HistoryManager {....raises: [ HistoryError, KeyError, IOError, WalError, LogStoreError, CatchableError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, WriteIOEffect], forbids: [].}
- Creates an ephemeral in-memory history. Nothing touches disk.
proc peekRedo(h: HistoryManager): Option[HistoryContent] {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Returns what redo() would return, without moving the cursor
proc peekUndo(h: HistoryManager): Option[HistoryContent] {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Returns what undo() would return, without moving the cursor
proc pushDiff(h: HistoryManager; offset, deletedLen: int; inserted: string) {....raises: [ Exception, KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [RootEffect, ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Records a text diff (offset, deleted character count, inserted text)
proc pushJson(h: HistoryManager; data: JsonNode) {....raises: [Exception, KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [RootEffect, ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Records a full JSON document state
proc pushJsonDiff(h: HistoryManager; prevState, newState: JsonNode) {....raises: [ KeyError, Exception, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [RootEffect, ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Diffs two JSON document states and records the delta as forward + inverse operation sets, keeping undo self-contained without storing full snapshots
proc pushJsonPatch(h: HistoryManager; ops, inverse: seq[JsonOp]) {....raises: [ Exception, KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [RootEffect, ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Records caller-supplied operation sets directly; inverse must undo exactly what ops applies
proc pushSnapshot(h: HistoryManager; content: string) {....raises: [Exception, KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [RootEffect, ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Records a full-text snapshot as a new undoable step
proc redo(h: HistoryManager): Option[HistoryContent] {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Steps forward one redoable step. When the cursor lands on an entry holding JSON Patch operations, that patch is returned (apply patchForward); otherwise the landed entry itself is returned. Returns none when already at the newest state.
proc restoreTo(h: HistoryManager; pos: int) {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Moves the cursor to pos (0 = pristine, pos = N = after the Nth entry) discarding everything newer. Equivalent to repeated undos or redos in one rotation. Positional: it counts entries, not steps.
proc setClock(h: HistoryManager; fn: NowFn) {....raises: [], tags: [], forbids: [].}
- Replaces the millisecond clock backing auto-grouping decisions. Intended for deterministic tests.
proc undo(h: HistoryManager): Option[HistoryContent] {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Steps back one undoable step. When the step being left holds JSON Patch operations, that patch is returned (apply patchBackward); otherwise the entry at the new cursor position is returned. Returns none when stepping onto the pristine state.
proc undoJson(h: HistoryManager): Option[JsonNode] {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Undo typed for JSON histories; none if the target is not JSON
proc undoText(h: HistoryManager): Option[string] {....raises: [KeyError, IOError, WalError, LogStoreError, CatchableError, HistoryError, OSError, JsonParsingError, ValueError], tags: [ReadIOEffect, TimeEffect, WriteIOEffect], forbids: [].}
- Undo typed for snapshot histories; none if the target is not a snapshot