@packall/core
The registry-agnostic engine: resolution, download, verification and packaging.
import { … } from "@packall/core";Services and classes
Archiver
Service tag for archive creation.
class Archiver {
readonly Service: {
readonly create: (options: CreateArchiveOptions) => Effect<ArchiveResult, ArchiveError | PlatformError, FileSystem>;
}
readonly key: "@packall/core/Archiver"
}Archiver.Service
The packing backend.
type Archiver.Service = {
readonly create: (options: CreateArchiveOptions) => Effect<ArchiveResult, ArchiveError | PlatformError, FileSystem>;
}Progress
Service tag for progress reporting.
class Progress {
readonly Service: { readonly emit: (event: ProgressEvent) => Effect<void, never, never>; }
readonly key: "@packall/core/Progress"
}Progress.Service
The reporting sink.
type Progress.Service = { readonly emit: (event: ProgressEvent) => Effect<void, never, never>; }Registry
Service tag for the registry backend.
class Registry {
readonly Service: {
readonly kind: string;
readonly registryFor: (packageName: string) => string;
readonly preflight: Effect<void, BundlerError, never>;
readonly packument: (packageName: string) => Effect<Packument, BundlerError, never>;
readonly download: (manifest: PackageManifest) => Effect<Uint8Array<ArrayBufferLike>, BundlerError, never>;
}
readonly key: "@packall/core/Registry"
}import { Effect } from "effect"
import { Registry } from "@packall/core"
const program = Effect.gen(function*() {
const registry = yield* Registry
return yield* registry.packument("lodash")
})Registry.Service
The set of operations a registry backend has to provide.
type Registry.Service = {
readonly kind: string;
readonly registryFor: (packageName: string) => string;
readonly preflight: Effect<void, BundlerError, never>;
readonly packument: (packageName: string) => Effect<Packument, BundlerError, never>;
readonly download: (manifest: PackageManifest) => Effect<Uint8Array<ArrayBufferLike>, BundlerError, never>;
}Functions
buildManifest
Builds the manifest for a completed (or dry) run.
const buildManifest: (input: {
readonly resolution: Resolution;
readonly included: readonly ResolvedPackage[];
readonly options: BundleOptions;
readonly registry: {
readonly kind: string;
readonly url: string;
};
readonly toolVersion: string;
readonly sizes: ReadonlyMap<string, number>;
readonly createdAt?: Date | undefined;
}) => BundleManifestbundle
Resolves, downloads and packages a set of specs.
Under dryRun this stops after resolution and reports the plan — useful for
checking what a command would pull before committing to a multi-gigabyte
download over a slow VPN.
const bundle: (specs: readonly PackageSpec[], options: BundleContext) => Effect<BundleResult, PlatformError | BundlerError, BundleEnv>bundleLocked
bundle, pinned to a lockfile.
const bundleLocked: (lockfile: LockedTree, options: ResolveOptions & {
readonly layout: Layout;
readonly outDir: string;
readonly archiveName?: string | undefined;
readonly dryRun: boolean;
readonly verifyIntegrity: boolean;
readonly force: boolean;
readonly skipExisting?: ReadonlySet<string> | undefined;
readonly overwrite?: ReadonlySet<string> | undefined;
} & {
readonly toolVersion: string;
} & {
readonly includeDev: boolean;
readonly declared?: readonly string[] | undefined;
}) => Effect<BundleResult, PlatformError | BundlerError, BundleEnv>bundleResolved
Downloads and packages an already-computed resolution.
Everything from here on touches the disk, and all of it happens inside
Effect.scoped — so the staging tree's lifetime is exactly this call,
including when it fails partway through or the user hits Ctrl-C.
const bundleResolved: (resolution: Resolution, options: BundleContext) => Effect<BundleResult, PlatformError | BundlerError, BundleEnv>createArchive
Packs an archive with whichever backend is installed.
The progress events are emitted here rather than inside the backends, so every implementation reports identically and a new one cannot forget to.
const createArchive: (options: CreateArchiveOptions) => Effect<ArchiveResult, ArchiveError | PlatformError, Archiver | FileSystem | Progress>dedupeSpecs
Collapses duplicate specs, keeping the first occurrence.
npmb react react@latest is a plausible thing to type and should not
download React twice.
const dedupeSpecs: (specs: readonly PackageSpec[]) => readonly PackageSpec[]detectLockfile
Deliberately cheap and total: it never throws, so --file can try this
first and fall through to its other shapes when the answer is NotALockfile.
const detectLockfile: (content: string) => LockfileDetectiondigestOf
Computes the base64 digest of some bytes under one algorithm.
const digestOf: (data: Uint8Array<ArrayBufferLike>, algorithm: "sha1" | "sha256" | "sha384" | "sha512") => Effect<string, PlatformError, Crypto>emitProgress
Emits an event to whichever reporter is installed.
const emitProgress: (event: ProgressEvent) => Effect<void, never, Progress>formatPlatformFilter
Renders a filter for the manifest and for --dry-run output.
const formatPlatformFilter: (filter: PlatformFilter) => stringformatSelector
Human-readable form of just the selector part.
const formatSelector: (selector: Selector) => stringformatSpec
Renders a spec back into the canonical name@selector form.
const formatSpec: (spec: PackageSpec) => stringhexDigestOf
Computes the hex digest of some bytes — the form legacy shasum uses.
const hexDigestOf: (data: Uint8Array<ArrayBufferLike>, algorithm: "sha1" | "sha256" | "sha384" | "sha512") => Effect<string, PlatformError, Crypto>importGuide
The import guide written into each bundle.
Kept short and copy-pasteable on purpose: whoever opens this is mid-task on a restricted network and does not want prose.
Layout-aware, because the instructions genuinely differ — a registry-layout tree uploads into Artifactory as it stands and a flat directory does not, and a guide that said otherwise would be discovered to be wrong by somebody who can no longer look anything up.
const importGuide: (options: GuideOptions) => stringindexPackages
Index of a resolution by name@version, for lookups during bundling.
const indexPackages: (resolution: Resolution) => ReadonlyMap<string, ResolvedPackage>isIncluded
Should this package be included, given the active filter?
Under All this is unconditionally true — that is the whole point.
const isIncluded: (constraints: PlatformConstraints, filter: PlatformFilter) => booleanisRecord
Narrows an unknown value to a plain object.
The entry point for reading anything the tool does not control — lockfiles, package.json files, registry responses — without reaching for a cast.
const isRecord: (value: unknown) => value is Record<string, unknown>layerCallbackProgress
Sends every event to a callback. Used by the CLI renderer.
const layerCallbackProgress: (onEvent: (event: ProgressEvent) => void) => Layer<Progress, never, never>lockfileNamesFor
The names a given manager writes.
const lockfileNamesFor: (format: LockfileFormat) => readonly string[]packageKey
const packageKey: (name: string, version: string) => stringpackagePath
The path of one package tarball within a bundle, using / separators
regardless of host platform — these become tar entry names, and tar entries
are always POSIX.
const packagePath: (name: string, version: string) => stringparseDependencyTarget
Classifies one name -> range entry.
An empty range, *, and latest all mean "any published version"; npm
treats them interchangeably and so do we.
const parseDependencyTarget: (name: string, raw: string) => DependencyTargetparseInputFile
Parses file content that has already been read.
Split out from the IO so the whole of this logic is testable with plain strings and no file system at all.
const parseInputFile: (filePath: string, content: string, options?: InputFileOptions) => InputFileResultparseIntegrity
Parses an SRI string such as sha512-abc...==.
npm permits several space-separated hashes; we keep every one we can verify and ignore algorithms we cannot.
const parseIntegrity: (integrity: string) => readonly IntegrityHash[]parseLockfile
Parses lockfile content into a flat graph.
Throws LockfileError; the caller is expected to be inside an Effect.try.
const parseLockfile: (path: string, content: string, format?: LockfileFormat | undefined, options?: LockfileParseOptions) => LockedTreeparsePlatformTarget
Parses linux, linux-x64, darwin-arm64, win32-x64, linux-x64-musl,
linux-musl.
An OS on its own means every architecture for that OS, which is almost always
what you want: "the Windows and Linux bindings" is a far more natural way to
describe a target set than enumerating four os-arch pairs, and it keeps
working when a package adds an arm64 build.
Returns null rather than throwing so the CLI can report every bad value at
once alongside the list of accepted forms.
const parsePlatformTarget: (input: string) => PlatformTarget | nullparseSpec
Parses a single spec string.
Splitting on @ is the fiddly part: a scoped name starts with @, so we
look for the last @ that is not at index 0.
const parseSpec: (raw: string) => PackageSpecparseSpecs
Parses many specs, collecting every failure rather than stopping at the first. Somebody bundling forty packages should be told about all four typos in one go, not made to re-run four times.
const parseSpecs: (inputs: readonly string[]) => {
readonly specs: readonly PackageSpec[];
readonly errors: readonly InvalidSpecError[];
}perSpecArchiveName
Name of the archive produced for one root spec in per-spec layout.
The same rule as flatName, which is where the reasoning lives — a
per-spec archive and a flat package tarball are both "one file in a
directory, named for what is in it", and there is no reason for a directory
of bundles and a directory of packages to be read differently.
const perSpecArchiveName: (name: string, version: string) => stringplan
Checks the registry is reachable, then resolves every spec.
Split out from bundle so a caller can inspect the plan — how many packages,
how many archives — and act on it before any bytes move. The CLI uses this to
offer a different layout when a run turns out to be much larger than
expected.
const plan: (specs: readonly PackageSpec[], options: BundleOptions) => Effect<Resolution, BundlerError, Progress | Registry>planLocked
The lockfile counterpart of plan.
Same preflight, same Resolution out; the difference is entirely in how the
package set is arrived at. Kept as a separate entry point rather than an
option on plan because the two take genuinely different inputs — a set of
specs to satisfy versus a graph to reproduce — and blurring that is how a
"pinned" run quietly starts resolving ranges again.
const planLocked: (lockfile: LockedTree, options: ResolveOptions & {
readonly layout: Layout;
readonly outDir: string;
readonly archiveName?: string | undefined;
readonly dryRun: boolean;
readonly verifyIntegrity: boolean;
readonly force: boolean;
readonly skipExisting?: ReadonlySet<string> | undefined;
readonly overwrite?: ReadonlySet<string> | undefined;
} & {
readonly includeDev: boolean;
readonly declared?: readonly string[] | undefined;
}) => Effect<Resolution, BundlerError, Progress | Registry>plannedOutputs
The files a run will write.
Computed before anything is downloaded, so a collision is reported in the first second rather than after a twenty-minute transfer.
const plannedOutputs: (resolution: Resolution, options: BundleContext) => readonly PlannedOutput[]platformTargets
const platformTargets: (targets: readonly PlatformTarget[]) => PlatformFilterreadInputFile
Reads and parses an input file.
const readInputFile: (filePath: string, options?: InputFileOptions) => Effect<InputFileResult, InvalidInputFileError | LockfileError, FileSystem>resolve
Resolves every requested spec.
Each selected root version is walked separately so per-spec layout has an
exact closure per tarball, while all walks share one cache, so a package
reached from twenty roots is fetched once.
const resolve: (specs: readonly PackageSpec[], options: ResolveOptions) => Effect<Resolution, BundlerError, Progress | Registry>resolveLocked
Turns a parsed lockfile into a resolution, pinning every version.
const resolveLocked: (lock: LockedTree, options: LockedResolveOptions) => Effect<Resolution, BundlerError, Progress | Registry>selectVersions
Turns a selector into the concrete version(s) it names.
all controls whether a range collapses to its best match — npm's behaviour,
and what you always want for a transitive dependency — or expands to every
satisfying published version, which is what --all-versions is for.
const selectVersions: (packument: Packument, selector: Selector, options: {
readonly all: boolean;
readonly maxVersions?: number | undefined;
readonly includePrerelease: boolean;
}) => readonly string[]serializeManifest
Serialises a manifest with stable key order and a trailing newline.
const serializeManifest: (manifest: BundleManifest) => stringsingleArchiveName
Name of the archive produced in single layout.
const singleArchiveName: (base?: string) => stringsplitName
Splits @scope/name into its parts. scope is undefined when unscoped.
const splitName: (name: string) => {
readonly scope: string | undefined;
readonly bare: string;
}summarize
Computes the plan summary for a resolution.
const summarize: (resolution: Resolution) => PlanSummarytarballFileName
@babel/core + 7.24.0 -> core-7.24.0.tgz
const tarballFileName: (name: string, version: string) => stringtolerant
An optional field that goes absent rather than failing when it is malformed.
This is the difference between "we do not understand this field" and "we do
not understand this document". An os: "linux" where an array was expected
costs the platform filter one signal; it should not cost the run the package.
const tolerant: <S extends Schema.Top>(schema: S) => optional<middlewareDecoding<UndefinedOr<S>, S["DecodingServices"]>>verifyIntegrity
Checks bytes against whichever checksums the registry supplied.
SRI is preferred; shasum is the fallback for versions published before
integrity strings existed. If the registry gave us neither, that is reported
as Unverifiable rather than silently treated as a pass — the caller decides
whether to tolerate it.
const verifyIntegrity: (data: Uint8Array<ArrayBufferLike>, dist: {
readonly integrity?: string | undefined;
readonly shasum?: string | undefined;
}) => Effect<VerificationResult, PlatformError, Crypto>withLayout
Replaces the layout on a set of options.
const withLayout: <T extends BundleOptions>(options: T, layout: Layout) => TConstants
allPlatforms
const allPlatforms: {
readonly _tag: "All";
} | {
readonly _tag: "Targets";
readonly targets: readonly PlatformTarget[];
}ArtifactKind
What a bundle run wrote to the output directory.
type ArtifactKind = "archive" | "directory" | "file"
const ArtifactKind: {
readonly Archive: "archive";
readonly Directory: "directory";
readonly File: "file";
}defaultBundleOptions
const defaultBundleOptions: {
readonly scope: DependencyScope;
readonly allVersions: boolean;
readonly maxVersions?: number | undefined;
readonly includePrerelease: boolean;
readonly concurrency: number;
readonly layout: Layout;
readonly archiveName?: string | undefined;
readonly dryRun: boolean;
readonly verifyIntegrity: boolean;
readonly force: boolean;
readonly skipExisting?: ReadonlySet<string> | undefined;
readonly overwrite?: ReadonlySet<string> | undefined;
}defaultInputFileOptions
const defaultInputFileOptions: {
readonly includeDev: boolean;
readonly importer?: string | undefined;
}defaultResolveOptions
const defaultResolveOptions: {
readonly scope: DependencyScope;
readonly allVersions: boolean;
readonly maxVersions?: number | undefined;
readonly includePrerelease: boolean;
readonly concurrency: number;
}defaultScope
const defaultScope: {
readonly optional: boolean;
readonly peer: boolean;
readonly platforms: PlatformFilter;
}DirectOnlyKind
The kind that only a direct dependency of the locked project can have.
type DirectOnlyKind = "dev"
const DirectOnlyKind: { readonly Dev: "dev"; }EdgeKind
Why one package depends on another.
EdgeKind and LockedRootKind are one concept in two parts, which is why
they share a file: Dev exists only at the top level, because lockfiles
record dev dependencies for the project alone — a transitive devDependency is
never installed, so it is never locked.
type EdgeKind = "optional" | "peer" | "prod"
const EdgeKind: { readonly Prod: "prod"; readonly Optional: "optional"; readonly Peer: "peer"; }formatByLockfileName
The file name each supported manager writes, in detection order.
Ordered: package-lock.json before npm-shrinkwrap.json before pnpm's and
bun's, so a directory holding several is searched the way npm itself would.
const formatByLockfileName: {
readonly "package-lock.json": "npm";
readonly "npm-shrinkwrap.json": "npm";
readonly "pnpm-lock.yaml": "pnpm";
readonly "bun.lock": "bun";
}InputFileKind
Which of the three shapes a --file input turned out to be.
type InputFileKind = "list" | "lockfile" | "package.json"
const InputFileKind: {
readonly PackageJson: "package.json";
readonly Lockfile: "lockfile";
readonly List: "list";
}layerSilentProgress
Discards every event.
The default for library consumers and for tests that do not care about progress — silence should never require ceremony.
const layerSilentProgress: Layer<Progress, never, never>Layout
How the resulting tarball(s) are shaped.
type Layout = "dir" | "flat" | "per-spec" | "single"
const Layout: {
readonly PerSpec: "per-spec";
readonly Single: "single";
readonly Dir: "dir";
readonly Flat: "flat";
}Layouts
const Layouts: readonly Layout[]LockedRootKind
Why a direct dependency of the locked project is a root.
type LockedRootKind = "dev" | "optional" | "peer" | "prod"
const LockedRootKind: {
readonly Prod: "prod";
readonly Optional: "optional";
readonly Peer: "peer";
readonly Dev: "dev";
}LockfileFormat
Which package manager wrote a lockfile.
type LockfileFormat = "bun" | "npm" | "pnpm"
const LockfileFormat: { readonly Npm: "npm"; readonly Pnpm: "pnpm"; readonly Bun: "bun"; }makeProgressCollector
Accumulates every event into a Ref, for assertions.
Returned as { layer, events } so a test can provide the layer and then read
the transcript afterwards.
const makeProgressCollector: Effect<{
readonly layer: Layer<Progress, never, never>;
readonly events: Effect<readonly ProgressEvent[], never, never>;
}, never, never>MANIFEST_FILE
File name of the manifest that ships inside every bundle.
const MANIFEST_FILE: "bundle-manifest.json"MANIFEST_VERSION
Schema version, bumped when the shape changes incompatibly.
const MANIFEST_VERSION: 1OptionalFlagRecordOrAbsentSchema
const OptionalFlagRecordOrAbsentSchema: decodeTo<UndefinedOr<$Record<String, Struct<{
readonly optional: Boolean;
}>>>, decodeTo<$Record<String, Struct<{
readonly optional: Boolean;
}>>, $Record<String, Unknown>, never, never>, never, never>OptionalFlagRecordSchema
peerDependenciesMeta, normalised.
Only optional is read, and only its true is meaningful — npm writes the
flag as a boolean, a hand-edited file may carry anything, and every other
value means "not optional".
const OptionalFlagRecordSchema: decodeTo<$Record<String, Struct<{
readonly optional: Boolean;
}>>, $Record<String, Unknown>, never, never>OptionalStringArraySchema
const OptionalStringArraySchema: decodeTo<UndefinedOr<$Array<String>>, decodeTo<$Array<String>, $Array<Unknown>, never, never>, never, never>OptionalStringRecordSchema
const OptionalStringRecordSchema: decodeTo<UndefinedOr<$Record<String, String>>, decodeTo<$Record<String, String>, $Record<String, Unknown>, never, never>, never, never>Phase
The phases a bundle run moves through, in order.
type Phase = "archive" | "done" | "download" | "preflight" | "resolve"
const Phase: {
readonly Preflight: "preflight";
readonly Resolve: "resolve";
readonly Download: "download";
readonly Archive: "archive";
readonly Done: "done";
}README_FILE
File name of the short import guide that ships inside every bundle.
const README_FILE: "IMPORT.md"StringArraySchema
The same per-entry tolerance for a list: ["linux", 7] decodes to ["linux"].
const StringArraySchema: decodeTo<$Array<String>, $Array<Unknown>, never, never>StringRecordSchema
Keeps only the string-valued members of an untyped object.
{ "lodash": "^4.0.0", "broken": 3 } decodes to { "lodash": "^4.0.0" }.
Per-entry rather than whole-record tolerance, because one unreadable edge in
a dependency block is no reason to forget the others.
const StringRecordSchema: decodeTo<$Record<String, String>, $Record<String, Unknown>, never, never>Types
ArchiveResult
A finished archive.
type ArchiveResult = { readonly path: string; readonly bytes: number; }BundleArtifact
One thing written to the output directory.
type BundleArtifact = {
readonly kind: ArtifactKind;
readonly path: string;
readonly bytes: number;
readonly packageCount: number;
readonly spec?: string | undefined;
readonly version?: string | undefined;
}BundleContext
Everything bundle needs beyond the user-facing options.
type BundleContext = ResolveOptions & {
readonly layout: Layout;
readonly outDir: string;
readonly archiveName?: string | undefined;
readonly dryRun: boolean;
readonly verifyIntegrity: boolean;
readonly force: boolean;
readonly skipExisting?: ReadonlySet<string> | undefined;
readonly overwrite?: ReadonlySet<string> | undefined;
} & {
readonly toolVersion: string;
}BundleManifest
type BundleManifest = {
readonly manifestVersion: number;
readonly tool: {
readonly name: string;
readonly version: string;
};
readonly createdAt: string;
readonly registry: {
readonly kind: string;
readonly url: string;
};
readonly requested: readonly string[];
readonly roots: readonly {
readonly spec: string;
readonly versions: readonly string[];
readonly packageCount: number;
}[];
readonly options: {
readonly layout: string;
readonly optionalDependencies: boolean;
readonly peerDependencies: boolean;
readonly platforms: string;
readonly allVersions: boolean;
readonly includePrerelease: boolean;
readonly integrityVerified: boolean;
};
readonly packages: readonly ManifestEntry[];
readonly warnings: readonly string[];
readonly totals: {
readonly packages: number;
readonly bytes: number;
};
}BundleOptions
Options for a full bundle run.
type BundleOptions = ResolveOptions & {
readonly layout: Layout;
readonly outDir: string;
readonly archiveName?: string | undefined;
readonly dryRun: boolean;
readonly verifyIntegrity: boolean;
readonly force: boolean;
readonly skipExisting?: ReadonlySet<string> | undefined;
readonly overwrite?: ReadonlySet<string> | undefined;
}BundlerError
Discriminant union of every failure the bundler can produce.
type BundlerError = ArchiveError | AuthenticationError | IntegrityError | InvalidInputFileError | InvalidSpecError | LockfileError | LockfileIncompleteError | LockfileOutOfDateError | NoMatchingVersionsError | OutputError | PackageNotFoundError | RegistryResponseError | RegistryUnreachableError | VersionNotFoundErrorBundleResult
The outcome of a bundle run.
type BundleResult = {
readonly resolution: Resolution;
readonly artifacts: readonly BundleArtifact[];
readonly downloadedBytes: number;
readonly unverified: readonly string[];
readonly dryRun: boolean;
}CreateArchiveOptions
What to pack, and where to put it.
type CreateArchiveOptions = {
readonly cwd: string;
readonly entries: readonly string[];
readonly outPath: string;
readonly gzipLevel?: number | undefined;
}DependencyScope
Which dependency edges the resolver follows.
type DependencyScope = {
readonly optional: boolean;
readonly peer: boolean;
readonly platforms: PlatformFilter;
}DependencyTarget
What a dependency edge points at.
type DependencyTarget = {
readonly _tag: "Registry";
readonly name: string;
readonly selector: Selector;
readonly aliasOf?: string | undefined;
} | {
readonly _tag: "Unsupported";
readonly name: string;
readonly raw: string;
readonly reason: string;
}InputFileOptions
Which dependency blocks of a package.json become root specs.
type InputFileOptions = {
readonly includeDev: boolean;
readonly importer?: string | undefined;
}InputFileResult
type InputFileResult = {
readonly kind: InputFileKind;
readonly specs: readonly PackageSpec[];
readonly warnings: readonly string[];
readonly lockfile?: LockedTree | undefined;
readonly required?: readonly string[] | undefined;
}IntegrityAlgorithm
An algorithm this module can verify against.
type IntegrityAlgorithm = "sha1" | "sha256" | "sha384" | "sha512"IntegrityHash
One parsed SRI hash.
type IntegrityHash = {
readonly algorithm: "sha1" | "sha256" | "sha384" | "sha512";
readonly digest: string;
}LockedEdge
One edge out of a locked package, already pinned to an exact version.
type LockedEdge = { readonly name: string; readonly version: string; readonly kind: EdgeKind; }LockedPackage
One package pinned by a lockfile.
type LockedPackage = {
readonly name: string;
readonly version: string;
readonly dependencies: readonly LockedEdge[];
}LockedResolveOptions
Options for a lockfile-pinned resolution.
type LockedResolveOptions = ResolveOptions & {
readonly includeDev: boolean;
readonly declared?: readonly string[] | undefined;
}LockedRoot
A direct dependency of the locked project.
type LockedRoot = {
readonly name: string;
readonly version: string;
readonly kind: LockedRootKind;
readonly specifier?: string | undefined;
}LockedTree
A whole lockfile, flattened.
type LockedTree = {
readonly format: LockfileFormat;
readonly lockfileVersion: string;
readonly path: string;
readonly importer?: string | undefined;
readonly packages: ReadonlyMap<string, LockedPackage>;
readonly roots: readonly LockedRoot[];
readonly warnings: readonly string[];
readonly incomplete: readonly string[];
}LockfileDetection
What a blob of file content turned out to be.
type LockfileDetection = {
readonly _tag: "Supported";
readonly format: LockfileFormat;
} | {
readonly _tag: "Unsupported";
readonly label: string;
readonly hint: string;
} | {
readonly _tag: "NotALockfile";
}LockfileParseOptions
Options for reading a lockfile.
type LockfileParseOptions = { readonly importer?: string | undefined; }ManifestEntry
type ManifestEntry = {
readonly name: string;
readonly version: string;
readonly path: string;
readonly tarball: string;
readonly integrity?: string | undefined;
readonly shasum?: string | undefined;
readonly bytes?: number | undefined;
readonly reasons: readonly string[];
}PackageDist
Where the tarball lives and how to prove it arrived intact.
type PackageDist = {
readonly tarball: string;
readonly integrity?: string | undefined;
readonly shasum?: string | undefined;
readonly unpackedSize?: number | undefined;
readonly fileCount?: number | undefined;
}PackageKey
name@version — the identity of a resolved package throughout the engine.
type PackageKey = stringPackageManifest
A single published version, as the registry describes it.
This is a narrowed view of a package.json — only the fields that affect what has to be downloaded. Unknown fields are ignored rather than rejected, because registries add metadata over time and a bundler that breaks on new metadata is worse than useless.
type PackageManifest = {
readonly name: string;
readonly version: string;
readonly dependencies?: Readonly<Record<string, string>> | undefined;
readonly optionalDependencies?: Readonly<Record<string, string>> | undefined;
readonly peerDependencies?: Readonly<Record<string, string>> | undefined;
readonly peerDependenciesMeta?: Readonly<Record<string, {
readonly optional?: boolean | undefined;
}>> | undefined;
readonly devDependencies?: Readonly<Record<string, string>> | undefined;
readonly bundleDependencies?: readonly string[] | undefined;
readonly os?: readonly string[] | undefined;
readonly cpu?: readonly string[] | undefined;
readonly libc?: readonly string[] | undefined;
readonly deprecated?: string | undefined;
readonly dist: PackageDist;
}PackageSpec
A parsed, validated package spec.
type PackageSpec = { readonly name: string; readonly selector: Selector; readonly raw: string; }Packument
Everything the registry knows about one package.
type Packument = {
readonly name: string;
readonly distTags: Readonly<Record<string, string>>;
readonly versions: Readonly<Record<string, PackageManifest>>;
}PlannedOutput
The files a run will write, known before anything is downloaded.
dir and flat are the odd ones: they merge a package tree into outDir
rather than writing an archive. Either way a package's path is derived from
its name and version, so the same package at the same version is the same
bytes at the same path — an overlap is idempotent rather than destructive.
Only the two summary files are genuinely replaced, and those are the ones
worth guarding.
type PlannedOutput = {
readonly file: string;
readonly name?: string | undefined;
readonly version?: string | undefined;
}PlanSummary
A summary of what a resolution would produce, for deciding whether the chosen layout is still the right one.
type PlanSummary = {
readonly uniquePackages: number;
readonly perSpecArchives: number;
readonly perSpecEntries: number;
}PlatformConstraints
Constraints declared by a package, as they appear in its manifest.
type PlatformConstraints = {
readonly os?: readonly string[] | undefined;
readonly cpu?: readonly string[] | undefined;
readonly libc?: readonly string[] | undefined;
}PlatformFilter
Which platforms optional dependencies should be resolved for.
All— keep everything, regardless ofos/cpu/libc(the default).Targets— keep only packages that could install on one of these.
type PlatformFilter = {
readonly _tag: "All";
} | {
readonly _tag: "Targets";
readonly targets: readonly PlatformTarget[];
}PlatformTarget
A target to keep, e.g. linux-x64, linux-x64-musl, or just linux.
type PlatformTarget = {
readonly os: string;
readonly cpu?: string | undefined;
readonly libc?: string | undefined;
}ProgressEvent
A structured progress event.
type ProgressEvent = {
readonly _tag: "PhaseStarted";
readonly phase: Phase;
readonly total?: number | undefined;
} | {
readonly _tag: "PhaseCompleted";
readonly phase: Phase;
} | {
readonly _tag: "PackageResolved";
readonly name: string;
readonly version: string;
readonly resolvedCount: number;
readonly pendingCount: number;
} | {
readonly _tag: "DownloadStarted";
readonly name: string;
readonly version: string;
} | {
readonly _tag: "DownloadCompleted";
readonly name: string;
readonly version: string;
readonly bytes: number;
readonly completedCount: number;
readonly totalCount: number;
} | {
readonly _tag: "DownloadRetrying";
readonly name: string;
readonly version: string;
readonly attempt: number;
readonly reason: string;
} | {
readonly _tag: "ArchiveStarted";
readonly path: string;
readonly entryCount: number;
} | {
readonly _tag: "ArchiveCompleted";
readonly path: string;
readonly bytes: number;
} | {
readonly _tag: "Warning";
readonly message: string;
}Reason
A single justification for including a package.
type Reason = {
readonly _tag: "Root";
readonly spec: string;
} | {
readonly _tag: "Edge";
readonly from: string;
readonly kind: EdgeKind;
}Resolution
The complete result of resolving every requested spec.
type Resolution = {
readonly roots: readonly RootResolution[];
readonly packages: readonly ResolvedPackage[];
readonly warnings: readonly ResolutionWarning[];
}ResolutionWarning
Something we could not include, reported rather than thrown.
type ResolutionWarning = { readonly message: string; readonly from?: string | undefined; }ResolvedPackage
One package, pinned to one version, with every reason it was pulled in.
type ResolvedPackage = {
readonly name: string;
readonly version: string;
readonly manifest: PackageManifest;
readonly reasons: readonly Reason[];
}ResolveOptions
Options governing version selection and the dependency walk.
type ResolveOptions = {
readonly scope: DependencyScope;
readonly allVersions: boolean;
readonly maxVersions?: number | undefined;
readonly includePrerelease: boolean;
readonly concurrency: number;
}RootResolution
What one requested spec expanded to.
type RootResolution = {
readonly spec: PackageSpec;
readonly versions: readonly string[];
readonly closures: ReadonlyMap<string, readonly string[]>;
readonly closure: readonly string[];
}Selector
How a spec narrows down which version(s) of a package we want.
type Selector = {
readonly _tag: "Exact";
readonly version: string;
} | {
readonly _tag: "Range";
readonly range: string;
} | {
readonly _tag: "Tag";
readonly tag: string;
}ValueOf
The union of an object's value types.
The companion to the as const object-enum pattern used throughout this
package: the object is the value, ValueOf<typeof X> is the type, and the two
share one name.
type ValueOf = T[keyof T]VerificationResult
Result of checking a download.
type VerificationResult = {
readonly _tag: "Verified";
readonly using: string;
} | {
readonly _tag: "Unverifiable";
readonly reason: string;
} | {
readonly _tag: "Mismatch";
readonly expected: string;
readonly actual: string;
}Errors
ArchiveError
Something went wrong writing the .tgz.
class ArchiveError {
new (path: string, detail: string, options: { cause?: unknown; } | undefined): ArchiveError
readonly _tag: "ArchiveError"
readonly path: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}AuthenticationError
401/403 — almost always a missing or stale token in .npmrc.
class AuthenticationError {
new (registry: string, status: number, packageName: string | undefined): AuthenticationError
readonly _tag: "AuthenticationError"
readonly registry: string
readonly status: number
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}IntegrityError
A downloaded tarball did not match the checksum the registry advertised.
Never soft-fail this: a bundle is a supply-chain artifact and a corrupt or substituted tarball is exactly what integrity checking exists to catch.
class IntegrityError {
new (packageName: string, version: string, expected: string, actual: string): IntegrityError
readonly _tag: "IntegrityError"
readonly packageName: string
readonly version: string
readonly expected: string
readonly actual: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}InvalidInputFileError
--file pointed at something that is neither a usable package.json nor a spec list.
class InvalidInputFileError {
new (path: string, reason: string, options: {
cause?: unknown;
} | undefined): InvalidInputFileError
readonly _tag: "InvalidInputFileError"
readonly path: string
readonly reason: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}InvalidSpecError
A package spec on the command line or in an input file could not be parsed.
class InvalidSpecError {
new (spec: string, reason: string): InvalidSpecError
readonly _tag: "InvalidSpecError"
readonly spec: string
readonly reason: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}LockfileError
A lockfile could not be read, or is in a format we do not support.
class LockfileError {
new (path: string, reason: string, options: { cause?: unknown; } | undefined): LockfileError
readonly _tag: "LockfileError"
readonly path: string
readonly reason: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}LockfileIncompleteError
The lockfile parsed, but does not pin everything the bundle needs.
Falling back to a range here would defeat the entire point of reading a
lockfile — you would get a bundle that is mostly what CI installed, with no
indication of which parts were guessed. So this is fatal, and it names every
gap at once so one npm install fixes all of them.
class LockfileIncompleteError {
new (path: string, missing: readonly string[], detail: string, remedy: string): LockfileIncompleteError
readonly _tag: "LockfileIncompleteError"
readonly path: string
readonly missing: readonly string[]
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}LockfileOutOfDateError
The lockfile pins versions the registry no longer serves.
Unpublished, or never mirrored into a private registry. Either way the bundle cannot be built as specified, and quietly substituting a nearby version would produce exactly the mismatch this feature exists to prevent.
class LockfileOutOfDateError {
new (path: string, registry: string, missing: readonly {
readonly name: string;
readonly version: string;
readonly detail: string;
}[]): LockfileOutOfDateError
readonly _tag: "LockfileOutOfDateError"
readonly path: string
readonly registry: string
readonly missing: readonly { readonly name: string; readonly version: string; }[]
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}NoMatchingVersionsError
A range (or dist-tag) matched nothing that is actually published.
class NoMatchingVersionsError {
new (packageName: string, selector: string, available: readonly string[]): NoMatchingVersionsError
readonly _tag: "NoMatchingVersionsError"
readonly packageName: string
readonly selector: string
readonly available: readonly string[]
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}OutputError
Something went wrong preparing or writing to the output directory.
class OutputError {
new (path: string, detail: string, options: { cause?: unknown; } | undefined): OutputError
readonly _tag: "OutputError"
readonly path: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}PackageNotFoundError
The registry answered, but has never heard of this package.
class PackageNotFoundError {
new (packageName: string, registry: string): PackageNotFoundError
readonly _tag: "PackageNotFoundError"
readonly packageName: string
readonly registry: string
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}RegistryResponseError
The registry responded, but with something we cannot use.
class RegistryResponseError {
new (url: string, detail: string, options: {
cause?: unknown;
status?: number | undefined;
} | undefined): RegistryResponseError
readonly _tag: "RegistryResponseError"
readonly url: string
readonly status: number | undefined
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}RegistryUnreachableError
The registry could not be reached at all — DNS failure, refused connection, a certificate the machine does not trust, proxy blackhole, or a preflight that timed out.
This is the error that fixes "hangs forever with no network": we surface it quickly and say which host we could not reach.
class RegistryUnreachableError {
new (registry: string, detail: string, options: {
cause?: unknown;
timeoutMs?: number | undefined;
hint?: string | undefined;
} | undefined): RegistryUnreachableError
readonly _tag: "RegistryUnreachableError"
readonly registry: string
readonly timeoutMs: number | undefined
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}VersionNotFoundError
The package exists but the exact version requested does not.
class VersionNotFoundError {
new (packageName: string, version: string, available: readonly string[]): VersionNotFoundError
readonly _tag: "VersionNotFoundError"
readonly packageName: string
readonly version: string
readonly available: readonly string[]
readonly name: string
readonly message: string
readonly stack: string | undefined
readonly cause: unknown
}