import type { FileCache } from '../../cache/file/mixin.ts'; import { IOResult } from '../../io/types.ts'; import { Policies } from '../../policy/index.ts'; import type { OpRecord } from '../../observe/record.ts'; import type { OpsRegistry } from '../../ops/registry.ts'; import { type Resource } from '../../resource/base.ts'; import { ConsistencyPolicy, MountMode, PathSpec } from '../../types.ts'; import type { DispatchFn } from '../../runtime/types.ts'; import type { DriftQueue } from '../snapshot/drift.ts'; import type { Namespace } from '../mount/namespace/namespace.ts'; import { Reconciler } from '../reconcile.ts'; export type ResolveFn = (path: string) => Promise<[Resource, PathSpec, MountMode]>; export declare class Dispatcher { private readonly namespace; private readonly cache; private readonly opsRegistry; private readonly policies; private readonly drift; readonly reconciler: Reconciler; constructor(namespace: Namespace, cache: FileCache & Resource, opsRegistry: OpsRegistry, consistency?: ConsistencyPolicy, policies?: Policies, drift?: DriftQueue); /** * The namespace's own answer for a path no backend serves. * * Child mounts and symlinks are structure the door owns, so a * directory that exists only because a mount or link sits below it * still lists and stats. Null for any other op, or when the * namespace knows nothing at `virtual`. */ private namespaceResult; dispatch: DispatchFn; /** * Whether the node table answers this op instead of a backend. * * `symlink` and `readlink` always, because a link exists nowhere else. * The rest only when the path itself is a link, and then for the same * reason the create and the read are the door's: forwarding reaches a * backend that has never heard of the name. A no-follow stat is the * read half of that fact (lstat asks for the link's own row, which * only the table holds); a following stat never arrives here, since * the follow below rewrote it to the target. Mirrors Python's * Dispatcher._table_answers. */ /** * The index kwargs normal dispatch stamps on every registered op, for * the door's own raw registry calls: an indexed backend cannot * resolve a nested path without it. */ private indexKwargs; /** * The door's own channel for internal walks: the TS twin of Python's * Mount.execute_op plus the dispatcher-side duties around it. The * same mode fence, index stamping and mount-prefix context normal * dispatch applies, plus the pre-ops admission for writes (Python * spells this on the channel as `_admit_cascade`) and the * dispatcher's own write invalidation, because raw registry calls * run outside the cache context dispatch establishes, so the cores' * invalidation cannot land. Invalidation runs even when the op * fails: a missing-path failure means the tree changed under the * walk, and the walk's own earlier listing is exactly the entry that * must not survive. Only the visibility filter stays off, which is * what lets a remnant walk see hidden entries. Every internal * registry call in this class routes through here; a bare * opsRegistry.call outside dispatch is a bug. */ private fencedCall; /** * Whether a rename's source stats as a directory. * * Only a directory can carry hidden content into view, so the reveal * refusal probes the source before it fires and lets a file rename * pass. An absent source moves nothing (the rename itself reports * it); a source the mount cannot classify fails toward refusal, the * same stance the pattern arm takes. Mirrors Python's * Dispatcher._moved_source_is_dir. */ private movedSourceIsDir; /** * Take a visibly-empty directory's hidden remnants with it. * * The backend refused the rmdir because entries remain, but when the * session's view of the directory is empty the refusal would leak * that something invisible exists. A session's mutation may destroy * what it cannot see, never learn of it, so the remnants go with the * directory through the shared removeRemnants walk; a visible child, * or any cascade failure (a mode-protected entry, a visible entry * appearing mid-walk), re-raises the backend's refusal. Emptiness is * the door's own readdir pipeline: backend entries merged with the * namespace's children (nested mounts, links) and judged by * visibility, so a visible child no backend can see keeps the * refusal instead of reporting a successful rmdir while the mounted * child remains. The namespace's own hidden nodes under the subtree * (links, attr overlays) are purged with it, so the removed tree * cannot resurface from the node table once the hide lifts. */ private rmdirRemnants; private tableAnswers; /** * Answer a node-table op at the door itself, gated like a backend. * * A symlink is namespace state with no backend behind it, so the door * owns every verb that names one. Admission still fires exactly as for * a backend write: the link's turf is the longest mount prefix above it * (the same ownership rule the link read filter uses), session grants * and both gates run, and the write leaves an OpRecord — a scoped * kernel mount refuses exactly like a scoped shell. The turf's mode * gates the write too (`requireTurfWritable`), so a read-only mount * or grant answers EROFS for a link exactly as for a file; a link * above every mount is bare namespace structure, gated with an empty * prefix and governed by `/` (see `lineage.ts`). A rename's * destination is judged on its own turf, since the endpoints need not * share one. Also answers the `unlink`, `rename` and no-follow * `stat` of a path the node table holds a link for. Mirrors Python's * Dispatcher._namespace_table_op. */ private namespaceTableOp; /** * The error a readlink of something that is not a link answers. * * readlink(2) splits the two misses and callers read them differently: * a path that is there but is not a link is EINVAL, and one that is * not there at all is ENOENT, which is the code a guest's * `except FileNotFoundError` catches. The node table only knows the * first half, so absence is probed here and only here, on the failure * path, where one extra round trip buys the right errno. Mirrors * Python's Dispatcher._readlink_miss. */ private readlinkMiss; /** * Whether anything at all is at `path`. * * Four channels, asked in the order of what they prove. The namespace * goes first: a link, and a directory that exists only because a * mount or a link sits below it, are structure no backend can see, * and a mount root is the deployment's own configuration. Then the * backend's row, which settles a file. A directory row settles * nothing, because an API tree synthesizes its directories: a * postgres schema lists `tables/` and `views/` before anything has * asked whether that schema is there, and a grouping mount stats * every path under a live collection as a directory. So a directory * is proven the way the hierarchy kit itself proves one, by appearing * in its parent's listing, which is also the only way a prefix store * can answer for a directory that is nothing but a set of keys. * Cannot reuse `resolvePathStat`: that dispatches, and the door is * what dispatch is inside of. Mirrors Python's * Dispatcher._path_present. */ private pathPresent; /** * Whether the path's own name is in its parent's listing. * * Compared on the final segment, because backends disagree on entry * shape: bare names, a trailing slash to mark a directory, or full * paths. The same normalization `mergeReaddir` dedupes on. Mirrors * Python's Dispatcher._listed_by_parent. */ private listedByParent; /** * Run one read op for a probe, or null when it found nothing. * * The probe reads on the caller's behalf but not at its request, so it * passes the same admission gate the op would at the door: a policy * that denies `stat` must not be reachable through a readlink. That * refusal is raised, not swallowed, because only the caller knows what * to answer when a channel goes dark. * * The index and the path's filetype are the two kwargs that decide * which registered op answers, so a probe that omitted them would ask * a different question than the door does and report a rendered path * as absent. Python needs no twin of that half: its dispatcher routes * through `Mount.execute_op`, which stamps both itself. * * Args: * opName: the op to run, `stat` or `readdir`. * resolved: what the namespace resolved the path to. * issuer: the mark on the op being served, carried to the probe's * gate so the probe is judged as its caller's. */ private probeOp; /** * Apply attributes natively where the backend can, overlay the rest. * * A resource with a registered setattr op applies what it can and * returns the residual; residual fields go to the overlay and * natively applied ones are dropped from it, so a stale overlay never * shadows a fresh backend value. A resource without the op, and a * link path (which has no backend inode), overlay everything. The * overlay half is the door's own write, so it runs inside the same * gates as the native half. Mirrors Python's Dispatcher._apply_setattr. */ private applySetattr; /** Store every requested field in the namespace overlay. */ private overlaySetattr; /** Write one overlay entry, converting an ISO mtime to epoch seconds. */ private writeOverlay; /** Drop the whole file cache (post-remote-line invalidation). */ clearFileCache(): Promise; invalidateAfterWriteByPath(rawPath: string, observed?: number | null): Promise; isCacheablePath: (path: string) => boolean; applyIo(io: IOResult, records?: readonly OpRecord[]): Promise; } //# sourceMappingURL=dispatcher.d.ts.map