/** * Habitat URI branded string. Uses at:// URIs instead of automerge: URLs. */ import type * as Automerge from '@automerge/automerge'; export type HabitatUri = string & { __brand: 'HabitatUri' }; export function isHabitatUri(value: string): value is HabitatUri { return value.startsWith('at://'); } export type VaultType = 'local' | 'synced'; /** * Minimal engine-facing view of a vault. This is the input contract for * `VaultSyncEngine` — deliberately decoupled from how the app catalogs vaults * (ids, local paths, persistence). Derive it from a `Vault` via * `toVaultConfig` (see `vault/registry.ts`). */ export interface VaultConfig { type: VaultType; rootUri: HabitatUri; } /** * Collection types for vault documents. */ export type VaultDocumentType = 'directory' | 'file'; /** * Entry in a directory document's `docs` array. * * `type` is `string` rather than `'file' | 'folder'` to stay compatible * with pushwork 2.x, which uses extension strings (`'md'`, `'png'`) for * file entries. Only `'folder'` is unambiguously a directory. */ export interface DirectoryEntry { name: string; type: string; url: HabitatUri; } /** * Directory node (in Automerge blob). Mirrors pushwork's * `PushworkDirectoryDocument` but with `HabitatUri` URLs. */ export interface DirectoryDocument { '@patchwork': { type: 'folder' }; docs: DirectoryEntry[]; name?: string; title?: string; lastSyncAt?: number; with?: string; } /** * File node (in Automerge blob). Mirrors pushwork's * `PushworkFileDocument` exactly for compatibility. */ export interface FileDocument { '@patchwork': { type: 'file' }; name: string; extension: string; mimeType: string; content: string | Uint8Array; metadata: { permissions: number }; } /** * Union type for any vault document (file or directory). */ export type VaultDocument = DirectoryDocument | FileDocument; /** * True if a DirectoryEntry points at a sub-directory document. * False for any file leaf, regardless of how the producer encoded the * file's `type` (`'file'`, `'md'`, `'png'`, etc.). */ export function isFolderEntry(entry: { type: unknown }): boolean { return entry.type === 'folder'; } /** * Convenience predicate: a file leaf. Requires `type` to actually be * a non-empty string — `undefined`, `null`, and `''` are NOT files. */ export function isFileEntry(entry: { type: unknown }): boolean { return ( typeof entry.type === 'string' && entry.type.length > 0 && entry.type !== 'folder' ); } /** * Change type classification for sync operations. */ export type ChangeType = 'add' | 'edit' | 'delete' | 'move'; /** * Represents a detected change during three-way comparison * (local filesystem ↔ snapshot ↔ remote doc). */ export interface DetectedChange { type: ChangeType; path: string; isDirectory: boolean; oldPath?: string; newPath?: string; uri?: HabitatUri; head?: string[]; contentHash?: string; } /** * Move detection result: a rename detected by content similarity. */ export interface MoveCandidate { oldPath: string; newPath: string; similarity: number; } /** * Tracked file entry in the sync snapshot. */ export interface SnapshotFileEntry { uri: HabitatUri; head: string[]; contentHash: string; } /** * Tracked directory entry in the sync snapshot. */ export interface SnapshotDirectoryEntry { uri: HabitatUri; head: string[]; } /** * Union type for snapshot entries. */ export type SnapshotEntry = SnapshotFileEntry | SnapshotDirectoryEntry; /** * Sync snapshot for local state management. * Only used for synced vaults. */ export interface SyncSnapshot { timestamp: number; rootUri: HabitatUri; files: Map; directories: Map; } /** * Serializable version of SyncSnapshot for JSON storage. */ export interface SerializableSyncSnapshot { timestamp: number; rootUri: HabitatUri; files: Array<[string, SnapshotFileEntry]>; directories: Array<[string, SnapshotDirectoryEntry]>; } /** * HabitatDocHandle mirrors the automerge-repo DocHandle API * but uses HabitatUri and is backed by AutomergeDocSession. * * This is a structural subset, not a literal implements. */ export interface HabitatDocHandle { /** The current document. */ doc(): Automerge.Doc; /** Current document heads (like a git commit). */ heads(): string[]; /** Apply a change to the document. */ change(fn: (d: T) => void): void; /** Apply a change as if the document were at the given heads. */ changeAt(heads: string[], fn: (d: T) => void): void; /** View the document at a specific point in time. */ view(heads: string[]): Automerge.Doc; /** Subscribe to local/remote changes. Returns an unsubscribe function. */ on(event: 'change', cb: () => void): () => void; } /** * HabitatRepo mirrors the automerge-repo Repo API * but uses HabitatUri and is backed by Habitat PDS + AutomergeDocSession. * * Uses separate collections for directories and files: * - network.habitat.vault.directory * - network.habitat.vault.file */ export interface HabitatRepo { /** Load an existing Habitat record into a handle. */ find(uri: HabitatUri): Promise>; /** Create a new directory document. */ createDirectory( initial: DirectoryDocument, ): Promise<{ uri: HabitatUri; handle: HabitatDocHandle }>; /** Create a new file document. */ createFile( initial: FileDocument, ): Promise<{ uri: HabitatUri; handle: HabitatDocHandle }>; /** Remove a record. */ delete(uri: HabitatUri): Promise; }