experimental

Metadata riders

Three riders on the describe() contract — toJSONSchema() x- extension keys, dictKey inline labels, and devtools PII masking.

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:

  1. derivePersistedSchema(this.schema) — produces the base JSON Schema (zod-4 z.toJSONSchema() path; zod-3 via zod-to-json-schema).
  2. await this.describe({}) — the normalized per-field metadata.
  3. Overlay each property with the x- extension keys below.

x- key table:

describe() fieldJSON-Schema keyNotes
labelx-label
unitx-unit
semanticTypex-semanticType
sensitivityx-sensitivity'public', 'pii', or 'secret'
widgetx-widgetderived hint, overridable
editable: falsex-readonly: trueonly emitted when false
moneyx-moneyfull money block: { mode, currency?, scale?, rounding? }
refx-refbare string — the ref target collection name
dict.values[].labelx-enumLabels{ [value]: label } map
classifiedx-classifiedfull 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 into dict.values[].label with zero store I/O — equivalent to staticDict for display purposes.
  • describe({ resolveDictLabels: true }) (async) overrides from _dict_ and falls back to the inline label.
  • toJSONSchema() x-enumLabels picks them up via describe().

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 r to toggle reveal-all for the pane (a single pane-level toggle, not per-field — terminal-appropriate).
  • Public fields (sensitivity: 'public') and unclassified fields (no sensitivity) 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