Initial Commit
This commit is contained in:
99
packages/security-stack/security/vault/src/items/items.md
Normal file
99
packages/security-stack/security/vault/src/items/items.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# vault items
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [migrations](./migrations/migrations.md) — <!-- begin:auto-docs --> _(no submodules)_- [helpers](./helpers.md) — `items-helpers.ts` extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
|
||||
- [items](./items.md) — <!-- begin:auto-docs --> _(no submodules)_- migrations — <!-- begin:auto-docs --> _(no submodule…
|
||||
- [migrations](./migrations/migrations.md) — <!-- begin:auto-docs --> _(no submodules)_- [helpers](./helpers.md) — `items-helpers.ts` extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
|
||||
- [items](./items.md) — <!-- begin:auto-docs --> _(no submodules)_- [migrations](./migrations/migrations.md) — <!-- begin:auto-docs --> _(no submodules)_- [helpers](./helpers.md) — `items-helpers.ts` extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
|
||||
- [items](./items.md) — <!-- begin:auto-docs --> _(no submodules)_- migrations — <!-- begin:auto…
|
||||
- [migrations](./migrations/migrations.md) — <!-- begin:auto-docs --> _(no submodules)_- [helpers](./helpers.md) — `items-helpers.ts` extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
|
||||
- [items](./items.md) — <!-- begin:auto-docs --> _(no submodules)_- migrations — <!-- begin:auto-docs --> _(no submodul…
|
||||
- [migrations](./migrations/migrations.md) — <!-- begin:auto-docs --> _(no submodules)_- [helpers](./helpers.md) — `items-helpers.ts` extracts the pure transformation helpers used by the items repository: row-to-item projection (with…
|
||||
- [items](./items.md) — <!-- begin:auto-docs --> _(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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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`.
|
||||
Reference in New Issue
Block a user