UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

377 lines (376 loc) 23.3 kB
import { RVersion, type VersionString } from '../../util/r-version'; import { type LibraryExports, type PkgBlob, type SigDb, type SigDbContent } from './schema'; import { type VersionRelease } from './sigdb-version'; import { type SigDbIndex } from './index-format'; import { type DecodedFunction, type ResolvedDependency } from './decode'; import { type SigDbManifest, type SigDbShardRef } from './manifest'; /** stream-read a whole bundle into a {@link SigDb} (any size; never one string). Prefer {@link SigDatabase} for partial access. */ export declare function readSignatureDb(file: string): Promise<SigDb>; /** * The read interface every package-signature source implements, so a single {@link SigDatabase} and a * sharded {@link SigDatabaseSet} are interchangeable. Queries are synchronous; any decompression/caching * happens once during `open`. */ export interface PackageSignatureSource { /** whether the source can resolve the package at all */ has(pkg: string): boolean; /** whether the source actually carries the given version of a package (not just the package itself) */ hasVersion(pkg: string, version: string): boolean; /** whether a version is a current CRAN release (i.e. not in the package's `noncran`/removed set) */ isCranVersion(pkg: string, version: string): boolean; /** the export view of a package version (defaults to its latest) */ lookup(pkg: string, version?: string): LibraryExports | undefined; /** * The package that OWNS the class `className`: an S3 class (a same-named constructor plus a registered method, * see {@link LibraryExports.s3Classes}) or an S4 class (exported via `exportClasses`, see * {@link LibraryExports.s4Classes}); S3 ownership wins a tie. `undefined` if none does. Without `version`, backed * by a reverse index over every package's latest version, built once and cached. */ /** * The packages exporting `name`, ordered by downloads (descending, ties by name). Answered without building * a reverse index: a name the database never stores is rejected outright, and only the blobs that may hold * it are decoded. */ packagesExporting(name: string): readonly string[]; classOwner(className: string, version?: string): string | undefined; /** rich per-function view (signatures + call graphs) of a package version */ functions(pkg: string, version?: string): DecodedFunction[] | undefined; /** the rich view of a single function by name, decoding only it (unlike {@link functions}, which decodes the whole package) */ functionByName(pkg: string, name: string, version?: string): DecodedFunction | undefined; /** the transitive callees of a function within one package version, expanding the stored local call graphs */ transitiveCallees(pkg: string, name: string, version?: string): string[] | undefined; /** declared dependencies (Depends/Imports/…) of a package version, with version qualifiers */ dependencies(pkg: string, version?: string): ResolvedDependency[] | undefined; /** every package name this source can resolve */ packageNames(): string[]; /** whether the package is an R-core / base package (its versions are the R releases it shipped with) */ isBaseR(pkg: string): boolean; /** how often the package was downloaded when the database was built, the popularity {@link SigDbPkgMeta} records */ downloads(pkg: string): number; /** for a base package, the R versions it was part of core (ascending); `undefined` otherwise */ coreVersions(pkg: string): RVersion[] | undefined; /** the release date of a package version (defaulting to the newest release), or `undefined` if unknown */ releaseDate(pkg: string, version?: string): Date | undefined; /** every known release of a package (version + date), ascending by R-version order */ releaseDates(pkg: string): VersionRelease[]; /** the newest version of a package by release date (falling back to the recorded latest) */ latestVersion(pkg: string): RVersion | undefined; /** release any held file handles */ close(): void; } /** one version a source can answer for a package, with its release date when known */ export interface AvailableVersion { readonly version: VersionString; readonly date?: Date; } /** the on-demand load state of one shard of a {@link SigDatabaseSet} */ export interface ShardStatus { /** the shard id, e.g. `current-top` (its `base`/`current`/`history` prefix is the scope it belongs to) */ readonly id: string; /** whether the shard ships only as a compressed `.br`/`.zst` bundle (so it must be unpacked to be read) */ readonly compressed: boolean; /** whether this session has opened (mounted) the shard */ readonly accessed: boolean; /** whether the shard's decompressed cache exists on disk (unpacked, this session or an earlier one) */ readonly unpacked: boolean; } /** * The versions a source can answer for a package (dated releases, base-R core releases, and the recorded latest), * deduplicated and ascending by R-version order. This is the single enumeration both the signature query and the * version-guessing query build on. */ export declare function availableVersionEntries(src: PackageSignatureSource, pkg: string): AvailableVersion[]; /** * The reverse index `class -> owning package` over `candidates`, at each one's latest version. S3 ownership (a * same-named constructor plus a registered method) is a stronger signal than an S4 `exportClasses`, so S3-owned * classes are indexed first and an S4 class only claims a name no S3 owner already took. * * Restricting the candidates is the targeted counterpart of {@link PackageSignatureSource.classOwner}: class names * collide heavily across CRAN, so a whole-database answer is decided by database order, and deriving one means * reading every package in the database. */ export declare function classOwnerIndexFor(src: PackageSignatureSource, candidates: Iterable<string>): Map<string, string>; /** union view over multiple sources for the same package; routes queries to the appropriate source */ export declare class MergedSignatureSource implements PackageSignatureSource { private readonly sources; constructor(sources: readonly PackageSignatureSource[]); /** source carrying a version, or the one with the newest release when unpinned */ private pick; has(pkg: string): boolean; hasVersion(pkg: string, version: string): boolean; isCranVersion(pkg: string, version: string): boolean; lookup(pkg: string, version?: string): LibraryExports | undefined; packagesExporting(name: string): readonly string[]; classOwner(className: string, version?: string): string | undefined; functions(pkg: string, version?: string): DecodedFunction[] | undefined; functionByName(pkg: string, name: string, version?: string): DecodedFunction | undefined; transitiveCallees(pkg: string, name: string, version?: string): string[] | undefined; dependencies(pkg: string, version?: string): ResolvedDependency[] | undefined; packageNames(): string[]; isBaseR(pkg: string): boolean; downloads(pkg: string): number; coreVersions(pkg: string): RVersion[] | undefined; releaseDate(pkg: string, version?: string): Date | undefined; releaseDates(pkg: string): VersionRelease[]; latestVersion(pkg: string): RVersion | undefined; close(): void; } /** source that answers for pkg, merging all sources that carry it; undefined if none do */ export declare function sourceForPackage(sources: readonly PackageSignatureSource[], pkg: string): PackageSignatureSource | undefined; /** options controlling where {@link SigDatabase}/{@link SigDatabaseSet} materialize decompressed caches */ export interface SigDbOpenOptions { /** directory for the decompressed, hash-keyed cache (default: see {@link sigDbCacheDir}) */ cacheDir?: string; /** content hash to key the cache (avoids reading the source header; supplied from a manifest) */ hash?: string; /** index to use instead of a sibling `.idx` (supplied from a manifest so no `.idx` file need ship) */ index?: SigDbIndex; } /** a caller-supplied index/dictionary for {@link SigDatabase.openSync} (both derived from the source otherwise) */ export interface OpenSyncOptions { index?: SigDbIndex; strings?: string[]; } /** {@link SigDatabase.openSyncFrom} options: cache settings plus an optional precomputed hash/index/dictionary */ export interface OpenSyncFromOptions extends SigDbOpenOptions, OpenSyncOptions { hash?: string; } export declare class SigDatabase implements PackageSignatureSource { private closed; /** parsed blobs by blob index so repeated lookups skip the re-read + JSON.parse; FIFO-bounded to cap memory */ private readonly blobCache; private static readonly BlobCacheCap; /** a version's function indices plus its `name -> index` view; FIFO-bounded like {@link blobCache} */ private readonly versionFnCache; /** * Which names a package can offer at all, as sorted dictionary ids -- integers, so the whole database of * them costs a few MB where the parsed blobs cost hundreds. Filled for a package the first time its blob is * read, so a later lookup for a name it does not have answers without touching the file again. */ private readonly nameIdsOfBlob; /** dictionary id per name asked for, so only the names someone actually queried are ever resolved */ private readonly nameIds; /** reverse index `S3 class -> owning package`, over every package's latest version; built once (see {@link classOwner}) */ private classIndex; private readonly fd; readonly strings: string[]; readonly index: SigDbIndex; readonly content: SigDbContent | undefined; private readonly cranBase; private constructor(); /** * Use an already-built {@link SigDb} directly, without writing or reading a file: its blobs simply start out in * the cache. It holds exactly what a bundle carries, so every query answers as it would from disk; the * {@link SigDbBuilder} plus this is all a test needs. */ static fromMemory(db: SigDb): SigDatabase; /** * Open a plain, seekable `.sigs.ndjson` synchronously. Pass `index` to skip reading the `.idx`, * and `strings` to use an already-loaded shared dictionary instead of the file's own `d` section (for a * blob-only shard). One ranged read loads the dictionary, no readline overhead. */ static openSync(plainFile: string, opts?: OpenSyncOptions): SigDatabase; /** open a `.sigs.ndjson`, `.br` or `.gz`; compressed sources are decompressed into a hash-keyed cache once */ static open(source: string, opts?: SigDbOpenOptions): Promise<SigDatabase>; /** * Like {@link open} but fully synchronous (blocking decompression); a `hash` keys the cache when `source` * is compressed. Pass `strings` for a blob-only shard that shares an already-loaded dictionary. */ static openSyncFrom(source: string, opts: OpenSyncFromOptions): SigDatabase; has(pkg: string): boolean; packageNames(): string[]; /** load a single package's blob by seeking to its line (undefined if absent); cached by blob index */ blob(pkg: string): PkgBlob | undefined; /** the dictionary id of `name`, or `-1` if the dictionary does not hold it, so no package can offer it */ private nameId; /** * Whether `pkg` can offer `name` in any of its versions, answered from {@link nameIdsOfBlob} alone. * `true` whenever nothing is known about the package yet, so this only ever skips work that would have * found nothing: the ids cover every function record, of which a version selects a subset. */ private mayOffer; /** store `value` under `key`, dropping the oldest entry first once the cache sits at {@link BlobCacheCap} */ private static cache; /** seek to a byte range, read + decode the package blob there (no caching) */ private readBlobAt; /** read every unique package blob in index order (used to re-hash a whole shard during verification) */ allBlobs(): PkgBlob[]; /** recompute this bundle's self-contained content hash from its re-read data (matches {@link writeSignatureDb}) */ contentHash(blobs?: PkgBlob[]): string; /** whether this bundle actually carries the given version of a package (not just the package) */ hasVersion(pkg: string, version: string): boolean; /** whether a version is a current CRAN release (not in the package's `noncran`/removed set) */ isCranVersion(pkg: string, version: string): boolean; lookup(pkg: string, version?: string): LibraryExports | undefined; packagesExporting(name: string): readonly string[]; classOwner(className: string, version?: string): string | undefined; /** resolve a package version to its blob and the function-record indices of that version (the shared prologue of {@link functions}/{@link functionByName}) */ private versionFns; functions(pkg: string, version?: string): DecodedFunction[] | undefined; functionByName(pkg: string, name: string, version?: string): DecodedFunction | undefined; transitiveCallees(pkg: string, name: string, version?: string): string[] | undefined; dependencies(pkg: string, version?: string): ResolvedDependency[] | undefined; /** whether this is an R-core / base package (its versions are the R releases it shipped with; see {@link SigDbPkgMeta}) */ isBaseR(pkg: string): boolean; /** the download count recorded for the package, `0` when this source does not carry it */ downloads(pkg: string): number; /** * The R versions a base package was part of core, in ascending R-version order (exactly its stored * versions). `undefined` for a non-base package. E.g. `mva` returns `…1.9.1`, `parallel` `2.14.0…`. */ coreVersions(pkg: string): RVersion[] | undefined; /** the release date of a package version (defaulting to the newest release), or `undefined` if unknown */ releaseDate(pkg: string, version?: string): Date | undefined; /** every known release date of a package, in ascending R-version order (empty when no dates were stored) */ releaseDates(pkg: string): VersionRelease[]; /** the newest version of a package by release date (falling back to the recorded latest, then SemVer order) */ latestVersion(pkg: string): RVersion | undefined; /** close the underlying file descriptor (idempotent; safe to call more than once) */ close(): void; } /** options for {@link SigDatabaseSet.openManifest}: the base cache options plus per-shard enable/disable */ export interface SigDbSetOpenOptions extends SigDbOpenOptions { /** only load these shard ids (e.g. `['base-current','current-top']`); omit to load all */ includeShards?: readonly string[]; /** load every shard except these ids (e.g. `['full-top','full-rest']` for a current-only view) */ excludeShards?: readonly string[]; } /** one mounted shard: its manifest entry paired with the opened {@link SigDatabase} */ interface MountedShard { ref: SigDbShardRef; db: SigDatabase; } /** * A transparent, read-only view over several {@link SigDatabase} shards described by a {@link SigDbManifest}. * When the manifest embeds each shard's index (the default), `openManifest()` reads only that small file to * build the package to shard routing table. */ export declare class SigDatabaseSet implements PackageSignatureSource { private readonly opened; readonly manifest: SigDbManifest; /** the directory the manifest's shard/dict paths are relative to */ readonly baseDir: string; private readonly indices; /** package name to shard indices, ordered by preference (current before full) */ private readonly routes; private readonly cacheDir?; /** reverse index `S3 class -> owning package`, over every package's latest version; built once (see {@link classOwner}) */ private classIndex; private constructor(); /** read + validate a manifest and apply the include/exclude shard filter */ private static prepManifest; /** build the package to shard routing (current before full) and construct the set from resolved indices */ private static assemble; static openManifest(manifestFile: string, opts?: SigDbSetOpenOptions): Promise<SigDatabaseSet>; /** * Synchronous {@link openManifest}, needing every shard to embed its index (the default for the bundles * flowR ships). Shards and dictionaries still decompress lazily. */ static openManifestSync(manifestFile: string, opts?: SigDbSetOpenOptions): SigDatabaseSet; /** shared dictionaries, loaded (decompressed + parsed) once and cached by id */ private readonly dictCache; /** load (and cache) a shared dictionary's strings, decompressing its `.br` into the cache once */ private dictionaryStrings; /** lazily open a shard, decompressing its `.br` (and its shared dictionary) into the cache on first access */ private shard; /** * Warm the shards (and their shared dictionaries) needed for `pkgs`, or **everything** when omitted. * Afterward, the synchronous query methods, for the latest *and* historical versions, do no I/O or decompression. */ preload(pkgs?: readonly string[]): Promise<void>; /** * Warm just the shards matching `include`, e.g., only the current-tier top shards (the base + most-downloaded * packages) to speed up common lookups without paying for the long tail or the history shards. See {@link preload}. */ preloadShards(include: (shard: SigDbShardRef) => boolean): Promise<void>; /** decompress the given shards + their shared dictionaries concurrently, then open them (see {@link preload}) */ private warmShards; /** the shard indices that can serve this package, preferred order; optionally requiring a specific version */ private route; /** read (once) the blob from the shard with the most complete history: a `full` or `history` tier if present */ private historyBlob; has(pkg: string): boolean; /** whether any active shard actually carries the given version of a package (not just the package) */ hasVersion(pkg: string, version: string): boolean; /** whether a version is a current CRAN release (not in the package's `noncran`/removed set) */ isCranVersion(pkg: string, version: string): boolean; packageNames(): string[]; /** the on-demand load state (opened + unpacked) of every shard in this set */ shardStatus(): ShardStatus[]; /** open (blocking) and return every shard database with its manifest ref, for whole-set verification */ allShards(): MountedShard[]; /** load (and cache) a shared dictionary's strings by id, for verification/inspection */ sharedDictionary(id: string): string[]; /** the first non-empty result from the shards that can serve `pkg` (in preferred order: current before full) */ private firstOf; lookup(pkg: string, version?: string): LibraryExports | undefined; packagesExporting(name: string): readonly string[]; classOwner(className: string, version?: string): string | undefined; functions(pkg: string, version?: string): DecodedFunction[] | undefined; functionByName(pkg: string, name: string, version?: string): DecodedFunction | undefined; transitiveCallees(pkg: string, name: string, version?: string): string[] | undefined; dependencies(pkg: string, version?: string): ResolvedDependency[] | undefined; /** whether this is an R-core / base package (see {@link SigDatabase.isBaseR}); O(1) via the hoisted metadata */ isBaseR(pkg: string): boolean; /** the download count of the package; O(1) via the hoisted metadata, so no shard has to be unpacked for it */ downloads(pkg: string): number; /** the R versions a base package was part of core (ascending); `undefined` if not a base package */ coreVersions(pkg: string): RVersion[] | undefined; /** every known release of a package (version + date), ascending, read once from the most complete shard */ releaseDates(pkg: string): VersionRelease[]; /** the release date of a package version (defaulting to the newest release), or `undefined` if unknown */ releaseDate(pkg: string, version?: string): Date | undefined; /** the newest version of a package by release date (falling back to the recorded latest, then SemVer order) */ latestVersion(pkg: string): RVersion | undefined; /** * Close every opened shard's file descriptor and drop the in-memory caches (opened shards + shared * dictionaries), so the (potentially large) dictionary strings can be reclaimed by the GC. Idempotent. */ close(): void; } /** * Open a path-based source once, process-wide, synchronously. Returns the shared instance (opening it on the * first call), or `undefined` if the path needs async opening (a `.br`/`.gz` bundle, use {@link getSharedSigSource}). * Throws only if a sync-openable source fails to open. */ export declare function getSharedSigSourceSync(source: string): PackageSignatureSource | undefined; /** Open a path-based source once, process-wide (async: also handles `.br`/`.zst`/`.gz` bundles and non-embedded manifests). */ export declare function getSharedSigSource(source: string): Promise<PackageSignatureSource | undefined>; /** per-shard result of {@link verifyShardedDatabase} */ export interface ShardVerifyResult { id: string; packages: number; /** the shard's content hash recomputed from its re-read blobs matches the manifest + file header */ hashOk: boolean; expectedHash: string; actualHash: string; } /** the outcome of {@link verifyShardedDatabase} */ export interface SigDbVerifyReport { ok: boolean; /** the shared dictionaries' recomputed hashes all match the manifest */ dictsOk: boolean; shards: ShardVerifyResult[]; /** number of distinct packages routed by the manifest */ routedPackages: number; /** functions/dependencies decoded during the spot-check without an out-of-range string */ spotChecked: number; /** required packages (e.g. base R) that were requested but not found */ missingRequired: string[]; /** every problem found (empty when `ok`) */ errors: string[]; } /** {@link verifyShardedDatabase} options: open settings plus which packages must exist and how many to spot-check */ export interface VerifyOptions extends SigDbOpenOptions { requirePackages?: readonly string[]; sample?: number; } /** * Re-read a written sharded database from its (compressed) files and check it is internally consistent: * every shard's content hash recomputed from its re-read blobs matches both the manifest and the file * header; every shared dictionary's hash matches; the manifest routes every package a shard holds; a * sample of packages decodes (functions + dependencies) with all string indices in range; and any * `requirePackages` (e.g. base R) are present. This is a strong correctness gate: correctness over speed. */ export declare function verifyShardedDatabase(manifestFile: string, opts?: VerifyOptions): Promise<SigDbVerifyReport>; export {};