Files
2026-05-27 23:47:53 +01:00

6.6 KiB

vault items

(no submodules)

  • migrations — (no submodules)- helpers — items-helpers.ts extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
  • items — (no submodules)- migrations — _(no submodule…
  • migrations — (no submodules)- helpers — items-helpers.ts extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
  • items — (no submodules)- migrations — (no submodules)- helpers — items-helpers.ts extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
  • items — (no submodules)- migrations — <!-- begin:auto…
  • migrations — (no submodules)- helpers — items-helpers.ts extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
  • items — (no submodules)- migrations — _(no submodul…
  • migrations — (no submodules)- helpers — items-helpers.ts extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
  • items — (no submodules)

Purpose

items.ts exposes the VaultItems class, the in-memory CRUD repository over the vault container's item map. It is the boundary between the public vault.items surface and the underlying state — every public method validates input via Zod, mutates the container Map<string, VaultItemRow>, appends an audit entry to the container history, encrypts concealed-field values per item, and notifies the orchestrator that a flush is due.

The repository operates on the in-memory state loaded from the encrypted container blob. It does NOT touch drive directly; persistence is the orchestrator's job (debounced flush + on lock). Concealed-field values (passwords / PINs / secure notes) are encrypted with per-item keys derived from the vault key, so even a leaked container snapshot keeps concealed values as ciphertext until decrypted on demand by rowToItem.

Features

  • create(fields) → VaultItem — Zod-validated insert with concealed-field encryption.
  • get(id) → VaultItem | null — lazy decryption; returns null for missing or tombstoned ids.
  • update(id, patch, expectedVersion?) → VaultItem — optimistic concurrency via version; throws VAULT.ITEM.VERSION_CONFLICT on stale writes.
  • delete(id) — tombstone (not physical delete) so sync can propagate the deletion.
  • list(options?) → VaultItem[] — filters by category / favorite, with offset / limit pagination.
  • allRows() — snapshot of non-deleted rows for the search index.
  • Every operation records a histogram sample and an outcome counter; OTel attributes are limited to { op, status } — no item ids, no field names, no plaintext.
  • Mutations call onMutation() so the orchestrator can debounce flushes.

Usage: Basic Example

import { VaultItems } from "@suaveplan/vault/items/items";

const items = new VaultItems({
    state,
    vaultKey,
    clock: Date.now,
    rng: undefined,
    onMutation: () => scheduleFlush(),
});

const created = items.create({
    title: "GitHub",
    category: "login",
    fields: { username: "alice", url: "https://github.com" },
    concealedFields: { password: "p@ssw0rd" },
    tags: ["work"],
    favorite: true,
});

Usage: Advanced Example

import { VaultItems } from "@suaveplan/vault/items/items";
import { VaultError, VAULT_CODES } from "@suaveplan/vault/errors";

// Optimistic concurrency: capture version, retry on conflict.
function updateWithRetry(items: VaultItems, id: string, patch: Partial<ItemFields>) {
    for (let attempt = 0; attempt < 3; attempt++) {
        const current = items.get(id);
        if (current === null) throw new Error("missing");
        try {
            return items.update(id, patch, current.version);
        } catch (err) {
            if (
                VaultError.is(err) &&
                err.code === VAULT_CODES["ITEM.VERSION_CONFLICT"] &&
                attempt < 2
            ) continue;
            throw err;
        }
    }
}

API Reference

  • class VaultItems — public surface for vault.items.
    • constructor(options: ItemsOptions).
    • create(fields: ItemFields): VaultItem — throws ITEM.VALIDATION_FAILED.
    • get(id: string): VaultItem | null.
    • update(id: string, patch: Partial<ItemFields>, expectedVersion?: number): VaultItem — throws ITEM.NOT_FOUND / ITEM.VERSION_CONFLICT / ITEM.VALIDATION_FAILED.
    • delete(id: string): void — throws ITEM.NOT_FOUND.
    • list(options?: ListOptions): VaultItem[] — throws ITEM.VALIDATION_FAILED.
    • allRows(): VaultItemRow[] — read-only snapshot for search.
  • interface ItemsOptions — { state, vaultKey, clock, rng, onMutation }.

Implementation Notes

  • The repository is stateful — it holds references to the container state map, the vault key, and the orchestrator's mutation callback. The vault layer creates a new VaultItems on every unlock; the state map is replaced when the container reloads from drive.
  • update always increments version by 1 per successful call. Concurrent writers using expectedVersion will see exactly one winner; the others receive VAULT.ITEM.VERSION_CONFLICT.
  • delete is a tombstone — the row stays in the map with deleted: true so the sync layer can propagate the deletion. A future garbage-collection pass (out of scope for this version) would prune tombstones older than a retention window.
  • The history log is append-only: every successful create / update / delete appends one row with (itemId, version, timestamp, operation). This feeds the audit-trail and CRDT merge paths.
  • list walks the map insertion order — the same order in which rows were created or last updated. Callers that need stable cross-process ordering should sort by createdAt or updatedAt after fetch.
  • The OTel outcome counter uses a fixed allowlist (success | not_found | conflict | rejected) — adding a new value requires an OpenSpec change in lockstep with getVaultInstruments.