WorkspaceRecords

Records

The Record layer every Workspace persists — reading records off a provider, the locked/unlocked distinction, value/unlock()/save(), and why a Record handle does not outlive its session.

Underneath every Workspace is a Provider, and a provider frames its storage as encrypted records. Each record is one sealed unit — the config, the network list, the asset list, the logger config, or one account — read and written through the Record<T> handle the provider hands back. This is the persistence layer most code never touches directly; it is exposed for custom-provider authors and for tools that need to work with the store below the Workspace API.

Record shape

record.ts
class Record<T> {
  readonly slug: Slug;
  readonly path: string;

  get locked(): boolean;
  get value(): T;             // throws RECORD_LOCKED while locked
  unlock(password: string): this;
  save(): Promise<void>;
}
MemberKindNotes
slugSlugThe record's key within its type.
pathstringWhere the record lives in the backing store.
lockedgettertrue until the record has been decrypted.
valuegetterThe decrypted payload of type T. Throws RECORD_LOCKED if the record is still locked.
unlock(password)methodDecrypts the record with password and returns this.
save()methodRe-encrypts the current value and writes it back through the owning provider.

Reading records off a provider

A provider exposes two read methods, both returning Record handles:

load.ts
// One record: `slug` is required for the ACCOUNTS type, optional otherwise.
const record = await provider.loadRecord<WorkspaceConfig>("CONFIG");

// Every record of a type.
const accounts = await provider.loadRecords("ACCOUNTS");
MethodReturns
loadRecord<T>(type, slug?)Promise<Record<T>>
loadRecords<T>(type)Promise<ReadonlyArray<Record<T>>>

A record that is genuinely absent is reported as a WativeError with code === "RECORD_NOT_FOUND" — that is part of the provider contract, and it is how the library tells "not there" apart from "there but unreadable".

The locked / unlocked distinction

loadRecord applies the container password eagerly, so a record it can open with that password comes back already unlockedrecord.locked is false and record.value returns the payload straight away.

A record the container password does not open comes back locked instead of throwing — a record sealed under a different password, or a damaged one. Reading .value on it throws:

locked-record.ts
const record = await provider.loadRecord("ACCOUNTS", slug);

if (record.locked) {
  // record.value here would throw RECORD_LOCKED
  record.unlock(otherPassword); // decrypt with the record's own password
}

record.value; // now safe to read

unlock() is an active credential check: a wrong password throws BAD_PASSWORD, and a record with no ciphertext to verify against throws DECRYPT_FAILED. Those two are distinguishable — "wrong password" versus "cannot be checked".

save() re-encrypts

save() seals the current value again and persists it through the owning provider, under the record's own type and slug. It refuses on a record that is still locked — there is nothing decrypted to write — throwing a WativeError with code === "RECORD_LOCKED".

save.ts
const record = await provider.loadRecord<WorkspaceConfig>("CONFIG");
// mutate record.value ...
await record.save(); // re-encrypts and writes back

A record handle does not outlive its session

A Record is bound to the container session that issued it. Session-backed providers install a liveness check that unlock() and save() consult first, so a handle you hold across a session boundary refuses rather than writing into a store it no longer belongs to:

  • After the workspace is locked (and possibly unlocked again into a new session), unlock() / save() throw WORKSPACE_LOCKED — the session that minted the handle has closed. Load the record again from the current session.
  • If the underlying record was dropped after the handle was loaded, they throw RECORD_NOT_FOUND — saving would otherwise recreate a deleted record, key material and all.

RecordType

Every record belongs to one of five types:

record-type.ts
type RecordType = "CONFIG" | "ACCOUNTS" | "NETWORK" | "ASSET" | "LOG";
TypeHolds
"CONFIG"Workspace-wide settings (see Workspace Config).
"ACCOUNTS"Per-account records — one per account, keyed by slug.
"NETWORK"The user-added network list.
"ASSET"The asset list (built-in plus imported).
"LOG"The logger configuration.

The ACCOUNTS type is the only one keyed by slug (one record per account); the other four are singletons. Absence of any singleton is re-seeded from the bundled templates when the workspace initializes.

Last updated on