← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/workspace-track-browser.ts
41.3 KB · Sep 30, 2026 · 23:01 UTC
import { createHash, randomUUID } from "node:crypto";
import { constants } from "node:fs";
import { lstat, open, opendir, realpath } from "node:fs/promises";
import path from "node:path";
import type { RootsRequestExtra } from "./chat-file-resource";
import { stripBiologicalCompressionSuffix } from "./compressed-file-name";
import { SEQUENCE_VIEWER_LIMITS } from "./runtime-contract";
import { isSafeWorkspaceBrowserChildName } from "./workspace-browser-protocol";
import {
type SequenceWorkspaceDirectoryIdentity,
type SequenceWorkspaceFileIdentity,
type SequenceWorkspaceSourceBinding,
SequenceWorkspaceExportPublisher,
} from "./workspace-export-publisher";
import type {
SequenceListWorkspaceTrackDirectoryInput,
SequenceListWorkspaceTrackDirectoryResult,
SequenceResolveWorkspaceTrackBundleInput,
SequenceResolveWorkspaceTrackBundleResult,
SequenceWorkspaceTrackCandidate,
SequenceWorkspaceTrackFormat,
SequenceWorkspaceTrackRole,
} from "./workspace-track-protocol";
type MutableDirectoryIdentity = SequenceWorkspaceDirectoryIdentity & {
changedAtNanoseconds: string;
modifiedAtNanoseconds: string;
};
type DirectoryCandidateBinding = {
bindingId: string;
candidateId: string;
expiresAt: number;
identity: SequenceWorkspaceDirectoryIdentity;
kind: "directory";
label: string;
path: string;
sessionId: string;
workspacePath: string;
};
type FileCandidateBinding = {
bindingId: string;
candidateId: string;
expiresAt: number;
format: SequenceWorkspaceTrackFormat;
identity: SequenceWorkspaceFileIdentity;
kind: "file";
label: string;
path: string;
role: SequenceWorkspaceTrackRole;
sessionId: string;
size: number;
workspacePath: string;
};
type CandidateBinding = DirectoryCandidateBinding | FileCandidateBinding;
type BundleBinding = {
bindingId: string;
bundleId: string;
expiresAt: number;
index?: FileCandidateBinding;
primary: FileCandidateBinding;
reference?: FileCandidateBinding;
sessionId: string;
};
type SnapshotEntry =
| {
identity: SequenceWorkspaceDirectoryIdentity;
kind: "directory";
label: string;
path: string;
workspacePath: string;
}
| {
format: SequenceWorkspaceTrackFormat;
identity: SequenceWorkspaceFileIdentity;
kind: "file";
label: string;
path: string;
role: SequenceWorkspaceTrackRole;
size: number;
workspacePath: string;
};
type DirectorySnapshot = {
entries: Array<SnapshotEntry>;
fingerprint: string;
omittedEntries: number;
};
export type ResolvedSequenceWorkspaceTrackBundle = {
format: Exclude<
SequenceWorkspaceTrackFormat,
"bai" | "crai" | "csi" | "fasta"
>;
index?: {
bytes: Uint8Array;
format: "bai" | "crai" | "csi";
name: string;
sha256: string;
};
primary: {
bytes: Uint8Array;
name: string;
sha256: string;
workspacePath: string;
};
reference?: {
bytes: Uint8Array;
name: string;
sha256: string;
};
};
export type SequenceWorkspaceTrackBrowserOptions = {
now?: () => number;
};
export function safeSequenceWorkspaceTrackError(error: unknown): Error {
if (isAbortError(error)) return error;
if (
error instanceof Error &&
!(error instanceof AggregateError) &&
!("code" in error)
) {
return error;
}
return new Error(
"Workspace track browsing could not safely access the source or candidate.",
);
}
export class SequenceWorkspaceTrackBrowser {
private readonly bundles = new Map<string, BundleBinding>();
private readonly candidates = new Map<string, CandidateBinding>();
private readonly now: () => number;
constructor(
private readonly sourceBindings: SequenceWorkspaceExportPublisher,
{ now = Date.now }: SequenceWorkspaceTrackBrowserOptions = {},
) {
this.now = now;
}
async listDirectory(
input: SequenceListWorkspaceTrackDirectoryInput,
extra?: RootsRequestExtra,
): Promise<SequenceListWorkspaceTrackDirectoryResult> {
throwIfAborted(extra?.signal);
const binding = await this.sourceBindings.getActiveSourceBinding(
input.sessionId,
extra,
);
const directory =
input.directoryCandidateId == null
? await this.bindDirectory(
binding,
input.sessionId,
path.dirname(binding.sourcePath),
)
: await this.requireDirectoryCandidate(
input.directoryCandidateId,
input.sessionId,
binding,
);
const snapshot = await readTrackDirectorySnapshot(binding, directory.path);
const offset = parseDirectoryCursor(
input.cursor,
{
bindingId: binding.bindingId,
directoryWorkspacePath: directory.workspacePath,
fingerprint: snapshot.fingerprint,
},
snapshot.entries.length,
);
const page = snapshot.entries.slice(offset, offset + input.limit);
const entries = await Promise.all(
page.map(async (entry) => {
const candidate = this.rememberCandidate(
candidateFromSnapshot(
entry,
binding,
input.sessionId,
this.expiresAt(),
),
);
if (candidate.kind === "directory") {
return {
candidateId: candidate.candidateId,
kind: "directory" as const,
label: candidate.label,
workspacePath: candidate.workspacePath,
};
}
return {
...publicFileCandidate(candidate),
bundle: bundleSummary(candidate, snapshot, binding),
};
}),
);
const breadcrumbs = await this.createBreadcrumbs(
binding,
input.sessionId,
directory.path,
);
throwIfAborted(extra?.signal);
await this.assertBindingAndDirectoryCurrent(binding, directory, extra);
const nextOffset = offset + entries.length;
return {
breadcrumbs,
directory: breadcrumbs.at(-1)!,
entries,
nextCursor:
nextOffset < snapshot.entries.length
? createDirectoryCursor({
bindingId: binding.bindingId,
directoryWorkspacePath: directory.workspacePath,
fingerprint: snapshot.fingerprint,
offset: nextOffset,
})
: undefined,
omittedEntries: snapshot.omittedEntries,
};
}
async resolveBundle(
input: SequenceResolveWorkspaceTrackBundleInput,
extra?: RootsRequestExtra,
): Promise<SequenceResolveWorkspaceTrackBundleResult> {
throwIfAborted(extra?.signal);
const binding = await this.sourceBindings.getActiveSourceBinding(
input.sessionId,
extra,
);
const primary = await this.requireFileCandidate(
input.primaryCandidateId,
input.sessionId,
binding,
);
if (!isPrimaryFormat(primary.format)) {
throw new Error(
"Choose a supported evidence or read file as the primary track.",
);
}
const explicitIndex =
input.indexCandidateId == null
? undefined
: await this.requireFileCandidate(
input.indexCandidateId,
input.sessionId,
binding,
);
const explicitReference =
input.referenceCandidateId == null
? undefined
: await this.requireFileCandidate(
input.referenceCandidateId,
input.sessionId,
binding,
);
if (
explicitIndex != null &&
primary.format !== "bam" &&
primary.format !== "cram"
) {
throw new Error("Only BAM and CRAM tracks accept an index companion.");
}
if (explicitReference != null && primary.format !== "cram") {
throw new Error("Only CRAM tracks accept a FASTA reference companion.");
}
const snapshot = await readTrackDirectorySnapshot(
binding,
path.dirname(primary.path),
);
const requirements: SequenceResolveWorkspaceTrackBundleResult["requirements"] =
[];
let selectedIndex: FileCandidateBinding | undefined;
let selectedReference: FileCandidateBinding | undefined;
if (primary.format === "bam" || primary.format === "cram") {
const primaryFormat = primary.format;
const compatibleIndexFormats =
primaryFormat === "bam" ? new Set(["bai", "csi"]) : new Set(["crai"]);
if (
explicitIndex != null &&
(explicitIndex.role !== "index" ||
!compatibleIndexFormats.has(explicitIndex.format))
) {
throw new Error(
`The selected index is not compatible with ${primary.format.toUpperCase()}.`,
);
}
const automaticIndexEntries = snapshot.entries.filter(
(entry): entry is Extract<SnapshotEntry, { kind: "file" }> =>
entry.kind === "file" &&
entry.role === "index" &&
compatibleIndexFormats.has(entry.format) &&
isNamedIndexCompanion(primary.label, entry.label, primaryFormat),
);
const automaticIndexes = automaticIndexEntries
.slice(0, SEQUENCE_VIEWER_LIMITS.workspace.maxBundleCandidates)
.map(
(entry) =>
this.rememberCandidate(
candidateFromSnapshot(
entry,
binding,
input.sessionId,
this.expiresAt(),
),
) as FileCandidateBinding,
);
const indexRequirement = createRequirement({
automaticCandidateCount: automaticIndexEntries.length,
automaticCandidates: automaticIndexes,
explicitCandidate: explicitIndex,
missingMessage: `${primary.label} needs a matching ${
primary.format === "bam" ? "BAI or CSI" : "CRAI"
} index. Browse to an index and select it explicitly.`,
role: "index",
});
requirements.push(indexRequirement.result);
selectedIndex = indexRequirement.selected;
}
if (primary.format === "cram") {
if (
explicitReference != null &&
(explicitReference.role !== "reference" ||
explicitReference.format !== "fasta")
) {
throw new Error("The selected CRAM reference is not a FASTA file.");
}
const referenceEntries = snapshot.entries.filter(
(entry): entry is Extract<SnapshotEntry, { kind: "file" }> =>
entry.kind === "file" && entry.format === "fasta",
);
const referenceCandidates = referenceEntries
.slice(0, SEQUENCE_VIEWER_LIMITS.workspace.maxBundleCandidates)
.map(
(entry) =>
this.rememberCandidate(
candidateFromSnapshot(
entry,
binding,
input.sessionId,
this.expiresAt(),
),
) as FileCandidateBinding,
);
const sourceInference = inferWorkspaceTrackFile(
path.basename(binding.sourcePath),
);
if (sourceInference?.format === "fasta") {
const sourceCandidate = await this.bindFile(
binding,
input.sessionId,
binding.sourcePath,
sourceInference,
);
if (
!referenceCandidates.some(({ path: candidatePath }) =>
sameCanonicalPath(candidatePath, sourceCandidate.path),
)
) {
referenceCandidates.push(sourceCandidate);
}
}
const automaticReferenceCount =
referenceEntries.length +
(sourceInference?.format === "fasta" &&
!referenceEntries.some(({ path: candidatePath }) =>
sameCanonicalPath(candidatePath, binding.sourcePath),
)
? 1
: 0);
const referenceRequirement = createRequirement({
automaticCandidateCount: automaticReferenceCount,
automaticCandidates: deduplicateCandidates(referenceCandidates),
explicitCandidate: explicitReference,
missingMessage: `${primary.label} needs a matching FASTA reference. Browse to a reference and select it explicitly.`,
role: "reference",
});
requirements.push(referenceRequirement.result);
selectedReference = referenceRequirement.selected;
}
const selected = [primary, selectedIndex, selectedReference].filter(
(candidate): candidate is FileCandidateBinding => candidate != null,
);
assertNoAliases(selected);
throwIfAborted(extra?.signal);
await Promise.all(
selected.map((candidate) =>
this.assertFileCandidateCurrent(candidate, binding),
),
);
await this.sourceBindings.getActiveSourceBinding(input.sessionId, extra);
const ready = requirements.every(({ status }) => status === "selected");
const bundleId = ready
? this.rememberBundle({
bindingId: binding.bindingId,
bundleId: randomUUID(),
expiresAt: this.expiresAt(),
index: selectedIndex,
primary,
reference: selectedReference,
sessionId: input.sessionId,
}).bundleId
: undefined;
return {
bundleId,
primary: publicFileCandidate(primary),
ready,
requirements,
};
}
async readBundle(
bundleId: string,
sessionId: string,
signal?: AbortSignal,
extra?: RootsRequestExtra,
): Promise<ResolvedSequenceWorkspaceTrackBundle> {
throwIfAborted(signal);
this.pruneExpired();
const bundle = this.bundles.get(bundleId);
if (
bundle == null ||
bundle.sessionId !== sessionId ||
bundle.expiresAt <= this.now()
) {
throw new Error(
"The workspace track bundle expired or belongs to another viewer. Resolve it again.",
);
}
const binding = await this.sourceBindings.getActiveSourceBinding(
sessionId,
extra,
);
if (binding.bindingId !== bundle.bindingId) {
throw new Error(
"The workspace source changed after the track bundle was resolved.",
);
}
const candidates = [bundle.primary, bundle.index, bundle.reference].filter(
(candidate): candidate is FileCandidateBinding => candidate != null,
);
assertNoAliases(candidates);
await Promise.all(
candidates.map((candidate) =>
this.assertFileCandidateCurrent(candidate, binding),
),
);
const [primary, index, reference] = await Promise.all([
readBoundCandidate(bundle.primary, 100 * 1_024 * 1_024, signal),
bundle.index == null
? undefined
: readBoundCandidate(
bundle.index,
bundle.index.format === "crai"
? 16 * 1_024 * 1_024
: 64 * 1_024 * 1_024,
signal,
),
bundle.reference == null
? undefined
: readBoundCandidate(bundle.reference, 100 * 1_024 * 1_024, signal),
]);
throwIfAborted(signal);
await Promise.all(
candidates.map((candidate) =>
this.assertFileCandidateCurrent(candidate, binding),
),
);
await this.sourceBindings.getActiveSourceBinding(sessionId, extra);
return {
format: bundle.primary
.format as ResolvedSequenceWorkspaceTrackBundle["format"],
index:
bundle.index == null || index == null
? undefined
: {
bytes: index,
format: bundle.index.format as "bai" | "crai" | "csi",
name: bundle.index.label,
sha256: sha256(index),
},
primary: {
bytes: primary,
name: bundle.primary.label,
sha256: sha256(primary),
workspacePath: bundle.primary.workspacePath,
},
reference:
bundle.reference == null || reference == null
? undefined
: {
bytes: reference,
name: bundle.reference.label,
sha256: sha256(reference),
},
};
}
private async bindDirectory(
binding: SequenceWorkspaceSourceBinding,
sessionId: string,
directoryPath: string,
): Promise<DirectoryCandidateBinding> {
const canonical = await realpath(directoryPath).catch(() => null);
if (
canonical == null ||
!sameCanonicalPath(canonical, directoryPath) ||
!isPathWithin(binding.rootPath, canonical)
) {
throw new Error(
"The workspace track directory is no longer a real directory inside the active root.",
);
}
await assertNoSymlinkComponents(binding.rootPath, canonical);
const identity = await readDirectoryIdentity(canonical);
return this.rememberCandidate({
bindingId: binding.bindingId,
candidateId: randomUUID(),
expiresAt: this.expiresAt(),
identity,
kind: "directory",
label: sameCanonicalPath(canonical, binding.rootPath)
? "Workspace"
: path.basename(canonical),
path: canonical,
sessionId,
workspacePath: workspaceLabel(path.relative(binding.rootPath, canonical)),
}) as DirectoryCandidateBinding;
}
private async bindFile(
binding: SequenceWorkspaceSourceBinding,
sessionId: string,
filePath: string,
inference: {
format: SequenceWorkspaceTrackFormat;
role: SequenceWorkspaceTrackRole;
},
): Promise<FileCandidateBinding> {
const canonical = await realpath(filePath).catch(() => null);
if (
canonical == null ||
!sameCanonicalPath(canonical, filePath) ||
!isPathWithin(binding.rootPath, canonical)
) {
throw new Error(
"The workspace track candidate is no longer a real file inside the active root.",
);
}
await assertNoSymlinkComponents(binding.rootPath, path.dirname(canonical));
const identity = await readFileIdentity(canonical);
const size = safeFileSize(identity);
return this.rememberCandidate({
bindingId: binding.bindingId,
candidateId: randomUUID(),
expiresAt: this.expiresAt(),
format: inference.format,
identity,
kind: "file",
label: path.basename(canonical),
path: canonical,
role: inference.role,
sessionId,
size,
workspacePath: workspaceLabel(path.relative(binding.rootPath, canonical)),
}) as FileCandidateBinding;
}
private async requireDirectoryCandidate(
candidateId: string,
sessionId: string,
binding: SequenceWorkspaceSourceBinding,
): Promise<DirectoryCandidateBinding> {
const candidate = this.requireCandidate(candidateId, sessionId, binding);
if (candidate.kind !== "directory") {
throw new Error("The selected workspace candidate is not a directory.");
}
await this.assertDirectoryCandidateCurrent(candidate, binding);
return candidate;
}
private async requireFileCandidate(
candidateId: string,
sessionId: string,
binding: SequenceWorkspaceSourceBinding,
): Promise<FileCandidateBinding> {
const candidate = this.requireCandidate(candidateId, sessionId, binding);
if (candidate.kind !== "file") {
throw new Error(
"The selected workspace candidate is not a regular file.",
);
}
await this.assertFileCandidateCurrent(candidate, binding);
return candidate;
}
private requireCandidate(
candidateId: string,
sessionId: string,
binding: SequenceWorkspaceSourceBinding,
): CandidateBinding {
this.pruneExpired();
const candidate = this.candidates.get(candidateId);
if (
candidate == null ||
candidate.sessionId !== sessionId ||
candidate.bindingId !== binding.bindingId ||
candidate.expiresAt <= this.now()
) {
throw new Error(
"The workspace candidate expired or belongs to another viewer. Refresh the listing.",
);
}
candidate.expiresAt = this.expiresAt();
return candidate;
}
private async assertBindingAndDirectoryCurrent(
binding: SequenceWorkspaceSourceBinding,
directory: DirectoryCandidateBinding,
extra?: RootsRequestExtra,
): Promise<void> {
const current = await this.sourceBindings.getActiveSourceBinding(
directory.sessionId,
extra,
);
if (current.bindingId !== binding.bindingId) {
throw new Error(
"The workspace source changed while the directory was listed.",
);
}
await this.assertDirectoryCandidateCurrent(directory, current);
}
private async assertDirectoryCandidateCurrent(
candidate: DirectoryCandidateBinding,
binding: SequenceWorkspaceSourceBinding,
): Promise<void> {
if (!isPathWithin(binding.rootPath, candidate.path)) {
throw new Error("The workspace directory left the active root.");
}
const canonical = await realpath(candidate.path).catch(() => null);
if (canonical == null || !sameCanonicalPath(canonical, candidate.path)) {
throw new Error("The workspace directory changed after it was listed.");
}
await assertNoSymlinkComponents(binding.rootPath, candidate.path);
const current = await readDirectoryIdentity(candidate.path);
if (!sameDirectoryIdentity(current, candidate.identity)) {
throw new Error("The workspace directory changed after it was listed.");
}
}
private async assertFileCandidateCurrent(
candidate: FileCandidateBinding,
binding: SequenceWorkspaceSourceBinding,
): Promise<void> {
if (!isPathWithin(binding.rootPath, candidate.path)) {
throw new Error("The workspace track candidate left the active root.");
}
const canonical = await realpath(candidate.path).catch(() => null);
if (canonical == null || !sameCanonicalPath(canonical, candidate.path)) {
throw new Error(
"The workspace track candidate changed after it was listed.",
);
}
await assertNoSymlinkComponents(
binding.rootPath,
path.dirname(candidate.path),
);
const current = await readFileIdentity(candidate.path);
if (!sameFileIdentity(current, candidate.identity)) {
throw new Error(
"The workspace track candidate changed after it was listed.",
);
}
if (
!sameCanonicalPath(candidate.path, binding.sourcePath) &&
sameUnderlyingFile(candidate.identity, binding.sourceIdentity)
) {
throw new Error(
"The workspace track candidate aliases the opened source.",
);
}
}
private async createBreadcrumbs(
binding: SequenceWorkspaceSourceBinding,
sessionId: string,
directoryPath: string,
): Promise<SequenceListWorkspaceTrackDirectoryResult["breadcrumbs"]> {
const paths = [binding.rootPath];
const relative = path.relative(binding.rootPath, directoryPath);
let current = binding.rootPath;
if (relative !== "") {
for (const component of relative.split(path.sep)) {
current = path.join(current, component);
paths.push(current);
}
}
return await Promise.all(
paths.map(async (breadcrumbPath, index) => {
const candidate = await this.bindDirectory(
binding,
sessionId,
breadcrumbPath,
);
return {
candidateId: candidate.candidateId,
label: index === 0 ? "Workspace" : candidate.label,
workspacePath: candidate.workspacePath,
};
}),
);
}
private rememberCandidate(candidate: CandidateBinding): CandidateBinding {
this.pruneExpired();
while (
this.candidates.size >=
SEQUENCE_VIEWER_LIMITS.workspace.maxCandidateTokens
) {
const oldest = this.candidates.keys().next().value as string | undefined;
if (oldest == null) break;
this.candidates.delete(oldest);
}
this.candidates.set(candidate.candidateId, candidate);
return candidate;
}
private rememberBundle(bundle: BundleBinding): BundleBinding {
this.pruneExpired();
while (
this.bundles.size >= SEQUENCE_VIEWER_LIMITS.workspace.maxBundleTokens
) {
const oldest = this.bundles.keys().next().value as string | undefined;
if (oldest == null) break;
this.bundles.delete(oldest);
}
this.bundles.set(bundle.bundleId, bundle);
return bundle;
}
private pruneExpired(): void {
const now = this.now();
for (const [candidateId, candidate] of this.candidates) {
if (candidate.expiresAt <= now) this.candidates.delete(candidateId);
}
for (const [bundleId, bundle] of this.bundles) {
if (bundle.expiresAt <= now) this.bundles.delete(bundleId);
}
}
private expiresAt(): number {
return this.now() + SEQUENCE_VIEWER_LIMITS.workspace.tokenTtlMs;
}
}
function candidateFromSnapshot(
entry: SnapshotEntry,
binding: SequenceWorkspaceSourceBinding,
sessionId: string,
expiresAt: number,
): CandidateBinding {
if (entry.kind === "directory") {
return {
bindingId: binding.bindingId,
candidateId: randomUUID(),
expiresAt,
identity: entry.identity,
kind: "directory",
label: entry.label,
path: entry.path,
sessionId,
workspacePath: entry.workspacePath,
};
}
return {
bindingId: binding.bindingId,
candidateId: randomUUID(),
expiresAt,
format: entry.format,
identity: entry.identity,
kind: "file",
label: entry.label,
path: entry.path,
role: entry.role,
sessionId,
size: entry.size,
workspacePath: entry.workspacePath,
};
}
function publicFileCandidate(
candidate: FileCandidateBinding,
): SequenceWorkspaceTrackCandidate {
return {
candidateId: candidate.candidateId,
format: candidate.format,
kind: "file",
label: candidate.label,
role: candidate.role,
size: candidate.size,
workspacePath: candidate.workspacePath,
};
}
function createRequirement({
automaticCandidateCount,
automaticCandidates,
explicitCandidate,
missingMessage,
role,
}: {
automaticCandidateCount?: number;
automaticCandidates: Array<FileCandidateBinding>;
explicitCandidate?: FileCandidateBinding;
missingMessage: string;
role: "index" | "reference";
}): {
result: SequenceResolveWorkspaceTrackBundleResult["requirements"][number];
selected?: FileCandidateBinding;
} {
const candidates = deduplicateCandidates(automaticCandidates);
const candidateCount = automaticCandidateCount ?? candidates.length;
const selected =
explicitCandidate ?? (candidateCount === 1 ? candidates[0] : undefined);
const status =
selected != null
? "selected"
: candidateCount === 0
? "missing"
: "ambiguous";
const visibleCandidates = candidates.slice(
0,
SEQUENCE_VIEWER_LIMITS.workspace.maxBundleCandidates,
);
const message =
status === "selected"
? `Selected ${selected!.label} as the ${role}.`
: status === "ambiguous"
? `More than one ${role} candidate matched. Select one explicitly.`
: missingMessage;
return {
result: {
candidates: visibleCandidates.map(publicFileCandidate),
message,
omittedCandidates: candidateCount - visibleCandidates.length,
role,
selectedCandidate:
selected == null ? undefined : publicFileCandidate(selected),
status,
},
selected,
};
}
function bundleSummary(
primary: FileCandidateBinding,
snapshot: DirectorySnapshot,
binding: SequenceWorkspaceSourceBinding,
): SequenceListWorkspaceTrackDirectoryResult["entries"][number] extends infer _Entry
?
| {
indexMatches: number;
indexStatus: "ambiguous" | "missing" | "not-required" | "ready";
referenceMatches: number;
referenceStatus: "ambiguous" | "missing" | "not-required" | "ready";
}
| undefined
: never {
if (primary.format !== "bam" && primary.format !== "cram") return undefined;
const primaryFormat = primary.format;
const indexMatches = snapshot.entries.filter(
(entry) =>
entry.kind === "file" &&
entry.role === "index" &&
isNamedIndexCompanion(primary.label, entry.label, primaryFormat),
).length;
const sourceIsReference =
inferWorkspaceTrackFile(path.basename(binding.sourcePath))?.format ===
"fasta";
const referencePaths = new Set(
snapshot.entries.flatMap((entry) =>
entry.kind === "file" && entry.format === "fasta" ? [entry.path] : [],
),
);
if (sourceIsReference) referencePaths.add(binding.sourcePath);
const referenceMatches = primary.format === "cram" ? referencePaths.size : 0;
return {
indexMatches,
indexStatus: countStatus(indexMatches),
referenceMatches,
referenceStatus:
primary.format === "cram"
? countStatus(referenceMatches)
: "not-required",
};
}
function countStatus(count: number): "ambiguous" | "missing" | "ready" {
return count === 0 ? "missing" : count === 1 ? "ready" : "ambiguous";
}
async function readTrackDirectorySnapshot(
binding: SequenceWorkspaceSourceBinding,
directoryPath: string,
): Promise<DirectorySnapshot> {
const before = await readMutableDirectoryIdentity(directoryPath);
const directory = await opendir(directoryPath);
const entries: Array<SnapshotEntry> = [];
let observedEntries = 0;
let omittedEntries = 0;
try {
for await (const entry of directory) {
observedEntries += 1;
if (
observedEntries > SEQUENCE_VIEWER_LIMITS.workspace.maxDirectoryEntries
) {
throw new Error(
`This directory contains more than ${SEQUENCE_VIEWER_LIMITS.workspace.maxDirectoryEntries.toLocaleString()} entries and cannot be browsed safely.`,
);
}
if (!isSafeWorkspaceBrowserChildName(entry.name)) {
omittedEntries += 1;
continue;
}
const entryPath = path.join(directoryPath, entry.name);
const fileStat = await lstat(entryPath, { bigint: true }).catch(
() => null,
);
if (fileStat == null || fileStat.isSymbolicLink()) {
omittedEntries += 1;
continue;
}
const workspacePath = workspaceLabel(
path.relative(binding.rootPath, entryPath),
);
if (fileStat.isDirectory()) {
entries.push({
identity: directoryIdentityFromStat(fileStat),
kind: "directory",
label: entry.name,
path: entryPath,
workspacePath,
});
continue;
}
if (!fileStat.isFile()) {
omittedEntries += 1;
continue;
}
const inference = inferWorkspaceTrackFile(entry.name);
if (inference == null) {
omittedEntries += 1;
continue;
}
const identity = fileIdentityFromStat(fileStat);
if (
!sameCanonicalPath(entryPath, binding.sourcePath) &&
sameUnderlyingFile(identity, binding.sourceIdentity)
) {
omittedEntries += 1;
continue;
}
entries.push({
...inference,
identity,
kind: "file",
label: entry.name,
path: entryPath,
size: safeFileSize(identity),
workspacePath,
});
}
} finally {
await directory.close().catch(() => undefined);
}
const after = await readMutableDirectoryIdentity(directoryPath);
if (!sameMutableDirectoryIdentity(before, after)) {
throw new Error(
"The workspace directory changed while it was being listed.",
);
}
entries.sort((left, right) =>
left.label === right.label ? 0 : left.label < right.label ? -1 : 1,
);
return {
entries,
fingerprint: sha256(
Buffer.from(
JSON.stringify(
entries.map(({ identity, kind, label, ...entry }) => ({
...entry,
identity,
kind,
label,
})),
),
),
),
omittedEntries,
};
}
export function inferWorkspaceTrackFile(fileName: string):
| {
format: SequenceWorkspaceTrackFormat;
role: SequenceWorkspaceTrackRole;
}
| undefined {
const lower = fileName.toLowerCase();
const textName = stripBiologicalCompressionSuffix(lower);
if (/\.(?:gff|gff3)$/u.test(textName)) {
return { format: "gff3", role: "annotation" };
}
if (textName.endsWith(".gtf")) return { format: "gtf", role: "annotation" };
if (textName.endsWith(".bed")) return { format: "bed", role: "annotation" };
if (textName.endsWith(".vcf")) return { format: "vcf", role: "variant" };
if (textName.endsWith(".sam")) return { format: "sam", role: "reads" };
if (lower.endsWith(".bam")) return { format: "bam", role: "reads" };
if (lower.endsWith(".cram")) return { format: "cram", role: "reads" };
if (lower.endsWith(".bai")) return { format: "bai", role: "index" };
if (lower.endsWith(".csi")) return { format: "csi", role: "index" };
if (lower.endsWith(".crai")) return { format: "crai", role: "index" };
if (/\.(?:fa|fas|fasta|fna)$/u.test(textName)) {
return { format: "fasta", role: "reference" };
}
return undefined;
}
function isPrimaryFormat(
format: SequenceWorkspaceTrackFormat,
): format is Exclude<
SequenceWorkspaceTrackFormat,
"bai" | "crai" | "csi" | "fasta"
> {
return (
format !== "bai" &&
format !== "crai" &&
format !== "csi" &&
format !== "fasta"
);
}
function isNamedIndexCompanion(
primaryName: string,
indexName: string,
format: "bam" | "cram",
): boolean {
const primary = primaryName.toLowerCase();
const index = indexName.toLowerCase();
const stem = primary.endsWith(`.${format}`)
? primary.slice(0, -1 * `.${format}`.length)
: primary;
const extensions = format === "bam" ? ["bai", "csi"] : ["crai"];
return extensions.some(
(extension) =>
index === `${primary}.${extension}` || index === `${stem}.${extension}`,
);
}
async function readBoundCandidate(
candidate: FileCandidateBinding,
maxBytes: number,
signal?: AbortSignal,
): Promise<Uint8Array> {
if (candidate.size > maxBytes) {
throw new Error(
`${candidate.label} exceeds the bounded ${(
maxBytes /
(1_024 * 1_024)
).toLocaleString()} MiB input limit.`,
);
}
throwIfAborted(signal);
const handle = await open(
candidate.path,
constants.O_RDONLY | constants.O_NOFOLLOW,
);
try {
const before = fileIdentityFromStat(await handle.stat({ bigint: true }));
if (!sameFileIdentity(before, candidate.identity)) {
throw new Error(`${candidate.label} changed before it could be read.`);
}
const bytes = Buffer.alloc(candidate.size);
let offset = 0;
while (offset < bytes.byteLength) {
throwIfAborted(signal);
const { bytesRead } = await handle.read(
bytes,
offset,
bytes.byteLength - offset,
offset,
);
if (bytesRead === 0) {
throw new Error(`${candidate.label} changed while it was being read.`);
}
offset += bytesRead;
}
const overflow = Buffer.alloc(1);
const { bytesRead: overflowBytes } = await handle.read(
overflow,
0,
1,
offset,
);
if (overflowBytes !== 0) {
throw new Error(`${candidate.label} changed while it was being read.`);
}
throwIfAborted(signal);
const after = fileIdentityFromStat(await handle.stat({ bigint: true }));
const pathIdentity = await readFileIdentity(candidate.path);
if (
!sameFileIdentity(before, after) ||
!sameFileIdentity(after, pathIdentity)
) {
throw new Error(`${candidate.label} changed while it was being read.`);
}
return bytes;
} finally {
await handle.close();
}
}
function assertNoAliases(candidates: Array<FileCandidateBinding>): void {
for (let index = 0; index < candidates.length; index += 1) {
for (let other = index + 1; other < candidates.length; other += 1) {
const left = candidates[index]!;
const right = candidates[other]!;
if (
!sameCanonicalPath(left.path, right.path) &&
sameUnderlyingFile(left.identity, right.identity)
) {
throw new Error(
"Workspace track bundle candidates cannot alias one another.",
);
}
}
}
}
function deduplicateCandidates(
candidates: Array<FileCandidateBinding>,
): Array<FileCandidateBinding> {
const seen = new Set<string>();
return candidates.filter((candidate) => {
const key = `${candidate.identity.device}:${candidate.identity.inode}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
type DirectoryCursor = {
bindingId: string;
directoryWorkspacePath: string;
fingerprint: string;
offset: number;
};
function createDirectoryCursor(cursor: DirectoryCursor): string {
return Buffer.from(JSON.stringify(cursor)).toString("base64url");
}
function parseDirectoryCursor(
cursor: string | undefined,
expected: Omit<DirectoryCursor, "offset">,
entryCount: number,
): number {
if (cursor == null) return 0;
try {
const parsed = JSON.parse(
Buffer.from(cursor, "base64url").toString("utf8"),
) as Partial<DirectoryCursor>;
if (
parsed.bindingId !== expected.bindingId ||
parsed.directoryWorkspacePath !== expected.directoryWorkspacePath ||
parsed.fingerprint !== expected.fingerprint ||
!Number.isInteger(parsed.offset) ||
(parsed.offset as number) < 0 ||
(parsed.offset as number) >= entryCount
) {
throw new Error("Invalid cursor.");
}
return parsed.offset as number;
} catch {
throw new Error(
"The workspace track cursor is stale or invalid. Refresh the listing.",
);
}
}
async function readFileIdentity(
filePath: string,
): Promise<SequenceWorkspaceFileIdentity> {
const fileStat = await lstat(filePath, { bigint: true });
if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
throw new Error(
"The workspace track candidate is not a stable regular file.",
);
}
return fileIdentityFromStat(fileStat);
}
function fileIdentityFromStat(fileStat: {
ctimeNs: bigint;
dev: bigint;
ino: bigint;
mtimeNs: bigint;
nlink: bigint;
size: bigint;
}): SequenceWorkspaceFileIdentity {
return {
changedAtNanoseconds: fileStat.ctimeNs.toString(),
device: fileStat.dev.toString(),
inode: fileStat.ino.toString(),
links: fileStat.nlink.toString(),
modifiedAtNanoseconds: fileStat.mtimeNs.toString(),
size: fileStat.size.toString(),
};
}
async function readDirectoryIdentity(
directoryPath: string,
): Promise<SequenceWorkspaceDirectoryIdentity> {
const directoryStat = await lstat(directoryPath, { bigint: true });
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
throw new Error(
"The workspace track browser target is not a real directory.",
);
}
return directoryIdentityFromStat(directoryStat);
}
function directoryIdentityFromStat(fileStat: {
dev: bigint;
ino: bigint;
}): SequenceWorkspaceDirectoryIdentity {
return {
device: fileStat.dev.toString(),
inode: fileStat.ino.toString(),
};
}
async function readMutableDirectoryIdentity(
directoryPath: string,
): Promise<MutableDirectoryIdentity> {
const fileStat = await lstat(directoryPath, { bigint: true });
if (fileStat.isSymbolicLink() || !fileStat.isDirectory()) {
throw new Error(
"The workspace track browser target is not a real directory.",
);
}
return {
...directoryIdentityFromStat(fileStat),
changedAtNanoseconds: fileStat.ctimeNs.toString(),
modifiedAtNanoseconds: fileStat.mtimeNs.toString(),
};
}
function safeFileSize(identity: SequenceWorkspaceFileIdentity): number {
const size = Number(identity.size);
if (!Number.isSafeInteger(size) || size < 0) {
throw new Error(
"The workspace track candidate size is not safe to inspect.",
);
}
return size;
}
function sameFileIdentity(
left: SequenceWorkspaceFileIdentity,
right: SequenceWorkspaceFileIdentity,
): boolean {
return (
left.changedAtNanoseconds === right.changedAtNanoseconds &&
left.device === right.device &&
left.inode === right.inode &&
left.links === right.links &&
left.modifiedAtNanoseconds === right.modifiedAtNanoseconds &&
left.size === right.size
);
}
function sameUnderlyingFile(
left: SequenceWorkspaceFileIdentity,
right: SequenceWorkspaceFileIdentity,
): boolean {
return left.device === right.device && left.inode === right.inode;
}
function sameDirectoryIdentity(
left: SequenceWorkspaceDirectoryIdentity,
right: SequenceWorkspaceDirectoryIdentity,
): boolean {
return left.device === right.device && left.inode === right.inode;
}
function sameMutableDirectoryIdentity(
left: MutableDirectoryIdentity,
right: MutableDirectoryIdentity,
): boolean {
return (
sameDirectoryIdentity(left, right) &&
left.changedAtNanoseconds === right.changedAtNanoseconds &&
left.modifiedAtNanoseconds === right.modifiedAtNanoseconds
);
}
async function assertNoSymlinkComponents(
rootPath: string,
directoryPath: string,
): Promise<void> {
const relative = path.relative(rootPath, directoryPath);
if (relative === "") return;
let current = rootPath;
for (const component of relative.split(path.sep)) {
current = path.join(current, component);
const fileStat = await lstat(current);
if (fileStat.isSymbolicLink() || !fileStat.isDirectory()) {
throw new Error("Workspace track directories must be real directories.");
}
}
}
function isPathWithin(root: string, candidate: string): boolean {
const relative = path.relative(root, candidate);
return (
relative === "" ||
(!path.isAbsolute(relative) &&
relative !== ".." &&
!relative.startsWith(`..${path.sep}`))
);
}
function sameCanonicalPath(left: string, right: string): boolean {
const normalize = (value: string) =>
process.platform === "win32"
? path.resolve(value).toLowerCase()
: path.resolve(value);
return normalize(left) === normalize(right);
}
function workspaceLabel(relativePath: string): string {
return relativePath === "" ? "." : relativePath.split(path.sep).join("/");
}
function sha256(value: Uint8Array): string {
return createHash("sha256").update(value).digest("hex");
}
function isAbortError(error: unknown): error is Error {
return error instanceof Error && error.name === "AbortError";
}
function throwIfAborted(signal?: AbortSignal): void {
if (!signal?.aborted) return;
throw (
signal.reason ??
new DOMException("Workspace track operation was cancelled.", "AbortError")
);
}
SHA-256: 1931e51c4c5b4ed576c32a1fd61a39808279e98e9dbf4f614b0f5d8c273ef946