Metadata riders
Three riders extend the describe() contract outward — into JSON Schema, dict labels, and devtools masking.
collection.toJSONSchema()
An async method on Collection that produces a JSON Schema object annotated
with describe() metadata as x- extension keys:
const schema = await sales.toJSONSchema()
schema, for the sales collection declared earlier (no zod schema was
attached, so toJSONSchema() falls back to the minimal-schema path described
below):
{
"type": "object",
"properties": {
"buyerId": { "type": "string", "x-label": "Buyer", "x-semanticType": "entity",
"x-sensitivity": "pii", "x-widget": "ref-select", "x-ref": "buyers" },
"notes": { "type": "string", "x-label": "Internal notes", "x-sensitivity": "secret",
"x-widget": "text" },
"saleDate": { "type": "string", "x-label": "Date", "x-widget": "text" },
"status": { "type": "string", "x-label": "Status", "x-widget": "select" },
"total": { "type": "number", "x-label": "Total (€)", "x-unit": "€",
"x-semanticType": "currency", "x-widget": "money",
"x-money": { "mode": "fixed", "currency": "EUR", "scale": 2 } }
}
}
Pipeline:
derivePersistedSchema(this.schema)— produces the base JSON Schema (zod-4z.toJSONSchema()path; zod-3 viazod-to-json-schema).await this.describe({})— the normalized per-field metadata.- Overlay each property with the
x-extension keys below.
x- key table:
describe() field | JSON-Schema key | Notes |
|---|---|---|
label | x-label | |
unit | x-unit | |
semanticType | x-semanticType | |
sensitivity | x-sensitivity | 'public', 'pii', or 'secret' |
widget | x-widget | derived hint, overridable |
editable: false | x-readonly: true | only emitted when false |
money | x-money | full money block: { mode, currency?, scale?, rounding? } |
ref | x-ref | bare string — the ref target collection name |
dict.values[].label | x-enumLabels | { [value]: label } map |
classified | x-classified | full classified block (preset, storage, list, equatable?) — emitted whenever present, regardless of gating |
x-classified is emitted whenever a field carries a classified spec — it is
not gated by whether withClassified() is opted in
for reveal/verify at the collection level; a deployment that wants the schema
key itself gated applies that gate on top.
Validator-agnostic fallback: when derivePersistedSchema yields no JSON
Schema (non-zod validator), toJSONSchema() builds a minimal
{ type: 'object', properties } from the field types derived by describe(),
then overlays the same x- metadata. No throw; no zod required.
dictKey inline labels
dictKey gains an optional inline label map alongside the existing array form:
// Today (still valid — bare array):
dictKey('saleStatus', ['draft', 'to_verify', 'paid'] as const)
// New — value→label map (keys inferred from the map):
dictKey('saleStatus', { draft: 'Draft', to_verify: 'To Verify', paid: 'Paid' })
// Or array + labels option (preserves explicit order):
dictKey('saleStatus', VALUES, { labels: { to_verify: 'To Verify' } })
DictKeyDescriptor gains readonly labels?: Record<string, string>.
Semantics — display fallback:
- Inline labels are code-provided defaults surfaced when the
_dict_collection has no label for a key. describe()(sync) surfaces inline labels intodict.values[].labelwith zero store I/O — equivalent tostaticDictfor display purposes.describe({ resolveDictLabels: true })(async) overrides from_dict_and falls back to the inline label.toJSONSchema()x-enumLabelspicks them up viadescribe().
This keeps staticDict (closed code labels, no _dict_) and
dictKey + inline labels (dynamic dict with code defaults) as distinct,
non-overlapping tools.
Devtools PII masking
The terminal inspector's RecordsPane (@noy-db/in-devtools-tui) masks
values for fields where sensitivity !== 'public' (i.e. pii and secret),
protecting data during screen-sharing or shoulder surfing.
Behavior:
- Sensitive cells render as
••••by default. - Press
rto toggle reveal-all for the pane (a single pane-level toggle, not per-field — terminal-appropriate). - Public fields (
sensitivity: 'public') and unclassified fields (nosensitivity) always render their raw values — they are never masked. - Back-compat: a collection without
described(non-live or pre-enrichment) has an empty sensitive-field set → nothing is masked → behavior is identical to before.
Collection-change reset (TUI): the reveal-all flag resets when the selected collection index changes, so revealed PII does not linger when navigating to another collection.
This is shoulder-surfing / screen-share safety over already-decrypted local data — it is not access control. The data is already in the trusted tier.
See also
- Field metadata — the describe() contract these extend