Browse documentation
Documentation/Storage & retention

Storage & retention

Two tables. Indexed lookup. Explicit erasure.

Janitor stores a stable ID and a short history of normalized observations. Use a dedicated database or schema per application; sharing these tables shares the identity namespace.

Two tables

visitors contains the opaque ID, creation time and last-seen time. observations contains the visitor link, timestamp, coarse candidate lookup fields and full normalized JSON.

Field Purpose
platform, browser Coarse device/browser lookup
timezone Additional coarse candidate retrieval
webgl_renderer Graphics environment candidate retrieval
signals_json The complete normalized observation
seen_at Recency and retention

D1 uses integer timestamps and JSON text. Postgres uses bigint timestamps and JSONB. Both index the candidate fields and visitor observation recency, with cascading observation deletion.

Apply a migration

Cloudflare D1, from the example directory:

pnpm exec wrangler d1 migrations apply VISITORS --local

For Postgres, the examples include an idempotent migration command:

pnpm migrate

For an existing application, apply the SQL from the D1 migration or Postgres migration with your migration runner.

Keep a small history

const visitor = createNodeVisitor({
  db,
  observationRetentionDays: 90,
  maxObservationsPerVisitor: 10,
});

await visitor.cleanup();

Each save prunes that visitor’s history. Matching reads only the last five retained observations. Expired history is excluded from matching even before cleanup physically removes it. Run cleanup from your existing maintenance task; Janitor installs no scheduler, worker or queue.

D1 batches insertion and pruning atomically. Postgres performs separate committed insertion and pruning statements; cleanup or the next save repairs interrupted pruning.

Delete a visitor

// In an application-authorized server operation:
await visitor.deleteVisitor(visitorId);

Deletion cascades through that visitor’s observations. Also clear the cookie, stop the browser client with destroy(), and respect the application’s opt-out on later visits. Do not expose an unauthenticated endpoint accepting arbitrary visitor IDs for deletion.

See Privacy & signals for the complete erasure procedure and data inventory.

Optional identity directory

Enable identity: { secret, namespace } to add verified subjects, identity-key associations and delegation. Apply the storage package’s 0002_identity.sql migration after 0001_visitors.sql. Example migration commands apply all three migrations, including the optional learning table.

The directory adds three tables: identity_subjects, identity_keys, and identity_delegations. Key digests are unique; references cascade on subject erasure. Grant expiry, principal and actor columns are indexed. Full records use JSONB in Postgres and JSON text in D1.

cleanup() removes expired grants alongside browser-history maintenance. Subject/key records require explicit removal and should follow the application’s account lifecycle. Deleting a subject removes its keys and grants but leaves browser histories independent. See the identity directory guide.

Optional learning data

0003_learning.sql adds learning_sessions with an opaque session ID, application scope, fixed expiry, latest normalized snapshot, verified account label, disputed flag and optional shadow prediction. Foreign keys cascade for both labeled and predicted subjects. Default retention is 30 days, with 20 completed sessions per subject. Feature configuration and per-request collection permission are both required; creating the table alone does not enable collection. See opt-in learning.

Search documentation

Search the API, guides, and implementation notes.

Local search. No query leaves your browser.