Reference
Everything XKV offers, on one page: the Go package, the xkv command, the HTTP mode, and the file. The README is the full manual, and the Go documentation is on pkg.go.dev.
Revisions
Every write is a revision. A revision has:
| Field | Meaning |
|---|---|
Rev |
store-wide revision number; only goes up |
Tx |
transaction id; revisions written together share it |
Key, Value |
the key and its new value |
Deleted |
true when the revision deletes the key |
Actor |
who made it |
Reason |
why; required unless the store is relaxed |
Ref |
an outside reference: a ticket, a request id, a tool call id |
Causes |
up to 8 earlier revisions, as key and revision number |
Time |
when it was written |
The Go Package
| Call | Does |
|---|---|
Open(path, opts) |
opens or creates a store file; one writer per file |
OpenMemory(opts) |
a store in memory, for tests and the browser |
Put(ctx, key, value, opts...) |
writes one value |
Delete(ctx, key, opts...) |
deletes a key; the delete is a revision too |
Update(ctx, fn, opts...) |
writes several keys in one transaction |
Get(ctx, key) |
the current revision |
GetAt(ctx, key, rev) |
the key as the store stood at revision rev, or ErrCompacted if compaction removed that state |
Revision(ctx, rev) |
one revision by number |
History(ctx, key, limit) |
a key’s revisions, newest first |
Why(ctx, key, depth) |
the tree of causes behind the current value |
WhyRev(ctx, rev, depth) |
the same for one revision |
Keys(ctx, prefix) |
keys that have a value |
Compact(ctx, opts) |
drops old revisions, keeping every one a kept value points at. On Windows it fails with nothing changed while another process has the file open |
Check(path) |
reads every record and cause of a file |
Write options: Reason(s), Actor(s), Ref(s), Cause(key, rev) and CauseLatest(key). WithActor(ctx, name) sets the actor for every write made with that context.
Open options: ReadOnly, Relaxed (accept writes without a reason, recorded as “unspecified”), NoSync (skip the fsync after each transaction; a power cut can then need a repair), Repair (trim a damaged file back to its last good transaction), DefaultActor.
Errors to check with errors.Is: ErrNotFound, ErrNoReason, ErrBadCause, ErrCompacted, ErrLocked, ErrReadOnly, ErrCorrupt, ErrFailed (a write or fsync failed; reopen the store), ErrTooLarge, ErrInvalid.
Causes
A cause must exist, must belong to the key it names, and must be older than the write. A write with a bad cause is refused and writes nothing. CauseLatest(key) points at the key’s revision current when the write is made. Inside a transaction, a cause may point at a write made earlier in the same transaction.
Why follows causes 16 levels deep by default. A node whose causes the limit didn’t reach is marked Truncated; a revision reached twice is shown in full once and marked Repeat after that. Each node also carries Prev, the key’s revision just before it. If compaction removed that revision, Prev is empty and PrevCompacted is set.
The xkv Command
| Command | Does |
|---|---|
xkv put FILE KEY VALUE --reason R |
writes a value; add --cause KEY[@REV], --actor, --ref, or --stdin for the value |
xkv del FILE KEY --reason R |
deletes a key |
xkv get FILE KEY [--at REV] |
prints the value |
xkv log FILE KEY [--limit N] |
a key’s revisions, newest first |
xkv why FILE KEY [--depth N] |
the chain of causes; --rev REV for one revision |
xkv rev FILE REV |
one revision |
xkv keys FILE [PREFIX] |
keys that have a value |
xkv stats FILE |
counts |
xkv check FILE |
checks every record and cause |
xkv compact FILE [--keep N] [--keep-days D] |
compacts |
xkv repair FILE |
trims a damaged file back to its last good transaction, keeping the cut bytes in a side file |
xkv serve FILE [--addr HOST:PORT] |
the HTTP mode |
Most commands take --json. The actor is --actor, else $XKV_ACTOR, else your user name. Read commands open the file read-only, so they work while xkv serve holds it.
HTTP Mode
xkv serve FILE listens on 127.0.0.1:7070 and speaks JSON. It has no authentication, so keep it on localhost.
| Method and path | Does |
|---|---|
GET /v1/keys?prefix=P |
keys that have a value |
GET /v1/keys/KEY[?at=REV] |
a key’s revision |
PUT /v1/keys/KEY |
writes; body {"value", "reason", "actor", "ref", "causes": [{"key", "rev"}]} |
DELETE /v1/keys/KEY |
deletes; same body without value |
GET /v1/history/KEY[?limit=N] |
revisions, newest first |
GET /v1/why/KEY[?depth=N] |
the tree of causes |
GET /v1/revs/REV and /v1/revs/REV/why |
one revision, and its tree |
GET /v1/stats |
counts |
A cause without rev points at the key’s current revision. Errors come back as {"error": "..."} with 400, 404, 410, 413 or 500. The HTTP reference has the details.
The File
A store is one append-only file: a 32-byte header, then frames, each with a length and a CRC-32C checksum. A transaction is its revision records followed by a commit record, written with one write call and an fsync. On open, a torn end of file is trimmed back to the last complete transaction; damage in the middle is refused. An index snapshot beside the file, FILE.idx, makes large files open faster and is always checked against the file. The format is written up byte by byte.
Limits
Keys up to 4 KB, values up to 16 MB, reasons up to 4 KB, 8 causes per write, 64 MB per transaction. The index lives in memory at about 70 bytes per revision.