Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162import { HABITAT_VAULT_ROOT_COLLECTION } from '../habitat/config';import type { HabitatDoc } from '../habitat/types';import { listPrivateRecords } from '../habitat/xrpc';import type { HabitatUri, VaultConfig } from './types';
/** * Unified vault model. A vault is one of three states: * * - `local` — a local-only directory (no PDS sync). * - `synced` — a PDS-backed vault cloned into a local directory. * - `remote-unsynced` — a PDS-backed vault that exists remotely but hasn't been * cloned locally yet. * * `local`/`synced` are "mounted" (present locally, persisted in the registry). * `remote-unsynced` is discovered from the PDS and never persisted. */export interface LocalVault { state: 'local'; /** Stable id derived from the local path. */ id: string; name: string; /** Absolute filesystem path to the vault root. */ path: string;}
export interface SyncedVault { state: 'synced'; id: string; name: string; path: string; /** DID of the owning Atmosphere account. */ did: string; /** Habitat URI of the root directory record. */ rootUri: HabitatUri;}
export interface RemoteVault { state: 'remote-unsynced'; name: string; did: string; rootUri: HabitatUri;}
/** A vault present locally and persisted in the registry. */export type MountedVault = LocalVault | SyncedVault;/** Any vault the app knows about (mounted or discoverable remotely). */export type Vault = MountedVault | RemoteVault;
/** Project a synced/remote vault to the engine's input contract. */export function toVaultConfig(vault: SyncedVault | RemoteVault): VaultConfig { return { type: 'synced', rootUri: vault.rootUri };}
const STORAGE_KEY = 'textile.openVaults';
/** * Owns the catalog of vaults: the persisted set of mounted (local/synced) * vaults, plus discovery of remote vault roots from the PDS. */export class VaultRegistry { private vaults: MountedVault[] = loadPersisted(); private listeners = new Set<() => void>();
/** Vaults present locally (local + synced). */ getMounted(): MountedVault[] { return this.vaults; }
add(vault: MountedVault): void { this.vaults = [...this.vaults, vault]; this.persist(); this.emit(); }
remove(id: string): void { this.vaults = this.vaults.filter((v) => v.id !== id); this.persist(); this.emit(); }
subscribe(listener: () => void): () => void { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
/** Stable id derived from a vault's local path. */ idFromPath(path: string): string { let hash = 0; for (let i = 0; i < path.length; i++) { const char = path.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return `vault-${Math.abs(hash).toString(36)}`; }
/** * List the account's vault roots from the PDS. Vault roots live in their own * collection, so this is a single unfiltered query. */ async listRemoteRoots(did: string): Promise<RemoteVault[]> { const { records } = await listPrivateRecords<HabitatDoc>( HABITAT_VAULT_ROOT_COLLECTION, { subjects: [did] }, ); return records.map((record) => { const fallback = record.uri.split('/').filter(Boolean).pop() ?? record.uri; const name = record.value.name?.trim() ? record.value.name : fallback; return { state: 'remote-unsynced', name, did, rootUri: record.uri as HabitatUri, }; }); }
/** Remote roots not yet synced into a local directory. */ static unsynced( remote: RemoteVault[], mounted: MountedVault[], ): RemoteVault[] { const syncedRoots = new Set( mounted .filter((v): v is SyncedVault => v.state === 'synced') .map((v) => v.rootUri), ); return remote.filter((r) => !syncedRoots.has(r.rootUri)); }
private persist(): void { if (typeof localStorage === 'undefined') return; try { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.vaults)); } catch { // Storage may be unavailable. } }
private emit(): void { for (const listener of this.listeners) listener(); }}
function loadPersisted(): MountedVault[] { if (typeof localStorage === 'undefined') return []; try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? (parsed as MountedVault[]) : []; } catch { return []; }}
/** App-wide singleton. */export const vaultRegistry = new VaultRegistry();