Devtools
@alexify/syncom/inspect is what you reach for when a record does not read as you expect and you want to see what it is made of — and what you hand over when you report it.
import { inspect, anonymize } from '@alexify/syncom/inspect';
const report = await inspect(storage, id);
report.reading; // the record as get() would read it, lens and hooks aside
report.data; // the op model's terms: every path a field of its own
report.fields; // [{ field, kind, reachable, …the kind's counts and stamps }]
report.log; // every retained log entry of the record, by seqWhat inspect() shows
present,encrypted,alive;base,deletedandpurged, the reset stamps;v, the schema version the record was written under.fields: every field the metadata holds, sorted — a register with itsstamp([ts, origin]), a counter with itssumsper stream (the origin, ororigin@scope), a set with itselements(slots) and how many arelive, a list or a text with its block codek, newest stamptsand how manyruns,cuts,ext,pos,valsandmarksit holds, a tree with itsnodesandmoves— andreachable, whether the composed reading shows it (a field under a list element that is not shown is kept but not read).log: the record's retained entries —seqandeventId, the op'stype,originand itsstreamseq,ts,scope,field,actor,message, whether it issigned,skipped(a hook or a validator left it unapplied here) orscrubbed(a purge took its payload).
Nothing is read through the lens or the hooks, nothing is written, and an encrypted record shows its data and no metadata (it has none that replicates). stats() and the telemetry say how the node is; inspect() says what a record is.
Handing a case over
anonymize(value, { salt, keys }) copies anything JSON — a report, a snapshot page, an op, a whole dump — with every string and every key replaced by a stable hash of it: the same id, origin or word reads the same throughout the copy, so the structure of a case survives. Keys are hashed too because a set's elements, the origins of a clock and the streams of a vector are keys; keys: false keeps them (field names, for a readable report). Without a salt each call takes a fresh random one; give one to line two copies up, and keep it to yourself.
It is pseudonymization, not anonymization: numbers, booleans and nulls stay (counts, stamps, lengths are the shape), and the hash — two xxHash32, 64 bits — is not cryptographic: whoever holds the salt finds a short or guessable string again by trying it.
const page = await storage.snapshot();
const safe = anonymize({ report: await inspect(storage, id), page }, { keys: false });