Workspace
The Workspace class — the top-level encrypted container holding accounts, networks, assets, config and a logger — with open/unlock/lock/close and the locked/closed lifecycle.
A Workspace is the top-level encrypted container. Everything else in the library hangs off it: the accounts it holds, the networks they transact on, the imported assets, the workspace-wide config, and a logger. You obtain one with Workspace.open() and drive it through a small session lifecycle — unlock, lock, close.
import { Workspace } from "wative-core";
const ws = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });
const account = await ws.accounts.create("Trading Desk", "wsp-pwd", MNEMONIC);
console.log(ws.accounts.length); // 1
await ws.lock();Lifecycle
A workspace is always in one of three states, exposed by two boolean getters:
| Getter | Type | Meaning |
|---|---|---|
ws.locked | boolean | true when there is no live session. A freshly opened no-password workspace is locked; so is one after lock(). |
ws.closed | boolean | true after close(). Terminal — the backing store has been released and cannot be reopened. Every closed workspace also reads as locked, so code that only checks locked still behaves. |
The transitions:
Workspace.open(...) returns an unlocked workspace when a password is supplied, or a locked one when it is omitted.await ws.unlock(password) starts a live session on a locked (but not closed) workspace.await ws.lock() ends the session and wipes in-memory secrets. The workspace can be unlocked again.await ws.close() ends the session and releases the provider's store. The workspace is finished.While a workspace is locked, every operation that needs a live session throws a WativeError with code === "WORKSPACE_LOCKED". Once it is closed, those same operations throw code === "UNSUPPORTED_OP" instead — a closed workspace deliberately does not tell you to unlock, because it cannot be unlocked. Open a new one.
ws.accounts, ws.networks, ws.logger
These are the primary handles on a live workspace. Each is a getter that requires an unlocked session — reading any of them while locked throws WORKSPACE_LOCKED.
| Accessor | Type | Notes |
|---|---|---|
ws.accounts | AccountCollection | The account registry. See Accounts. |
ws.networks | NetworkCollection | Built-in plus user-added networks. See Networks. |
ws.logger | Logger | Workspace-scoped logger, materialized on first read after unlock. |
ws.config | WorkspaceConfig | A frozen snapshot of workspace settings — see Workspace Config. |
ws.rootDescriptor | string | A human-readable descriptor of where this workspace lives. |
ws.damagedAccountSlugs | ReadonlyArray<string> | Slugs of account records present on disk that could not be opened (read-only). |
ws.accounts and ws.networks are read-only accessors over private backing fields. There is no setter: ws.accounts = [] throws a TypeError in strict mode rather than silently replacing the collection.
The logger auto-persists its configuration. Mutating it — setLevel, setSinks — writes the new LoggerConfig back through the provider, so it survives a lock/reopen cycle:
await ws.logger.setLevel("debug");
await ws.lock();
const reopened = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });
reopened.logger.config.minLevel; // "debug"Stop emission entirely with ws.logger.setSinks([]).
lock() vs close()
Both end the current session; the difference is what happens to the storage backend.
lock() | close() | |
|---|---|---|
| Wipes in-memory secrets (password, mnemonics, keys) | yes | yes |
| Closes logger sinks | yes | yes |
| Releases the provider claim | yes | yes |
Reopenable with unlock(password) | yes | no |
Calls Provider.close() (releases the store) | no | yes |
| State afterward | locked === true | closed === true |
For a filesystem workspace the two are nearly identical in resource terms — the default provider's close() is the same lockContainer() that lock() already runs, and file handles are released either way. close() matters for providers where locking is not enough: the browser IdbProvider.close() drops the IDBDatabase connection, and a custom provider over a pool, socket, or remote session runs its teardown there.
close() is idempotent and single-flight — concurrent calls share one teardown and all resolve when it is done. Calling lock() on an already-closed workspace is a no-op, not an error.
Re-read handles after unlock
lock() replaces the account and network collections with fresh, empty ones and wipes the previous session's state. Any reference you captured before locking is now stale, and the same is true across an idle-lock followed by unlock() — hydration builds fresh instances. Always re-read the handle from the workspace after a session boundary rather than reusing a cached reference.
let ws = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });
const hd = await ws.accounts.create("Desk One", "wsp-pwd", MNEMONIC);
await ws.lock();
ws = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });
// Re-read the account off the reopened workspace — `hd` above is stale.
const reopened = ws.accounts.bySlug(hd.slug);What a stale handle does if you do reuse it is deliberately loud, never silent:
- A cached
ws.networkscollection refuses mutations withUNSUPPORTED_OP("this networks collection is no longer live"). Re-readws.networks. - A cached account or address handle refuses operations that need key material with
ACCOUNT_LOCKED, even while the reopened workspace is unlocked and holds a fresh twin.