Files
orca/src/shared/quick-open-readdir-budget.ts
T

115 lines
3.8 KiB
TypeScript

import { QUICK_OPEN_LISTING_MAX_RESULTS } from './quick-open-listing-limits'
export const QUICK_OPEN_READDIR_MAX_FILES = QUICK_OPEN_LISTING_MAX_RESULTS
export const QUICK_OPEN_READDIR_MAX_ENTRIES = 50_000
export const QUICK_OPEN_READDIR_MAX_DIRECTORIES = 25_000
export const QUICK_OPEN_READDIR_MAX_DEPTH = 256
export const QUICK_OPEN_READDIR_MAX_PATH_CODE_UNITS = 16 * 1024 * 1024
export const QUICK_OPEN_READDIR_TIMEOUT_MS = 10_000
export type QuickOpenReaddirBudget = {
remainingFiles: number
remainingEntries: number
remainingDirectories: number
remainingPathCodeUnits: number
maxFiles: number
maxEntries: number
maxDirectories: number
maxDepth: number
maxPathCodeUnits: number
deadlineMs: number
}
export function createQuickOpenReaddirBudget(
opts: {
maxFiles?: number
maxEntries?: number
maxDirectories?: number
maxDepth?: number
maxPathCodeUnits?: number
timeoutMs?: number
nowMs?: number
} = {}
): QuickOpenReaddirBudget {
const maxFiles = opts.maxFiles ?? QUICK_OPEN_READDIR_MAX_FILES
const maxEntries = opts.maxEntries ?? QUICK_OPEN_READDIR_MAX_ENTRIES
const maxDirectories = opts.maxDirectories ?? QUICK_OPEN_READDIR_MAX_DIRECTORIES
const maxDepth = opts.maxDepth ?? QUICK_OPEN_READDIR_MAX_DEPTH
const maxPathCodeUnits = opts.maxPathCodeUnits ?? QUICK_OPEN_READDIR_MAX_PATH_CODE_UNITS
for (const [name, value] of Object.entries({
maxFiles,
maxEntries,
maxDirectories,
maxDepth,
maxPathCodeUnits
})) {
if (!Number.isSafeInteger(value) || value < 0) {
throw new RangeError(`${name} must be a non-negative safe integer`)
}
}
return {
remainingFiles: maxFiles,
remainingEntries: maxEntries,
remainingDirectories: maxDirectories,
remainingPathCodeUnits: maxPathCodeUnits,
maxFiles,
maxEntries,
maxDirectories,
maxDepth,
maxPathCodeUnits,
deadlineMs: (opts.nowMs ?? Date.now()) + (opts.timeoutMs ?? QUICK_OPEN_READDIR_TIMEOUT_MS)
}
}
const FILE_LISTING_TIMED_OUT = 'File listing timed out'
const FILE_LISTING_EXCEEDED_PREFIX = 'File listing exceeded'
/** Budget errors are the only fallback failures translated into install-rg guidance. */
export function isQuickOpenReaddirBudgetError(error: unknown): boolean {
const message = error instanceof Error ? error.message : ''
return message === FILE_LISTING_TIMED_OUT || message.startsWith(FILE_LISTING_EXCEEDED_PREFIX)
}
export function assertQuickOpenReaddirDeadline(budget: QuickOpenReaddirBudget): void {
if (Date.now() > budget.deadlineMs) {
throw new Error(FILE_LISTING_TIMED_OUT)
}
}
export function consumeQuickOpenReaddirFileBudget(budget: QuickOpenReaddirBudget): void {
if (budget.remainingFiles <= 0) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxFiles} files`)
}
budget.remainingFiles--
}
export function consumeQuickOpenReaddirEntryBudget(budget: QuickOpenReaddirBudget): void {
if (budget.remainingEntries <= 0) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxEntries} entries`)
}
budget.remainingEntries--
}
export function consumeQuickOpenReaddirDirectoryBudget(budget: QuickOpenReaddirBudget): void {
if (budget.remainingDirectories <= 0) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxDirectories} directories`)
}
budget.remainingDirectories--
}
export function assertQuickOpenReaddirDepth(budget: QuickOpenReaddirBudget, depth: number): void {
if (depth > budget.maxDepth) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} depth ${budget.maxDepth}`)
}
}
export function consumeQuickOpenReaddirPathBudget(
budget: QuickOpenReaddirBudget,
path: string
): void {
if (path.length > budget.remainingPathCodeUnits) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxPathCodeUnits} path code units`)
}
budget.remainingPathCodeUnits -= path.length
}