/** * A ranged cache * Copyright (c) 2025 James Prevett */ export interface Options { /** * If true, use multiple buffers to cache a file. * This is useful when working with small parts of large files, * since we don't need to allocate a large buffer that is mostly unused * @default true */ sparse?: boolean; /** * The threshold for whether to combine regions or not * @see Region * @default 0xfff // 4 KiB */ regionGapThreshold?: number; /** * Whether to only update the cache when changing or deleting resources * @default false */ cacheOnly?: boolean; /** * Multiplier applied to a region's capacity when it has to grow. * Values outside `[1, 100]` fall back to the default. * Higher trades memory for fewer reallocations, while `1` allocates exactly what is needed and makes repeated appends quadratic. * @default 2 */ growFactor?: number; } export type Range = { start: number; end: number; }; /** Insert `[start, end)` into a sorted, non-overlapping list of ranges, merging it with any ranges it touches. */ export declare function addRange(ranges: Range[], start: number, end: number): void; export interface Region { /** The region's offset from the start of the resource */ offset: number; /** Ranges cached in this region. These are absolute! */ ranges: Range[]; /** Data for this region */ data: Uint8Array; } /** * The cache for a specific resource * @internal */ export declare class Resource { /** The resource ID */ readonly id: ID; protected _size: number; protected readonly options: Options; /** Regions used to reduce unneeded allocations. Think of sparse arrays. */ readonly regions: Region[]; /** The full size of the resource */ get size(): number; set size(value: number); constructor( /** The resource ID */ id: ID, _size: number, options: Options, resources?: Map | undefined>); /** * Resize `region.data` to `length` bytes. * Capacity can be over-allocated geometrically so appending to a region repeatedly stays amortized O(1). */ protected resizeRegion(region: Region, length: number): void; /** Merge the region after `index` into the one at `index`, if the gap between them is small enough */ protected mergeAt(index: number): boolean; /** Combines adjacent regions and combines adjacent ranges within a region */ collect(): void; /** * Combine the region at `index` with the neighbors it now reaches. * Only that region can have changed, so this does not re-walk the whole list the way `collect` does. */ protected collectAt(index: number): void; /** Takes an initial range and finds the sub-ranges that are not in the cache */ missing(start: number, end: number): Range[]; /** * Get the cached sub-ranges of an initial range. * This is conceptually the inverse of `missing`. */ cached(start: number, end: number): Range[]; /** Index of the first region positioned after `offset`. Regions are kept sorted, so this is a binary search. */ protected indexAfter(offset: number): number; /** Get the region who's ranges include an offset */ regionAt(offset: number): Region | undefined; /** The regions overlapping `[start, end)`, in order. Seeks to the first one rather than scanning from the start. */ regionsIn(start: number, end: number): Generator; /** Add new data to the cache at given specified offset */ add(data: Uint8Array, offset: number): this; }