import type { SqlClient, SqlParam, PgFileSystemOptions, FsStat, DirentEntry, DirentStatEntry, WalkEntry, MkdirOptions, CpOptions } from "../types.js"; import { type CompiledExcludes } from "../exclude.js"; import type { InternalEntryShape } from "./internals/entry-shapes.js"; import type { BlobRow, DirChildRow, EntryRow, SubtreeRow } from "./internals/rows.js"; export declare class FsBase { protected client: SqlClient; protected rawDb: SqlClient; readonly workspaceId: string; /** * Mutable backing for the public `version` getter. Internal code that needs * to change the instance's label after a successful commit (e.g. `renameVersion()`) * writes here. */ protected versionLabel: string; readonly permissions: { read: boolean; write: boolean; }; protected maxFileSize: number; protected maxReadSize: number | undefined; protected maxFiles: number; protected maxWorkspaceBytes: number | undefined; protected maxDepth: number; protected maxSymlinkDepth: number; protected maxCpNodes: number; protected statementTimeoutMs: number; protected embed?: (text: string) => Promise; protected embeddingDimensions?: number; protected rootDir: string; protected versionRootPath: string; protected excludes: CompiledExcludes; protected readonly baseOptions: PgFileSystemOptions; protected cachedVersionId: number | null; protected cachedVersionRootId: number | null; protected blobsHasEmbeddingCache: boolean | null; /** * Optimistic visible-node count for `validateNodeCount`. Avoids running a * `COUNT(*)` over the COW-resolved view of the whole workspace on every * write while there's still comfortable headroom under `maxFiles`. May * drift upward when entries are removed (we never decrement); the drift * is self-correcting because as the cache approaches `maxFiles` we fall * back to the real `COUNT` and reset. */ protected cachedNodeCount: number | null; /** * When non-null, this instance is a transaction-bound facade. All `withWorkspace()` * calls on the facade run `fn(txClient)` directly instead of opening a new * transaction. The outer `transaction()` call has already wired up RLS * (`app.workspace_id`) and `statement_timeout` on this client. */ protected txClient: SqlClient | null; /** * Hooks queued by tx-bound facade methods (e.g. `renameVersion()`) to apply * instance-state mutations on the originating instance only after the outer * transaction commits. Set on the facade by `transaction()`; never mutated on * a top-level instance. */ protected postCommitHooks: Array<() => void> | null; /** * The instance that produced this facade via `createTxFacade()`. Used so that * post-commit hooks (such as label updates) can target the surviving outer * instance rather than the transient facade. */ protected originInstance: this | null; constructor(options: PgFileSystemOptions); /** * The version label this instance is bound to. Backed by a mutable private field * so that operations like `renameVersion()` can update the label after commit. */ get version(): string; /** Absolute workspace path that owns this instance's version graph. */ get versionRoot(): string; /** * Open a transaction on `client`, install the per-tx workspace + timeout * settings, and run `fn`. Used by both `withWorkspace()` and `transaction()`. */ protected runInWorkspace(client: SqlClient, fn: (tx: SqlClient) => Promise): Promise; /** * Run `fn` inside a workspace-scoped transaction. If this instance is a * transaction-bound facade (i.e. `txClient` is set), reuse the open * transaction directly. Maps PostgreSQL "read-only transaction" violations * (SQLSTATE 25006) into the public `EPERM` `FsError`. */ protected withWorkspace(fn: (tx: SqlClient) => Promise): Promise; /** * Run `fn` inside a single database transaction. `fn` receives a * transaction-bound facade for the same workspace, version, permissions, * limits, and rootDir. Multiple operations on the facade share the same * transaction: if `fn` throws or rejects, every write rolls back; if `fn` * returns, the transaction commits and the return value is the * `transaction()` result. * * Re-entrant: calling `transaction()` on a facade that is already inside an * outer transaction reuses that outer transaction (no nested savepoints). * * Read-only instances still produce a read-only transaction; writes inside * `fn` raise `FsError(EPERM)`. * * The facade should not be retained after `fn` resolves — its underlying * SQL transaction has closed and further calls will fail. */ transaction(fn: (tx: this) => Promise): Promise; /** * Build a transaction-bound facade that shares this instance's configuration * and runs every operation against the supplied SQL transaction client. */ protected createTxFacade(sqlTx: SqlClient, postCommitHooks: Array<() => void>): this; protected getVersionRootId(tx: SqlClient): Promise; protected createVersionRootRecord(tx: SqlClient, internalPath: string): Promise; protected assertCanCreateVersionRoot(tx: SqlClient, internalPath: string): Promise; protected ensureVersionRootInitialized(tx: SqlClient, internalPath: string): Promise; protected getCurrentVersionId(tx: SqlClient): Promise; protected ensureVersion(tx: SqlClient): Promise; /** Resolve a label to a version ID in this workspace, or null if missing. */ protected getVersionIdByLabel(tx: SqlClient, label: string): Promise; protected requireVersionIdByLabel(tx: SqlClient, label: string): Promise; /** * Acquire transaction-scoped advisory locks for the given version IDs in * deterministic order (sorted ascending) to avoid deadlocks. Released at end * of transaction. */ protected lockVersions(tx: SqlClient, versionIds: number[]): Promise; /** * Find the lowest common ancestor (in the version graph) of `idA` and `idB`, * or `null` if they have no common ancestor. "Lowest" = smallest sum of * depths across the two ancestor chains, which is the version closest to * both endpoints. Used by `merge()` for three-way classification. */ protected findLCA(tx: SqlClient, idA: number, idB: number): Promise; /** * Count of visible (non-tombstone) entries across the entire workspace at * `versionId`. Used by batch operations to validate `maxFiles` once before * committing many writes, instead of re-checking after every write. */ protected globalVisibleCount(tx: SqlClient, versionId: number): Promise; /** * Find the visible entry at `posixPath` for the current version, walking the * version-ancestors closure to the closest ancestor that has a row at this path. * Returns null if not visible (no ancestor has a row, or the closest hit is a tombstone). */ protected resolveEntry(tx: SqlClient, posixPath: string): Promise; /** * Visibility lookup for many paths in a single round-trip. Mirrors * `resolveEntry` but unrolls over an array of requested paths, evaluates * each one's nearest-ancestor row independently, and returns a Map keyed * by the original input path. Lets hot write paths fold parent-existence * + existing-entry checks into one query. * * Single-path callers should keep using `resolveEntry`: the planner * picks a tighter plan for a literal `path = $n::ltree` predicate than * for `path = req.p` over `unnest`, and we don't want to regress reads * for the savings on writes. */ protected resolveEntries(tx: SqlClient, posixPaths: string[]): Promise>; protected resolveEntryFollowSymlink(tx: SqlClient, posixPath: string, maxDepth?: number): Promise; protected getBlob(tx: SqlClient, hash: Uint8Array): Promise; protected listVisibleChildren(tx: SqlClient, parentPosix: string): Promise; /** * Fetch every visible (non-tombstone) entry under `scopeLtree` for * `versionId`, keyed by internal POSIX path. Used by batch primitives * (merge/cherryPick/revert) to do classification entirely in TypeScript * once each side's tree is in memory. */ protected fetchVisibleEntryMap(tx: SqlClient, versionId: number, scopeLtree: string): Promise>; protected listVisibleSubtree(tx: SqlClient, rootPosix: string, includeRoot?: boolean): Promise; protected resolveLinkTargetPath(linkPath: string, target: string): string; protected validateFileSize(content: string | Uint8Array): void; protected validateNodeCount(tx: SqlClient): Promise; protected validatePathDepth(path: string): void; protected toInternalPath(userPath: string): string; protected toUserPath(internalPath: string): string; protected guardRead(userPath: string): string; protected guardWrite(userPath: string): string; /** * Build the SQL fragment that filters out excluded paths from a result set. * Returns `{ sql: "TRUE", params: [] }` when no patterns are configured. * * `pathExpr` names the ltree path column in the surrounding query * (e.g. `e.path`, `path`, `src.path`). `nextParamIdx` is the next free `$N`. */ protected buildExcludeClause(pathExpr: string, nextParamIdx: number): { sql: string; params: SqlParam[]; }; /** * Throw `ENOENT` if `internalPath` matches an exclude pattern. Used by every * public write method so that excluded paths are invisible to writers, not * merely shadowed at read time. */ protected guardExcludedWrite(internalPath: string, op: string, userPath: string): void; protected guardRootBoundary(internalPath: string): void; protected mapDirChildToDirent(row: DirChildRow): DirentEntry; protected mapDirChildToStatEntry(row: DirChildRow): DirentStatEntry; protected mapSubtreeToWalk(row: SubtreeRow): WalkEntry; protected statFromEntry(row: EntryRow): FsStat; protected upsertBlob(tx: SqlClient, hash: Uint8Array, content: string | Uint8Array, sizeBytes: number, embedding: number[] | null, userPath: string): Promise; protected validateWorkspaceBytes(tx: SqlClient, hash: Uint8Array, sizeBytes: number, userPath: string): Promise; /** * Fused blob+entry upsert for the file write hot path. Runs both INSERTs * in a single CTE so the round-trip count drops from 2 → 1; data-modifying * statements in `WITH` clauses are always evaluated by Postgres regardless * of whether the outer query references them. Used only for `node_type = * 'file'` writes; symlinks, directories, and bulk copy paths still go * through `upsertBlob` / `upsertEntry` separately because they have * different shapes (no blob, embedding-only refresh, etc.). */ protected upsertFileBlobAndEntry(tx: SqlClient, versionId: number, posixPath: string, hash: Uint8Array, content: string | Uint8Array, sizeBytes: number, embedding: number[] | null, mode: number): Promise; protected upsertEntry(tx: SqlClient, versionId: number, posixPath: string, nodeType: string, blobHash: Uint8Array | null, sizeBytes: number, mode: number, symlinkTarget: string | null): Promise; protected writeTombstone(tx: SqlClient, versionId: number, posixPath: string): Promise; protected writeTombstonesForVisibleSubtree(tx: SqlClient, versionId: number, rootPosix: string, includeRoot: boolean): Promise; protected copyVisibleSubtreeEntries(tx: SqlClient, versionId: number, srcPosix: string, destPosix: string, includeRoot: boolean): Promise; protected countVisibleSubtreeNodes(tx: SqlClient, versionId: number, rootPosix: string): Promise<{ total: number; files: number; }>; /** * Apply a pre-fetched entry shape to the destination version's `fs_entries`. * Used by batch operations (merge, cherry-pick, revert, detach) to copy * within the same workspace without rehashing content. A `null` shape writes * a tombstone. */ protected writeEntryShape(tx: SqlClient, versionId: number, posixPath: string, shape: InternalEntryShape | null): Promise; protected writeEntryShapes(tx: SqlClient, versionId: number, writes: Array<{ internalPath: string; shape: InternalEntryShape | null; }>): Promise; protected internalWriteFile(tx: SqlClient, versionId: number, path: string, content: string | Uint8Array, precomputedEmbedding?: number[] | null): Promise; protected blobsHasEmbedding(tx: SqlClient): Promise; protected maybeEmbed(tx: SqlClient, hash: Uint8Array, content: string): Promise; protected internalMkdir(tx: SqlClient, versionId: number, path: string, options?: MkdirOptions): Promise; protected internalCp(tx: SqlClient, versionId: number, src: string, dest: string, options?: CpOptions, counter?: { count: number; }): Promise; } //# sourceMappingURL=base.d.ts.map