[bldr]docs
Build APIbldr

bldr

The core build API: artifacts, pods, images, scopes and targets.

The core build API: artifacts, pods, images, scopes and targets.

Import from bldr in a .bldr.ts. Generated from the module's TypeScript.

Contents

AllOfPipelineConditionclassConjunction of conditions.
AnyOfPipelineConditionclassDisjunction of conditions.
ArtifactclassA content-addressed build artifact: a lazy, memoized computation of a CID.
buildArchitecturefunction
cacheVolumefunction
CacheVolumeclassA named cache volume, mountable into a pod with Pod.cache.
claimfunction
CompressionvariableConstructors for the supported codecs.
ContainerImageclassA content-addressed build artifact: a lazy, memoized computation of a CID.
CoverageclassA content-addressed build artifact: a lazy, memoized computation of a CID.
DebianContainerImageclassA Debian/Ubuntu base image, with apt-get spelled once.
DEFAULT_ARCHITECTURE_OPTIONvariableThe workspace option the node fills in with its host architecture.
defaultArchitecturefunction
DeploymentclassA content-addressed build artifact: a lazy, memoized computation of a CID.
DeploymentPipelineActionclassRun a deployment into an environment.
DiagnosticsclassA content-addressed build artifact: a lazy, memoized computation of a CID.
DirectoryclassA content-addressed build artifact: a lazy, memoized computation of a CID.
encodeUtf8function
EnvironmentclassA deployment environment: a name plus free-form labels.
exitedWithfunction
FileclassA content-addressed build artifact: a lazy, memoized computation of a CID.
fromBiomeJsonfunction
fromCargoJsonfunction
fromJestJsonfunction
fromLcovfunction
fromLibtestJsonfunction
fromLlvmJsonfunction
httpGetfunction
httpPostfunction
httpPutfunction
invokefunction
invokeJsonfunction
isArtifactfunction
languageServerfunction
languageServerLabelsfunction
linkfunction
locatefunction
LSP_CLAIMS_LABELvariableWhich files this server answers about, relative to a mounted tree, as
LSP_PIPE_DIRvariableWhere a v1 language server reads and writes.
LSP_PIPE_INvariable
LSP_PIPE_OUTvariable
LSP_PRIORITY_LABELvariableWhich server wins when two claim the same file, as an integer. Higher wins;
LSP_RUNNABLE_LABELvariableThe label the daemon looks for, and the contract version it names.
LSP_RUNNABLE_VERSIONvariable
LSP_SERVICEvariableThe service name a single-server runnable uses.
memberScopefunction
MetricsclassA content-addressed build artifact: a lazy, memoized computation of a CID.
metricsFromCoveragefunction
metricsFromPodDagfunction
metricsFromTestResultsfunction
metricsTransformfunction
narrowSourcesfunction
nestSourcesfunction
nodeCapabilitiesfunction
nodeSupportsfunction
NoopPipelineActionclassNo action: the step exists to sequence or gate others. What a step stores
NotPipelineConditionclassNegation of one condition.
PipelineclassA content-addressed build artifact: a lazy, memoized computation of a CID.
PipelineActionclassWhat a step does. Abstract: the family is open — each concrete action
PipelineConditionclassA gate on a step. Abstract: the family is open — each concrete condition
PipelineStepclassOne step of a pipeline. Constructed by Pipeline.addStep — the value
Podclass
registrationsfunction
ReportclassA content-addressed build artifact: a lazy, memoized computation of a CID.
resetRegistrationsfunction
rootScopefunction
runnablefunction
RunnableclassA runnable builder. Fluent: declare network(...)s and service(...)s, then
SCCACHE_BIN_DIRvariableWhere the node mounts the sccache wrapper inside the pod. The node also
SCCACHE_CAPABILITYvariableThe node capability a pod needs before it may ask for sccache.
SCCACHE_SOCKETvariableWhere the node binds the pod's sccache socket ($SCCACHE_SERVER_UDS).
sccacheCargoConfigfunction
sccacheSupportedfunction
ScopeclassA named place in the build tree.
setWorkspaceOptionsfunction
shquotefunction
tailLinesfunction
TimeWindowPipelineConditionclassA cron-style time window: holds when the current time matches every field.
toYamlfunction
unlinkfunction
ValueclassA lazily-decoded non-CID value read out of the graph — an image's
workspaceOptionfunction
workspaceOptionsfunction
yamlDocumentsfunction

Types

Each has its own page.

AptInstallOptionsinterfaceKnobs for DebianContainerImage.aptInstall. Every one defaults to
ArtifactKindtypeWhich kind of artifact — the phantom brand that makes the subclasses
ArtifactSourcetypeWhat an Artifact can be built from: a resolved CID, another lazy CID,
BuildEntrytypeA member's build function: given its root Scope, declare what it
CacheModetypeHow a volume is shared with concurrent holders. RwLock, essentially.
CacheVolumeOptionsinterfaceOptions for cacheVolume.
CidtypeA content-addressed block identifier.
CidSourcetypeSomething that resolves to a content-addressed CID: a ContainerImage / Directory
CommandtypeThe command to run: a shell string, an explicit argv, or a thunk of either
CompositeBlocktypeThe options of a composite deployment: exactly one of the block forms.
CompositeCallinterfaceA child deployment run by a composite: the function and its options.
CompositeDaginterfaceNamed child deployments run one at a time in an order that respects deps.
CompositeDagNodeinterfaceA node of the DAG block: a named child deployment and the names it waits for.
CompositeParallelinterfaceSteps that run concurrently; the block waits for all of them and fails if
CompositeSequenceinterfaceSteps that run one after another; the first failure stops the block and is
CompositeSteptypeOne step of a block: a child deployment, or another block written inline.
CompositeTryinterfacetry runs; catch runs only when try failed; finally runs in every
CompressiontypeConstructors for the supported codecs.
ConfigPatchinterfaceA partial config to set (used by ContainerImage.mapConfig). Each
ContainerImageMemberinterfaceA member whose content is an OCI image manifest.
CoverageBucketRuleinterfaceA rule routing paths into the generated or external bucket.
CoverageConvertOptionsinterfaceOptions shared by every coverage.from-* conversion.
CoverageMappinginterfaceOne path prefix and the member it belongs to.
DagMemberinterfaceA member whose content is a DAG-CBOR object rather than files or an image.
DagResourcesinterfaceWhat a whole DAG is granted: the budget its pods share, not a per-pod limit.
DeploymentSpecinterfaceA deployment definition. Mirrors bldr_types::build_output::Deployment:
DiagnosticsOptionsinterfaceHow a converter should resolve the paths a report names.
DiffResultinterfaceThe result of Directory.diff: paths added / removed / modified.
DirectoryMemberinterfaceA member whose content is a filesystem tree.
EnvironmentPropsinterfaceProps for Environment.
HttpOptionsinterface
HttpResponseinterface
ImageConfigInfointerfaceAn image's effective config, as read by ContainerImage.config.
LanguageServerinterfaceA language server ready to register: the runnable that runs it, and what it
LanguageServerOptionsinterfaceHow to wrap one stdio language server as a v1 service.
LayerInfointerfaceOne layer of an image, as read by ContainerImage.layers.
MembertypeA workspace member, as seen from build code.
MemberKindtype
MetadatainterfaceMetadata to set on a path (see Directory.setMetadata).
MetricsTagOptionsinterfaceExtra tags stamped on every produced row.
MetricsTransformOptionsinterfaceRow selection + aggregation, mirroring the GetBuildMetrics query.
MountOptionsinterfaceOptions for Pod.mount.
OtherMemberinterfaceA member of a kind this API has no typed accessor for. cid still works.
PipelineSpecinterfaceA pipeline definition's own props. Steps are not part of the spec: they are
PipelineStepPropsinterfaceProps for Pipeline.addStep. Optional fields default — see the
RegisteredDeploymentinterfaceA registered deployment: a node function plus the options to invoke it with.
RegistrationsinterfaceEverything the members' build functions declared, keyed by target path.
RunMountDefinterfaceAn Directory (build artifact) mounted into a service.
RunOptionsinterfaceKnobs for ContainerImage.run.
RunPortinterfaceA host→service port forward.
RunServiceDefinterfaceA service (a long-lived pod) in a runnable.
SccacheModetypeHow a pod may use the shared cache.
SccacheOptionsinterfaceWhat Pod.sccache accepts.
ScopeOptionsinterfaceExtra metadata for a scope, shown against its node in the build view.
SourceOrigininterfaceOne subtree of a composed tree, and where it came from.
TimeWindowPropsinterfaceProps for TimeWindowPipelineCondition: cron field expressions.

AllOfPipelineCondition

class

Conjunction of conditions.

.constructor(conditions: PipelineCondition[])

constructor

constructor(conditions: PipelineCondition[]): AllOfPipelineCondition

See PipelineCondition.

conditions: readonly PipelineCondition[]

property

See PipelineCondition.

.allOf(...conditions: PipelineCondition[])

static method

Holds when every one of conditions holds (none: always holds).

allOf(...conditions: PipelineCondition[]): AllOfPipelineCondition

See PipelineCondition.

.anyOf(...conditions: PipelineCondition[])

static method

Holds when at least one of conditions holds (none: never holds).

anyOf(...conditions: PipelineCondition[]): AnyOfPipelineCondition

See AnyOfPipelineCondition, PipelineCondition.

.noneOf(...conditions: PipelineCondition[])

static method

Holds when none of conditions hold — not(anyOf(…)), spelled the way a reader thinks it.

noneOf(...conditions: PipelineCondition[]): NotPipelineCondition

See NotPipelineCondition, PipelineCondition.

.not(condition: PipelineCondition)

static method

Holds when condition does not.

not(condition: PipelineCondition): NotPipelineCondition

See NotPipelineCondition, PipelineCondition.

AnyOfPipelineCondition

class

Disjunction of conditions.

.constructor(conditions: PipelineCondition[])

constructor

constructor(conditions: PipelineCondition[]): AnyOfPipelineCondition

See PipelineCondition.

conditions: readonly PipelineCondition[]

property

See PipelineCondition.

.allOf(...conditions: PipelineCondition[])

static method

Holds when every one of conditions holds (none: always holds).

allOf(...conditions: PipelineCondition[]): AllOfPipelineCondition

See AllOfPipelineCondition, PipelineCondition.

.anyOf(...conditions: PipelineCondition[])

static method

Holds when at least one of conditions holds (none: never holds).

anyOf(...conditions: PipelineCondition[]): AnyOfPipelineCondition

See PipelineCondition.

.noneOf(...conditions: PipelineCondition[])

static method

Holds when none of conditions hold — not(anyOf(…)), spelled the way a reader thinks it.

noneOf(...conditions: PipelineCondition[]): NotPipelineCondition

See NotPipelineCondition, PipelineCondition.

.not(condition: PipelineCondition)

static method

Holds when condition does not.

not(condition: PipelineCondition): NotPipelineCondition

See NotPipelineCondition, PipelineCondition.

Artifact

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): Artifact

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

buildArchitecture()

function

The architecture this build's own pods run on: the node's, as published in the build's options, or amd64 under a daemon that does not fill it in.

What a tool defaults to when it must pick one platform of a multi-architecture image to run: an image for another architecture does not start (exec format error). A tool that produces something for another architecture names it instead.

buildArchitecture(): string

cacheVolume(name: string, opts?: CacheVolumeOptions)

function

Declare a cache volume named name, namespaced by opts.scope if given.

Two calls with the same resulting name are the same volume — that is the point, and it is why the namespacing is explicit rather than automatic: a volume shared on purpose and one shared by accident look identical afterwards.

cacheVolume(name: string, opts?: CacheVolumeOptions): CacheVolume

See CacheVolume, CacheVolumeOptions.

CacheVolume

class

A named cache volume, mountable into a pod with Pod.cache.

const downloads = cacheVolume("npm-cache");                       // global
const target = cacheVolume("target", { scope, mode: "exclusive" }); // per-crate
pod.cache(downloads, "/tmp/.npm-cache").cache(target, "/work/target");

.constructor(name: string, mode: CacheMode, perImage: boolean)

constructor

constructor(name: string, mode: CacheMode, perImage: boolean): CacheVolume

See CacheMode.

mode: CacheMode

property

See CacheMode.

name: string

property

The volume's full name — what the daemon keys the workspace's cache on.

perImage: boolean

property

Whether each rootfs image gets its own copy — see CacheVolumeOptions.perImage.

exclusive

accessor

claim(path: string, kind: string)

function

Claim path for one kind of registration, or say who already has it.

Names are checked across every kind, not per-kind: bldr/daemon/docs as both an output and a test would be two different things reachable by one name, and which one a caller got would depend on which map was searched first. One name, one thing.

claim(path: string, kind: string): void

Compression: { … }

variable

Constructors for the supported codecs.

ContainerImage

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(image: string | Lazy<string>, arch?: string, overrides?: Overrides)

constructor

image is a manifest CID (or a lazy one). arch selects the platform for a multi-arch image and is fixed for this image and all images derived from it. overrides is internal (carried through the chain).

constructor(image: string | Lazy<string>, arch?: string, overrides?: Overrides): ContainerImage

architecture

accessor

nodeLabel

accessor

shape

accessor

valueScope

accessor

.add(src: Directory, dest: string)

method

ADD — like copy for a local tree. (URL sources and tar auto-extraction are not supported; use copy + run tar.)

add(src: Directory, dest: string): ContainerImage

See Directory.

.arg(key: string, value?: string)

method

ARG — a build-time variable (available to mapConfig callbacks via buildArgs; not persisted to the image).

arg(key: string, value?: string): ContainerImage

.buildArgs()

method

The build-time args set so far (for use in derived logic).

buildArgs(): Record<string, string>

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.cmd(argv: string[])

method

CMD (exec form).

cmd(argv: string[]): ContainerImage

.config()

method

The effective config (base image config with this image's overrides merged).

Memoized and placed, which is the only combination that is honest. The value is read once and shared by every consumer (pod, run, layers, build code), so an unplaced one would be attributed to whichever of them won the race to force it — the very mis-attribution ContainerImage.fromMember places the image to avoid. Placing it in the image's scope makes the winner irrelevant: a Lazy with a scope of its own ignores the slot a consumer offers. An image with no scope keeps the old behaviour, because there is nothing better to say.

config(): Value<ImageConfigInfo>

See ImageConfigInfo, Value.

.copy(src: Directory, dest: string)

method

COPY — the tree src becomes a new layer at dest (its contents land under dest). Lazy.

A content-level graft (fs.at), not a pod: the layer is src placed under dest, with its modes and symlinks as they are. No process runs, which is what lets an image for another architecture be assembled on this one — a cp inside an arm64 rootfs would need an arm64 CPU.

copy(src: Directory, dest: string): ContainerImage

See Directory.

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.entrypoint(argv: string[])

method

ENTRYPOINT (exec form).

entrypoint(argv: string[]): ContainerImage

.env(key: string, value: string)

method

ENV KEY VALUE.

env(key: string, value: string): ContainerImage

.envAll(vars: Record<string, string>)

method

ENV for several variables at once.

envAll(vars: Record<string, string>): ContainerImage

.expose(...ports: string[])

method

EXPOSE — document a port ("8080" or "8080/tcp").

expose(...ports: string[]): ContainerImage

.fromOci()

method

Convert to bldr's native layer form: every .tar blob layer is untarred into an FsNode (cheap to build on / diff / hot-swap). The inverse of toOci. (oci.image.from-oci.)

fromOci(): ContainerImage

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.label(key: string, value: string)

method

LABEL key=value.

label(key: string, value: string): ContainerImage

.labels(map: Record<string, string>)

method

LABEL for several labels at once.

labels(map: Record<string, string>): ContainerImage

.layers()

method

The ordered layer list (base-first).

layers(): Value<LayerInfo[]>

See LayerInfo, Value.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.mapConfig(fn: (…) => …)

method

Read this image's effective config, transform it in the fn callback (running lazily), and write the returned patch back as the image's config. The new image carries the result baked in.

const bumped = img.mapConfig((cfg) => ({
    labels: { ...cfg.labels, "build.n": String(Number(cfg.labels["build.n"] ?? 0) + 1) },
}));
mapConfig(fn: (…) => …): ContainerImage

See ConfigPatch, ImageConfigInfo.

.mapLabels(fn: (…) => …)

method

Read all labels, transform the map in fn, and write the result back. A convenience over mapConfig for the common label case.

const img2 = img.mapLabels((labels) => ({
    ...labels,
    "org.opencontainers.image.revision": gitSha,
}));
mapLabels(fn: (…) => …): ContainerImage

.pod()

method

A Pod over this image's rootfs, pre-seeded with the image's effective env (so PATH etc. apply) and its WORKDIR/USER/SHELL. Set the command and take outputs as usual.

pod(): Pod

See Pod.

.root()

method

The merged root filesystem of the image (layers). (Config does not affect the rootfs.) Placed in this image's scope, like every other derivation of it: materializing a shared base image's rootfs is that image's own work, not the work of the first pod to ask for it.

root(): Directory

See Directory.

.run(command: Command, opts?: RunOptions)

method

RUN — run command over this image's rootfs and append the resulting overlay diff as a new layer. A shell string runs through the image's SHELL/WORKDIR/USER with its ENV applied; an argv runs directly (exec form). Lazy.

The layer's pod has no network unless { internet: true } approves it — see RunOptions.

run(command: Command, opts?: RunOptions): ContainerImage

See Command, RunOptions.

.shell(argv: string[])

method

SHELL — the shell used for run shell-form commands. Build-time.

shell(argv: string[]): ContainerImage

.stopSignal(signal: string)

method

STOPSIGNAL.

stopSignal(signal: string): ContainerImage

.toOci()

method

Convert to the OCI layer form: every native layer (an FsNode overlay diff, as run/copy produce) is tarred + gzipped into a blob with real content digests, and the config's rootfs.diff_ids are rebuilt — a standard, registry-pushable image. (oci.image.to-oci.)

toOci(): ContainerImage

.user(user: string)

method

USER — default user for run (shell form) and the image.

user(user: string): ContainerImage

.volume(...paths: string[])

method

VOLUME — declare a mount point as a volume.

volume(...paths: string[]): ContainerImage

.workdir(dir: string)

method

WORKDIR — default directory for run (shell form) and the image.

workdir(dir: string): ContainerImage

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.from(image: string | Lazy<string>, arch?: string)

static method

FROM — start from a base image manifest CID at architecture arch.

from(image: string | Lazy<string>, arch?: string): ContainerImage

.fromMember(member: ImageMemberRef, arch?: string)

static method

FROM an image member — the form to prefer when the base is a workspace member (import rust from "rust").

The image is placed in that member's own scope, so everything derived from it (an apt-get, a copy, the pods those run) lands in the member's subtree. A shared base image has no other honest home: placing it in a consumer's scope would attribute it to whichever member forced it first, and leaving it unplaced strands its work at the tree root.

fromMember(member: ImageMemberRef, arch?: string): ContainerImage

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

Coverage

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): Coverage

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Coverage from an existing DataType::CoverageReport CID.

fromCid(cid: string): Coverage

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

DebianContainerImage

class

A Debian/Ubuntu base image, with apt-get spelled once.

Every callsite of apt-get in a build has to remember the same three things: refresh the lists first, refuse recommends, and delete the lists in the same layer — a separate cleanup .run() writes a new layer over the old one and the bytes stay in the image. Writing that by hand at each callsite is how one of them ends up missing a piece.

new DebianContainerImage(ContainerImage.from(DEBIAN))
    .aptInstall(["curl", "ca-certificates"])

Nothing else about the image changes: this is ContainerImage with one extra verb, so .run(), .copy(), .env() and the rest behave exactly as they do on the base class.

.constructor(image: ContainerImage)

constructor

Wrap image — its baked manifest becomes this image's base, so the config it carries (env, workdir, user, shell) comes along.

constructor(image: ContainerImage): DebianContainerImage

See ContainerImage.

architecture

accessor

nodeLabel

accessor

shape

accessor

valueScope

accessor

.add(src: Directory, dest: string)

method

ADD — like copy for a local tree. (URL sources and tar auto-extraction are not supported; use copy + run tar.)

add(src: Directory, dest: string): ContainerImage

See ContainerImage, Directory.

.aptInstall(packageNames: string | string[], opts?: AptInstallOptions)

method

apt-get install as one layer: update, install, and drop the package lists, joined with && so a failure anywhere fails the layer.

The layer's pod is approved for the internet: installing packages is fetching them.

Returns a DebianContainerImage, so installs chain.

aptInstall(packageNames: string | string[], opts?: AptInstallOptions): DebianContainerImage

See AptInstallOptions.

.arg(key: string, value?: string)

method

ARG — a build-time variable (available to mapConfig callbacks via buildArgs; not persisted to the image).

arg(key: string, value?: string): ContainerImage

See ContainerImage.

.buildArgs()

method

The build-time args set so far (for use in derived logic).

buildArgs(): Record<string, string>

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.cmd(argv: string[])

method

CMD (exec form).

cmd(argv: string[]): ContainerImage

See ContainerImage.

.config()

method

The effective config (base image config with this image's overrides merged).

Memoized and placed, which is the only combination that is honest. The value is read once and shared by every consumer (pod, run, layers, build code), so an unplaced one would be attributed to whichever of them won the race to force it — the very mis-attribution ContainerImage.fromMember places the image to avoid. Placing it in the image's scope makes the winner irrelevant: a Lazy with a scope of its own ignores the slot a consumer offers. An image with no scope keeps the old behaviour, because there is nothing better to say.

config(): Value<ImageConfigInfo>

See ImageConfigInfo, Value.

.copy(src: Directory, dest: string)

method

COPY — the tree src becomes a new layer at dest (its contents land under dest). Lazy.

A content-level graft (fs.at), not a pod: the layer is src placed under dest, with its modes and symlinks as they are. No process runs, which is what lets an image for another architecture be assembled on this one — a cp inside an arm64 rootfs would need an arm64 CPU.

copy(src: Directory, dest: string): ContainerImage

See ContainerImage, Directory.

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.entrypoint(argv: string[])

method

ENTRYPOINT (exec form).

entrypoint(argv: string[]): ContainerImage

See ContainerImage.

.env(key: string, value: string)

method

ENV KEY VALUE.

env(key: string, value: string): ContainerImage

See ContainerImage.

.envAll(vars: Record<string, string>)

method

ENV for several variables at once.

envAll(vars: Record<string, string>): ContainerImage

See ContainerImage.

.expose(...ports: string[])

method

EXPOSE — document a port ("8080" or "8080/tcp").

expose(...ports: string[]): ContainerImage

See ContainerImage.

.fromOci()

method

Convert to bldr's native layer form: every .tar blob layer is untarred into an FsNode (cheap to build on / diff / hot-swap). The inverse of toOci. (oci.image.from-oci.)

fromOci(): ContainerImage

See ContainerImage.

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.label(key: string, value: string)

method

LABEL key=value.

label(key: string, value: string): ContainerImage

See ContainerImage.

.labels(map: Record<string, string>)

method

LABEL for several labels at once.

labels(map: Record<string, string>): ContainerImage

See ContainerImage.

.layers()

method

The ordered layer list (base-first).

layers(): Value<LayerInfo[]>

See LayerInfo, Value.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.mapConfig(fn: (…) => …)

method

Read this image's effective config, transform it in the fn callback (running lazily), and write the returned patch back as the image's config. The new image carries the result baked in.

const bumped = img.mapConfig((cfg) => ({
    labels: { ...cfg.labels, "build.n": String(Number(cfg.labels["build.n"] ?? 0) + 1) },
}));
mapConfig(fn: (…) => …): ContainerImage

See ConfigPatch, ContainerImage, ImageConfigInfo.

.mapLabels(fn: (…) => …)

method

Read all labels, transform the map in fn, and write the result back. A convenience over mapConfig for the common label case.

const img2 = img.mapLabels((labels) => ({
    ...labels,
    "org.opencontainers.image.revision": gitSha,
}));
mapLabels(fn: (…) => …): ContainerImage

See ContainerImage.

.pod()

method

A Pod over this image's rootfs, pre-seeded with the image's effective env (so PATH etc. apply) and its WORKDIR/USER/SHELL. Set the command and take outputs as usual.

pod(): Pod

See Pod.

.root()

method

The merged root filesystem of the image (layers). (Config does not affect the rootfs.) Placed in this image's scope, like every other derivation of it: materializing a shared base image's rootfs is that image's own work, not the work of the first pod to ask for it.

root(): Directory

See Directory.

.run(command: Command, opts?: RunOptions)

method

RUN — run command over this image's rootfs and append the resulting overlay diff as a new layer. A shell string runs through the image's SHELL/WORKDIR/USER with its ENV applied; an argv runs directly (exec form). Lazy.

The layer's pod has no network unless { internet: true } approves it — see RunOptions.

run(command: Command, opts?: RunOptions): ContainerImage

See Command, ContainerImage, RunOptions.

.shell(argv: string[])

method

SHELL — the shell used for run shell-form commands. Build-time.

shell(argv: string[]): ContainerImage

See ContainerImage.

.stopSignal(signal: string)

method

STOPSIGNAL.

stopSignal(signal: string): ContainerImage

See ContainerImage.

.toOci()

method

Convert to the OCI layer form: every native layer (an FsNode overlay diff, as run/copy produce) is tarred + gzipped into a blob with real content digests, and the config's rootfs.diff_ids are rebuilt — a standard, registry-pushable image. (oci.image.to-oci.)

toOci(): ContainerImage

See ContainerImage.

.user(user: string)

method

USER — default user for run (shell form) and the image.

user(user: string): ContainerImage

See ContainerImage.

.volume(...paths: string[])

method

VOLUME — declare a mount point as a volume.

volume(...paths: string[]): ContainerImage

See ContainerImage.

.workdir(dir: string)

method

WORKDIR — default directory for run (shell form) and the image.

workdir(dir: string): ContainerImage

See ContainerImage.

.aptInstallScript(packageNames: string | string[], opts?: AptInstallOptions)

static method

The same shell one-liner aptInstall runs, for the callsites that cannot take it as a layer of its own.

Some images install packages in the middle of a longer script — after writing an apt.conf.d fragment the install depends on, or with a tolerated failure the surrounding steps recover from. Splitting those into three layers to use the method would cost two extra pods and change what is cached, so they compose the string instead and keep one .run() — which has to pass { internet: true } itself, since the script fetches.

aptInstallScript(packageNames: string | string[], opts?: AptInstallOptions): string

See AptInstallOptions.

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.from(image: string | Lazy<string>, arch?: string)

static method

FROM — start from a base image manifest CID at architecture arch.

from(image: string | Lazy<string>, arch?: string): ContainerImage

See ContainerImage.

.fromMember(member: ImageMemberRef, arch?: string)

static method

FROM an image member — the form to prefer when the base is a workspace member (import rust from "rust").

The image is placed in that member's own scope, so everything derived from it (an apt-get, a copy, the pods those run) lands in the member's subtree. A shared base image has no other honest home: placing it in a consumer's scope would attribute it to whichever member forced it first, and leaving it unplaced strands its work at the tree root.

fromMember(member: ImageMemberRef, arch?: string): ContainerImage

See ContainerImage.

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

DEFAULT_ARCHITECTURE_OPTION: "bldr.default-architecture"

variable

The workspace option the node fills in with its host architecture.

defaultArchitecture(options: unknown)

function

The architecture this build is for, in OCI spelling ("amd64", "arm64").

const arch = defaultArchitecture(input.options);
const img = new ContainerImage(base, arch);

undefined when the build runs against a daemon old enough not to fill the option in — treat that as "unknown", and fall back to whatever the build assumed before it could ask.

defaultArchitecture(options: unknown): string

Deployment

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource | DeploymentSpec, labelHint?: string)

constructor

A deployment authored in place — new Deployment({ function: "deployment/local.shell/v0.0.0", … }).

The ArtifactSource form is what fromCid and Artifact.dynamic use to wrap an existing CID; build code writes the spec form.

constructor(source: ArtifactSource | DeploymentSpec, labelHint?: string): Deployment

See ArtifactSource, DeploymentSpec.

COMPOSITE_FUNCTION: "deployment/composite/v0.0.0"

static property

The composite deployment function: runs other deployments as one.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.composite(block: CompositeBlock, labels?: Record<string, string>)

static method

A composite deployment authored in place: block as the options of Deployment.COMPOSITE_FUNCTION.

composite(block: CompositeBlock, labels?: Record<string, string>): Deployment

See CompositeBlock.

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Deployment from an existing DataType::Deployment CID.

fromCid(cid: string): Deployment

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

.parallel(steps: CompositeStep[])

static method

A block whose steps run concurrently — see CompositeParallel.

parallel(steps: CompositeStep[]): CompositeParallel

See CompositeParallel, CompositeStep.

.sequence(steps: CompositeStep[])

static method

A block whose steps run one after another — see CompositeSequence.

sequence(steps: CompositeStep[]): CompositeSequence

See CompositeSequence, CompositeStep.

.tryCatch(block: CompositeTry)

static method

A try/catch/finally block — see CompositeTry for the outcome rules. An absent catch or finally is left out of the block, so the stored options carry only the phases that exist.

tryCatch(block: CompositeTry): CompositeTry

See CompositeTry.

DeploymentPipelineAction

class

Run a deployment into an environment.

The deployment is stored as a CID link (Ref<Deployment> on the Rust side): the pipeline references the deployment block, it does not embed it — so several steps can share one deployment, and the block stays inspectable on its own. Forcing the pipeline forces the deployment first, since a link needs a CID to point at.

.constructor(environment: Environment, deployment: Deployment)

constructor

constructor(environment: Environment, deployment: Deployment): DeploymentPipelineAction

See Deployment, Environment.

deployment: Deployment

property

See Deployment.

environment: Environment

property

See Environment.

Diagnostics

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): Diagnostics

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Diagnostics from an existing DataType::Diagnostics CID.

fromCid(cid: string): Diagnostics

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

Directory

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): Directory

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.at(path: string)

method

This tree placed under directory path (empty intermediate dirs created) — the inverse of resolve. Pair with merge to graft a subtree into another at a subpath, at the content level (no copies): Directory.merge(client.at("src/gen"), project).

at(path: string): Directory

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.diff(other: Directory)

method

Path-level diff against another tree.

diff(other: Directory): Promise<DiffResult>

See DiffResult.

.entries()

method

The direct entries of this directory as [name, Directory], sorted by name. Genuinely async: it reads the directory node. Use it to fan out — e.g. trigger a child build per entry, then Directory.merge(...results).

entries(): Promise<[string, Directory][]>

.file(path: string, opts?: { … })

method

The payload of the file at path, as a File (content not read).

file(path: string, opts?: { … }): File

See File.

.filterGlob(...patterns: string[])

method

This tree filtered to the paths matching any of the glob patterns.

filterGlob(...patterns: string[]): Directory

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.resolve(path: string, opts?: { … })

method

The subtree at path (a directory or file node).

resolve(path: string, opts?: { … }): Directory

.setMetadata(path: string, meta: Metadata)

method

This tree with metadata changed at path.

setMetadata(path: string, meta: Metadata): Directory

See Metadata.

.sources()

method

How this tree was assembled, as (prefix → origin) entries.

A tool that ran over this tree reports paths in its coordinates; these entries are what turns one back into a path inside the member it came from. See locate.

sources(): SourceOrigin[]

See SourceOrigin.

.tar(compression?: Compression)

method

This tree as a (optionally compressed) tar archive File.

tar(compression?: Compression): File

See Compression, File.

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Directory from an existing CID (e.g. a member's root).

fromCid(cid: string): Directory

.merge(...layers: Directory[])

static method

Overlay-merge trees left→right (later layers win).

The source maps concatenate in the same order, so a lookup that finds two entries for one path prefers the later — the layer whose bytes actually survived the merge.

merge(...layers: Directory[]): Directory

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

.textFile(text: string, options?: { … })

static method

A single-file tree holding text — the root is the file, so it goes straight to a function that reads one file (k8s.apply's root), or under a name with at: Directory.textFile(yaml).at("job.yaml").

The text is stored as a blob and fs.file wraps it as a regular file (mode defaults to 0o644): the node writes the FsNode in the store's own encoding, typed fs-node, so bldr inspect and the console show a file rather than a bare map. No process runs, and the CID is a function of the text and the mode alone.

textFile(text: string, options?: { … }): Directory

encodeUtf8(text: string)

function

The UTF-8 bytes of text. A lone surrogate encodes as U+FFFD, as TextEncoder does.

encodeUtf8(text: string): number[]

Environment

class

A deployment environment: a name plus free-form labels.

.constructor(props: EnvironmentProps)

constructor

constructor(props: EnvironmentProps): Environment

See EnvironmentProps.

labels: Readonly<Record<string, string>>

property

name: string

property

exitedWith(what: string, code: number, log: string, maxLines?: number)

function

<what> exited with code <code>, followed by the end of its log.

The error a build shows for a failed command. The end of the log is where a command says why it stopped — cargo's error: blocks, a test harness's summary — and bounding it keeps a log of thousands of Compiling lines from becoming an error of thousands of lines.

exitedWith(what: string, code: number, log: string, maxLines?: number): string

File

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): File

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.bytes()

method

The full byte content.

bytes(): Promise<Uint8Array<ArrayBufferLike>>

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.compress(compression?: Compression)

method

This file, compressed (default: gzip).

compress(compression?: Compression): File

See Compression.

.decompress(compression: Compression)

method

This file, decompressed with the given codec.

decompress(compression: Compression): File

See Compression.

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.length()

method

The total byte length — reads only the file's part list, not its data.

length(): Promise<number>

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.read(from: number, until?: number)

method

The bytes of the range [from, until).

read(from: number, until?: number): Promise<Uint8Array<ArrayBufferLike>>

.slice(from: number, until?: number)

method

A byte range [from, until) of this file (without reading content).

slice(from: number, until?: number): File

.text()

method

The full content as UTF-8 text.

Here rather than at each call site because the build runtime is hardened: TextDecoder is not among the globals it keeps, so decoding bytes in build code fails at the point of use with a ReferenceError that says nothing about why. The host already knows how to hand back text.

text(): Promise<string>

.untar(compression?: Compression)

method

Extract this file as a tar archive into a directory tree.

untar(compression?: Compression): Directory

See Compression, Directory.

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A File from an existing CID.

fromCid(cid: string): File

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

fromBiomeJson(source: Directory, paths: string | string[], opts?: DiagnosticsOptions)

function

Postprocess Biome's JSON report (biome ci --reporter=json --reporter-file=…) into a Diagnostics. paths are glob(s) matched against source (a tree holding the report file(s)), so a project that lints in several passes is collected in one call.

Its output type is DataType::Diagnostics, which is what tells a consumer there are findings to rank, group and jump to rather than a file to browse.

fromBiomeJson(source: Directory, paths: string | string[], opts?: DiagnosticsOptions): Diagnostics

See Diagnostics, DiagnosticsOptions, Directory.

fromCargoJson(source: Directory, paths: string | string[], opts?: DiagnosticsOptions)

function

Postprocess cargo's newline-delimited JSON (cargo check --message-format=json, and the same from clippy) into a Diagnostics. paths are glob(s) matched against source.

The richest of the converters, because rustc is the most forthcoming tool: the offending span with byte offsets and the source line it came from, the secondary spans that explain it, the notes beneath, and the suggested replacement with rustc's own word for how safe it is. Clippy's findings arrive on the same stream and are told apart by their clippy:: code.

fromCargoJson(source: Directory, paths: string | string[], opts?: DiagnosticsOptions): Diagnostics

See Diagnostics, DiagnosticsOptions, Directory.

fromJestJson(source: Directory, paths: string | string[])

function

Postprocess Jest/Vitest JSON (vitest run --reporter=json --outputFile=…, or jest --json) into a Report. paths are glob(s) matched against source (a tree holding the report file(s)). Its output type is DataType::TestResults, which is what tells bldr test to inspect the cases.

fromJestJson(source: Directory, paths: string | string[]): Report

See Directory, Report.

fromLcov(source: Directory, paths: string | string[], opts?: CoverageConvertOptions)

function

Convert LCOV (cargo llvm-cov --lcov, grcov, gcov's own output) into a Coverage.

LCOV has no region counters, so a report converted from it has line and function coverage and nothing finer. Same options as fromLlvmJson.

fromLcov(source: Directory, paths: string | string[], opts?: CoverageConvertOptions): Coverage

See Coverage, CoverageConvertOptions, Directory.

fromLibtestJson(source: Directory, paths: string | string[])

function

Postprocess cargo test's libtest JSON into a Report. paths are glob(s) matched against source (a tree holding the report file(s)), so a project with several test binaries — hence several .jsons — is collected and merged in one call. Its output type is DataType::TestResults, which is what tells bldr test to inspect the cases.

fromLibtestJson(source: Directory, paths: string | string[]): Report

See Directory, Report.

fromLlvmJson(source: Directory, paths: string | string[], opts?: CoverageConvertOptions)

function

Convert LLVM's own JSON export — what cargo llvm-cov --json writes — into a Coverage. paths are glob(s) matched against source, a tree holding the report file(s).

This is the lossless one: it carries regions, which is the only counter that can tell two branches on one line apart. Prefer it to LCOV unless something downstream demands LCOV.

fromLlvmJson(source: Directory, paths: string | string[], opts?: CoverageConvertOptions): Coverage

See Coverage, CoverageConvertOptions, Directory.

httpGet(url: string, opts?: HttpOptions)

function

httpGet(url: string, opts?: HttpOptions): Promise<HttpResponse>

See HttpOptions, HttpResponse.

httpPost(url: string, body?: File, opts?: HttpOptions)

function

httpPost(url: string, body?: File, opts?: HttpOptions): Promise<HttpResponse>

See File, HttpOptions, HttpResponse.

httpPut(url: string, body?: File, opts?: HttpOptions)

function

httpPut(url: string, body?: File, opts?: HttpOptions): Promise<HttpResponse>

See File, HttpOptions, HttpResponse.

invoke(fn: string, input: Record<string, unknown>, slot?: TaskSlot)

function

Run a node function with a structured input now; resolves to the output CID. slot is the tree node the work appears under. For a truly-async read, not for composition — that is op.

invoke(fn: string, input: Record<string, unknown>, slot?: TaskSlot): Promise<string>

invokeJson(fn: string, input: Record<string, unknown>, slot?: TaskSlot)

function

Run a node function now and read its output block as JSON.

invokeJson(fn: string, input: Record<string, unknown>, slot?: TaskSlot): Promise<T>

isArtifact(value: unknown)

function

Whether a value is a build Artifact (a Directory, File, ContainerImage or Report).

isArtifact(value: unknown): value is Artifact

See Artifact.

languageServer(opts: LanguageServerOptions)

function

Wrap a stdio language server as a v1 LSP runnable.

The command is not run directly. It is run with its standard input and output bound to the two FIFOs, which have to exist before it starts and have to be created inside the pod — a FIFO is a filesystem object, and mounting one from outside would put a host path in a content-addressed tree.

The shell line is doing three things worth naming:

  • mkfifo -m 600 — the pipes are private to the pod's user. There is no other consumer inside, and the daemon reaches them through the pod, not the filesystem.
  • exec — the shell replaces itself with the server, so signals reach the server rather than a shell that would have to forward them, and the pod's main process is the thing whose exit actually means something.
  • < in > out — the redirections open both pipes. This blocks until the other end of each is opened, which is deliberate: a language server that started before anyone was listening would write its first frames into a pipe with no reader and get SIGPIPE for it.
languageServer(opts: LanguageServerOptions): LanguageServer

See LanguageServer, LanguageServerOptions.

languageServerLabels(server: LanguageServer, extra?: Record<string, string>)

function

The labels that mark a runnable as a v1 language server and say what it claims.

Everything the daemon needs that it cannot discover for itself. It can discover where each mount came from — content addressing makes a mounted tree's origin findable — so no path appears here. Recording one would be recording something already true elsewhere, and the two would drift.

languageServerLabels(server: LanguageServer, extra?: Record<string, string>): Record<string, string>

See LanguageServer.

link(cid: string)

function

Wrap a CID string as an IPLD link.

link(cid: string): Link

locate(sources: readonly SourceOrigin[], path: string)

function

The origin a path belongs to: the longest matching prefix, and among equal prefixes the last entry.

Longest wins because a tree assembled from bldr-types and then patched at bldr-types/src/gen has both as prefixes, and the more specific one is the answer. Last wins on a tie because that is what fs.merge does — later layers overwrite earlier ones, so the later origin is whose bytes survived.

Returns the entry and the path relative to it, which together are what makes a location openable: bldr-types/src/cid.rs in a merged workspace is src/cid.rs in bldr/types.

locate(sources: readonly SourceOrigin[], path: string): { … }

See SourceOrigin.

LSP_CLAIMS_LABEL: "bldr.runnable.lsp.claims"

variable

Which files this server answers about, relative to a mounted tree, as comma-separated globs — "**/*.rs,**/Cargo.toml".

The daemon can work out where a server's mounts came from (a mounted tree is content-addressed, so the member whose content it is can be found by looking), but not what it is for. rust-analyzer and a TypeScript server mounted on the same tree claim disjoint halves of it, and nothing about the mount says which half. Only the build knows, so the build says.

LSP_PIPE_DIR: "/run/lsp"

variable

Where a v1 language server reads and writes.

Named pipes rather than a socket: a language server built for an editor reads LSP frames from stdin and writes them to stdout, and two FIFOs are the smallest thing that turns that program into a service without patching it. rust-analyzer needs no argument to speak this way; neither does any other server worth wrapping.

Under /run rather than /dev. /dev in a container is a small tmpfs the runtime populates with the device nodes it decided the pod should have, and adding a directory of our own to it invites a collision with a runtime that has its own opinion — including a future one. /run is the standard place for exactly this: mutable, tmpfs-backed state belonging to a running service, discarded when it stops. FIFOs are that, precisely.

LSP_PIPE_IN: "/run/lsp/in"

variable

LSP_PIPE_OUT: "/run/lsp/out"

variable

LSP_PRIORITY_LABEL: "bldr.runnable.lsp.priority"

variable

Which server wins when two claim the same file, as an integer. Higher wins; ties go to the more deeply-rooted mapping. Absent means zero.

LSP_RUNNABLE_LABEL: "bldr.runnable.lsp"

variable

The label the daemon looks for, and the contract version it names.

A version rather than a boolean because "how to talk to this" is a contract: where the pipes are, what is written on them. A daemon that meets a version it does not implement skips the runnable rather than guessing.

LSP_RUNNABLE_VERSION: "v1"

variable

LSP_SERVICE: "lsp"

variable

The service name a single-server runnable uses.

memberScope(name: string)

function

The root scope of the member named name — its subtree of the build tree. Called by the generated per-member module to define its scope global; build code uses that global, or names another member's directly.

memberScope(name: string): Scope

See Scope.

Metrics

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): Metrics

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Metrics from an existing line-protocol blob CID.

fromCid(cid: string): Metrics

.fromFile(file: Artifact)

static method

A Metrics from a file artifact whose content is line-protocol rows — e.g. pod.output("out").at("metrics.txt") for a pod that wrote its own numbers. The daemon accepts both a bare blob and an FsNode file.

fromFile(file: Artifact): Metrics

See Artifact.

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

metricsFromCoverage(source: Coverage, opts?: MetricsTagOptions & { … })

function

Turn a Coverage report into metrics rows.

granularity: "summary" (default) emits one coverage row of workspace totals; "member" emits one per member with the member tag.

metricsFromCoverage(source: Coverage, opts?: MetricsTagOptions & { … }): Metrics

See Coverage, Metrics, MetricsTagOptions.

metricsFromPodDag(result: Artifact, opts?: MetricsTagOptions)

function

Turn a pod.dag result into metrics rows: one pod_op row per op (op/action tags) carrying duration_ms, exit_code, cpu_usage_usec and memory_peak_bytes — whatever the op recorded.

metricsFromPodDag(result: Artifact, opts?: MetricsTagOptions): Metrics

See Artifact, Metrics, MetricsTagOptions.

metricsFromTestResults(source: Report, opts?: MetricsTagOptions & { … })

function

Turn a test Report into metrics rows.

granularity: "case" (default) emits one test_case row per case with name/status tags and duration_ms/count fields; "summary" emits a single tests row with total/passed/failed/skipped counts.

metricsFromTestResults(source: Report, opts?: MetricsTagOptions & { … }): Metrics

See Metrics, MetricsTagOptions, Report.

metricsTransform(sources: Metrics | Metrics[], opts?: MetricsTransformOptions)

function

Merge one or more Metrics blobs and optionally filter/aggregate the result, as a build step — so a target can carry a distilled series (say, per-suite duration sums) instead of, or beside, the raw rows.

metricsTransform(sources: Metrics | Metrics[], opts?: MetricsTransformOptions): Metrics

See Metrics, MetricsTransformOptions.

narrowSources(sources: readonly SourceOrigin[], path: string)

function

The entries that still apply after narrowing to path — what .resolve() does.

Two cases, and both matter. An entry containing path (a whole member, narrowed to one directory inside it) becomes the root of the result, since everything left came from it. An entry inside path (a layer that was patched into the part being kept) survives, re-rooted. An entry that neither contains nor is contained by path describes a part that was narrowed away.

narrowSources(sources: readonly SourceOrigin[], path: string): SourceOrigin[]

See SourceOrigin.

nestSources(sources: readonly SourceOrigin[], path: string)

function

Every entry of sources, moved under path — what .at(path) does.

nestSources(sources: readonly SourceOrigin[], path: string): SourceOrigin[]

See SourceOrigin.

nodeCapabilities()

function

Every capability the node advertises — for diagnostics and reporting.

nodeCapabilities(): string[]

nodeSupports(name: string)

function

Does the node running this build support name?

if (nodeSupports("pod.cache-volumes")) pod.cache(volume, "/target");

A name nobody advertises answers false, which is what makes it safe to check for something that does not exist yet.

nodeSupports(name: string): boolean

NoopPipelineAction

class

No action: the step exists to sequence or gate others. What a step stores when its props declare no action.

.constructor()

constructor

constructor(): NoopPipelineAction

NotPipelineCondition

class

Negation of one condition.

.constructor(condition: PipelineCondition)

constructor

constructor(condition: PipelineCondition): NotPipelineCondition

See PipelineCondition.

condition: PipelineCondition

property

See PipelineCondition.

.allOf(...conditions: PipelineCondition[])

static method

Holds when every one of conditions holds (none: always holds).

allOf(...conditions: PipelineCondition[]): AllOfPipelineCondition

See AllOfPipelineCondition, PipelineCondition.

.anyOf(...conditions: PipelineCondition[])

static method

Holds when at least one of conditions holds (none: never holds).

anyOf(...conditions: PipelineCondition[]): AnyOfPipelineCondition

See AnyOfPipelineCondition, PipelineCondition.

.noneOf(...conditions: PipelineCondition[])

static method

Holds when none of conditions hold — not(anyOf(…)), spelled the way a reader thinks it.

noneOf(...conditions: PipelineCondition[]): NotPipelineCondition

See PipelineCondition.

.not(condition: PipelineCondition)

static method

Holds when condition does not.

not(condition: PipelineCondition): NotPipelineCondition

See PipelineCondition.

Pipeline

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource | PipelineSpec, labelHint?: string)

constructor

A pipeline authored in place — new Pipeline({ name: "bldr" }), then addStep for each step, before anything forces the CID.

The ArtifactSource form is what fromCid and Artifact.dynamic use to wrap an existing CID; build code writes the spec form.

constructor(source: ArtifactSource | PipelineSpec, labelHint?: string): Pipeline

See ArtifactSource, PipelineSpec.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.addStep(props: PipelineStepProps)

method

Add a step. Returns the step, which is the handle later steps name in dependsOn — dependencies are references to steps of this pipeline, never bare strings, so an edge to a missing or foreign step cannot be written. Ids are assigned in declaration order (step-0, step-1, …), which keeps the stored block deterministic.

addStep(props: PipelineStepProps): PipelineStep

See PipelineStep, PipelineStepProps.

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Pipeline from an existing DataType::Pipeline CID.

fromCid(cid: string): Pipeline

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

PipelineAction

class

What a step does. Abstract: the family is open — each concrete action serialises to { "type": "<tag>", … }, one variant of the Rust PipelineAction enum.

.constructor()

constructor

constructor(): PipelineAction

PipelineCondition

class

A gate on a step. Abstract: the family is open — each concrete condition serialises to { "type": "<tag>", … }, one variant of the Rust PipelineCondition enum.

The combinators are static helpers here (PipelineCondition.allOf(a, b)) and ordinary members of the family in the block: combination is data, not syntax, so a stored condition tree is uniform.

.constructor()

constructor

constructor(): PipelineCondition

.allOf(...conditions: PipelineCondition[])

static method

Holds when every one of conditions holds (none: always holds).

allOf(...conditions: PipelineCondition[]): AllOfPipelineCondition

See AllOfPipelineCondition.

.anyOf(...conditions: PipelineCondition[])

static method

Holds when at least one of conditions holds (none: never holds).

anyOf(...conditions: PipelineCondition[]): AnyOfPipelineCondition

See AnyOfPipelineCondition.

.noneOf(...conditions: PipelineCondition[])

static method

Holds when none of conditions hold — not(anyOf(…)), spelled the way a reader thinks it.

noneOf(...conditions: PipelineCondition[]): NotPipelineCondition

See NotPipelineCondition.

.not(condition: PipelineCondition)

static method

Holds when condition does not.

not(condition: PipelineCondition): NotPipelineCondition

See NotPipelineCondition.

PipelineStep

class

One step of a pipeline. Constructed by Pipeline.addStep — the value of holding one is naming it in another step's dependsOn.

id: string

property

Identifies the step within its pipeline (step-0, step-1, …).

Pod

class

.constructor(rootfs: string | Directory)

constructor

rootfs is an image reference, a rootfs CID, or a rootfs Directory.

constructor(rootfs: string | Directory): Pod

See Directory.

.allowExit(...codes: number[])

method

Accept these exit codes as success. By default only 0 is accepted and anything else fails the build at this pod's task, with its log.

Pass no codes to accept any exit — for a command whose non-zero exit is data rather than failure (a test harness reporting failures through its output, a linter whose verdict you want to branch on).

allowExit(...codes: number[]): this

.allowRunning(on?: boolean)

method

Let this pod's captures read a filesystem that is still being written.

A capture is otherwise only valid for a run that finished, and the daemon enforces it twice: a snapshot must be ordered after something that establishes its pod exited, and the node refuses to capture a pod still running. The reason is that a live overlay is read mid-write, so what comes back is whatever the pod had got to by then — and pod.dag's result is stored in the invocation cache under the spec's CID, so a single torn tree would be replayed as the answer for every later identical build. That is the bug this defaults against.

Some callers genuinely want the live read — peeking at a long build's progress, inspecting a pod that is stuck — and the overlay's cache hygiene is written to tolerate it. This is the opt-out for them, with the understanding that the result is a point-in-time observation, not a reproducible build output.

A pod driven from here always runs to completion before its captures dispatch (run { wait: true }), so this changes nothing for the ordinary run-then-capture shape; it is the escape hatch for a capture the daemon would otherwise refuse as "still running".

allowRunning(on?: boolean): this

.cache(volume: CacheVolume, dest: string)

method

Mount a CacheVolume at dest: a writable directory the daemon keeps between runs, seeded from what the volume held last time and written back when this pod exits.

It is not an input. The volume's name reaches the pod's spec; its content never does, so mounting a cache cannot change what the pod produces or which cached result a repeat of this pod is served — see cache.ts.

Requires the pod to run to completion (every Pod does), because "when it exits" is when the write-back happens.

cache(volume: CacheVolume, dest: string): this

See CacheVolume.

.cacheCapture(volume: CacheVolume, subpath?: string)

method

Read subpath out of a mounted CacheVolume, merged.

The way to take an artifact out of a directory that is also a cache: a cache volume is a separate mount, so it is not part of the pod's rootfs diff and Pod.capture would find nothing there. Merged rather than diffed, because an incremental build's whole point is that it did not rewrite what it could reuse.

cacheCapture(volume: CacheVolume, subpath?: string): Directory

See CacheVolume, Directory.

.capture(subpath: string)

method

A directory of the pod's rootfs changes (the overlay delta) at subpath, as a Lazy Directory. Call it any number of times, for different subpaths, before forcing — the pod runs once and every capture shares the single snapshot. Forcing a capture of a failed run (non-zero exit) throws.

capture(subpath: string): Directory

See Directory.

.captureMerged(subpath: string)

method

A directory of the pod's merged rootfs view (base + changes) at subpath, as a Lazy Directory. Use this to read a path regardless of whether the run touched it; use capture to read only what changed.

captureMerged(subpath: string): Directory

See Directory.

.env(key: string, value: string)

method

Set an environment variable for the run (cleared env otherwise).

env(key: string, value: string): this

.envAll(vars: Record<string, string>)

method

Set several environment variables at once.

envAll(vars: Record<string, string>): this

.envLazy(source: Lazy<Record<string, string>>)

method

A lazily-resolved env source (e.g. an image's effective env), merged under any statically-set vars when the pod runs.

envLazy(source: Lazy<Record<string, string>>): this

.exitCode()

method

The pod's exit code (forcing it runs the pod once).

Asking for the code implies allowExit with no codes: the point is to report the exit, so a non-zero one must not fail the build first — otherwise a non-zero value could never be observed.

exitCode(): Value<number>

See Value.

.internet(on?: boolean)

method

Approve internet access for this pod's DAG: the pod and every service beside it.

A DAG is isolated by default. Its pods share one network namespace of their own — so they reach each other on 127.0.0.1 — and that namespace has no way out: no package index, no registry, no release download. That is what makes a pod's result a function of its declared inputs, and a step that fetches is the exception a build has to name. This is how it names it.

What is approved is the internet, not the host. The node's own loopback stays out of reach either way; what the node serves to a pod (the notification facade, sccache) arrives through the socket the matching method binds into the pod (notify, sccache), never through the network.

The approval is per DAG because the namespace is: asking for it on a service approves the pod it serves as well. It reaches the DAG's spec, and so its invocation-cache key. internet(false) takes the request back.

Accepted and emitted as nothing on a node that does not advertise pod.internet: such a node runs its pods on the host's network, where the approval is already satisfied.

internet(on?: boolean): this

.keepMtime(on?: boolean)

method

Keep the pod's real file mtimes in everything this pod captures.

By default every capture normalises mtimes away, so a pod that produces identical bytes produces an identical CID and the build cache keeps working. (The pod's filesystem tracks real mtimes either way — make, cargo and tsc --incremental see time advance inside the run; only the captured content is normalised.)

Turn this on when the timestamps are part of what you are producing — extracting an archive that must round-trip its stamps, an rsync-like flow, a release tarball whose dates are meaningful. The output CID then depends on the wall clock, so such a capture never hits the cache.

keepMtime(on?: boolean): this

.logs()

method

The pod's captured stdout/stderr as a File (sealed once the pod exits) — a build artifact you can scope.addOutputDirectory (wrapped), fold into a report, or read for assertions.

logs(): File

See File.

.mount(source: string | Directory, dest: string, opts?: MountOptions)

method

Mount a tree at dest (read-only by default; { mutable: true } for a writable overlay whose diff you can capture with mountDelta).

mount(source: string | Directory, dest: string, opts?: MountOptions): this

See Directory, MountOptions.

.mountDelta(id: string, subpath?: string)

method

The diff of a mutable mount (by its id, default m<index>) as a Lazy Directory rooted at the mount point. Only meaningful for a mount created with { mutable: true }. Pass a subpath to re-root within it.

mountDelta(id: string, subpath?: string): Directory

See Directory.

.mountMerged(id: string, subpath?: string)

method

The merged view of a mutable mount — what the pod could see there: the files it wrote on top of the ones the mount was seeded with.

This is the difference that matters for a CacheVolume: what a cached build leaves behind is largely what it inherited, so the diff alone is empty exactly when the cache did its job.

mountMerged(id: string, subpath?: string): Directory

See Directory.

.notify(on?: boolean)

method

Give this pod the build-notification facade: a bldr.notify.v1 server on a Unix socket bound for this pod alone, the statically-linked bldr-notify client at /bin/bldr-notify, and BLDR_NOTIFY_SOCKET naming the socket.

It is how a pod reports what only it knows — the test case it is running, the file that failed to compile — to the build that started it, as structure rather than as log text. Everything else a build learns about a pod is observed from outside it: an exit code, a log stream, a captured tree.

Off unless asked for. The socket reaches the daemon, and that is authority a pod holds only because its build said so.

notify(on?: boolean): this

.profile(on?: boolean)

method

CPU-profile this pod: the node samples its cgroup and seals collapsed stacks on the pod, readable with bldr pod profile <id> or from the pod's page in the console.

profile(false) opts out — of the node's profiling.enabled default, and of a bldr build --profile that asked for everything. The narrower statement wins.

Unlike a cache volume, this does change the pod's spec and therefore its invocation-cache key, so asking for a profile makes the pod run. That is the point: a profile of a pod that was served from the cache would be a profile of nothing.

profile(on?: boolean): this

.resources(resources: DagResources)

method

What this run is admitted against, and then held to — see DagResources.

pod.resources({ cpus: 8, memory: 12 * GiB })

Dropped on a node that cannot read it, which runs the work on its own default share instead.

resources(resources: DagResources): this

See DagResources.

.rootfsDelta()

method

The pod's entire rootfs delta (the overlay upper — everything the run created, modified, or deleted, deletions marked as overlay whiteouts), as a Lazy Directory rooted at /. This is a container image layer. Forcing a delta of a failed run (non-zero exit) throws.

rootfsDelta(): Directory

See Directory.

.rootfsMerged()

method

The pod's entire merged rootfs view (the base image with the run's changes applied — what the container actually saw at /), as a Lazy Directory. Unlike rootfsDelta this includes untouched base files. Forcing a merged view of a failed run (non-zero exit) throws.

rootfsMerged(): Directory

See Directory.

.run(command: Command)

method

The command to run (shell string, argv, or a thunk of either).

run(command: Command): this

See Command.

.runtime(id: string)

method

Run this pod on a named container runtime (a runtimes: id in the node config, e.g. a cloud-hypervisor VM runtime). Omit for the node's default runtime. Selection is per-DAG: this one pod's run executes in that runtime.

runtime(id: string): this

.sccache(opts?: false | SccacheOptions)

method

Give this pod sccache, served by the node: a socket bound for this pod alone at /run/bldr-sccache.sock, the sccache wrapper the node ships on PATH, and the variables that point the wrapper at the socket (SCCACHE_SERVER_UDS, SCCACHE_CLIENT_SIDE). The wrapper compiles in the pod; only cache entries cross the socket, and the node keeps them in its content-addressed store, so every pod of every build on the cluster shares one compile cache.

Nothing routes a compiler through it by itself — set RUSTC_WRAPPER or cargo's build.rustc-wrapper (see sccacheCargoConfig), or prefix a C compiler with sccache. bldr/rust-tools does this for its Cargo pods.

Like a cache volume, it cannot change what the pod produces: the declaration reaches the pod's spec (and so its invocation-cache key); the cache's contents never do. Entries are the tenant's own — a pod never reads another tenant's, because the socket it is given is what decides whose cache it is.

mode: "read-only" looks entries up and stores nothing. sccache(false) takes the request back.

Throws on a node that does not advertise pod.sccache: asked for explicitly, a cache that silently is not there is a slow build nobody can explain. Build code that wants it only where available checks sccacheSupported first.

sccache(opts?: false | SccacheOptions): this

See SccacheOptions.

.service(id: string, pod: Pod)

method

service(id: string, pod: Pod): this

.shell(shell: string[])

method

The shell for shell-form commands (default ["/bin/sh", "-c"]).

shell(shell: string[]): this

.user(user: string)

method

Run a shell-form command as this user (su; no-op for root).

user(user: string): this

.waitHealthy(probe?: Command, opts?: { … })

method

Declare when this pod is ready to be used — the probe that answers it, and the bounds on waiting for that answer.

Only meaningful on a pod passed to service: a service is started rather than waited on, so "the pod is running" is the last thing the DAG knows about it, and that is strictly earlier than "the process inside is listening". Whatever talks to it next would otherwise race the service and lose intermittently, under exactly the load that makes the race likely.

probe runs inside the pod (an exec, so every runtime that can exec can do this — native and VM alike) and is retried until it exits 0. Omit it to use the image's own HEALTHCHECK, the same way omitting a command uses the image's CMD: an image that ships a probe has already said how to tell whether it is serving, and restating it here is a second copy to keep in step. A build fails if neither names one.

etcd.waitHealthy(["etcdctl", "endpoint", "health"], { timeoutSecs: 60 })
postgres.waitHealthy()  // the image's HEALTHCHECK

The wait is bounded twice over, and neither bound is optional: a total timeoutSecs, and a maxAttempts cap so that a tiny intervalMs cannot turn this into a spin of execs. Whichever is reached first ends the wait, failing the build with the pod's name and the probe's last exit code — a readiness wait that could not end would otherwise present as a hung build with nothing to point at.

Progress is reported per attempt, so the wait is visible while it happens.

waitHealthy(probe?: Command, opts?: { … }): this

See Command.

.workdir(dir: string)

method

Run a shell-form command from this directory (cd before it).

workdir(dir: string): this

registrations()

function

Everything registered so far. The driver reads this after running the members' build functions.

registrations(): Registrations

See Registrations.

Report

class

A content-addressed build artifact: a lazy, memoized computation of a CID.

Composition on the subclasses is synchronous and builds a graph; nothing runs until the artifact is forced. Force with cid (or the inherited get(), which it aliases).

.constructor(source: ArtifactSource, labelHint?: string)

constructor

constructor(source: ArtifactSource, labelHint?: string): Report

See ArtifactSource.

nodeLabel

accessor

shape

accessor

valueScope

accessor

.cid(inherited?: TaskSlot)

method

Force and return this artifact's CID. Alias of the inherited get(), kept because .cid() reads better at a build's call sites. inherited is passed by a consuming composition step — see Lazy.get.

cid(inherited?: TaskSlot): Promise<string>

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<string>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive a new lazy value from this one without forcing it now. Inherits this value's scope, so composition stays in the subtree that owns it.

map(f: (…) => …): Lazy<U>

.merge(..._others: Report[])

method

Combine this report with others into one — summing totals and concatenating cases. Planned composition feature (a test.merge node function); the multi-binary case is already handled by fromLibtestJson's globbing, which merges every matched report in one call.

merge(..._others: Report[]): Report

.dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …)

static method

A dynamic artifact: one whose subgraph cannot be known until deps have resolved, because deciding what to build requires reading them.

Called on the concrete class, which is what types the result:

const crates = Directory.dynamic("compile each crate", [src], async () =>
    Directory.merge(...(await src.entries()).map(([n, e]) => compile(n, e))))
    .in(scope);

label is what this node is called in the build tree — the one thing a dynamic node cannot derive, since it runs no node function of its own. Where it goes is [in][Lazy.in]'s business, like every other value; a dynamic artifact with no scope groups its expansion under whichever consumer forces it.

This is the only asynchronous surface in the API: everything else composes synchronously. The graph has a hole inside a dynamic node, but every edge around it is static — the returned handle is an ordinary artifact, so downstream work is declared normally.

deps are forced before expand runs, and are the node's declared dependency edges.

dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …): T

See Artifact, ArtifactSource.

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.fromCid(cid: string)

static method

A Report from an existing DataType::TestResults CID.

fromCid(cid: string): Report

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

resetRegistrations()

function

Forget everything. For tests, and for a driver that runs more than one build in a process.

resetRegistrations(): void

rootScope()

function

The tree's root. Build code should prefer its member's own scope.

rootScope(): Scope

See Scope.

runnable()

function

Convenience constructor: runnable().service(...).

runnable(): Runnable

See Runnable.

Runnable

class

A runnable builder. Fluent: declare network(...)s and service(...)s, then hand it to scope.addRunnable(name, runnable). The framework calls materialize() to force all artifact CIDs into the stored block.

.constructor()

constructor

constructor(): Runnable

.materialize()

method

Force every artifact CID and build the stored block. The field names match the Rust Runnable struct's serde (snake_case) exactly.

materialize(): Promise<unknown>

.network(name: string, def?: NetworkDef)

method

Declare a bridge network the services can attach to.

network(name: string, def?: NetworkDef): this

.service(name: string, def: RunServiceDef)

method

Declare a service (a long-lived pod).

service(name: string, def: RunServiceDef): this

See RunServiceDef.

SCCACHE_BIN_DIR: "/usr/local/lib/bldr/sccache"

variable

Where the node mounts the sccache wrapper inside the pod. The node also prepends this directory to the pod's PATH, so sccache resolves by name.

SCCACHE_CAPABILITY: "pod.sccache"

variable

The node capability a pod needs before it may ask for sccache.

SCCACHE_SOCKET: "/run/bldr-sccache.sock"

variable

Where the node binds the pod's sccache socket ($SCCACHE_SERVER_UDS).

sccacheCargoConfig()

function

The Cargo configuration (TOML) that runs every rustc through the node's wrapper: [build] rustc-wrapper.

By name, not by path — the node puts SCCACHE_BIN_DIR on PATH. A config-file setting rather than RUSTC_WRAPPER in the environment, because cargo ranks the environment above its config files: a build that sets its own RUSTC_WRAPPER keeps it.

sccacheCargoConfig(): string

sccacheSupported()

function

Can the node running this build serve sccache to a pod?

sccacheSupported(): boolean

Scope

class

A named place in the build tree.

Obtained three ways:

  • the ambient scope of the member being built (its root node);
  • import { scope as foobarScope } from "foobar" — another member's root, for work performed on that member's behalf;
  • Scope.childScope, for a subtree you own.

The underlying task node is created lazily, on first use, and only once. A scope that nothing ever runs in leaves no empty box in the tree, so a tool can offer fine-grained scopes without cluttering builds that skip that work. Creating a child forces its ancestors, so the chain always appears in order.

.constructor(name: string, parent?: Scope, options?: ScopeOptions)

constructor

Prefer Scope.childScope; the roots are supplied by the runtime.

constructor(name: string, parent?: Scope, options?: ScopeOptions): Scope

See ScopeOptions.

name: string

property

options: ScopeOptions

property

See ScopeOptions.

.addCompanion(name: string, companion: string, register: (…) => …)

method

Register a companion target beside name and declare it a peer.

The two halves always went together and were written out separately, so one of them was sometimes forgotten: a companion registered without the peer exists, is derived, and is never selected — which is the same as not existing, except that it looks fine in the target list.

Companions live at <name>/<companion>, a tree path, so a narrowed build forces one along with its base for the same reason a subtree filter does.

scope.addCompanion(n, "metrics", (c, m) => c.addMetrics(m, rows));

addCompanion(name: string, companion: string, register: (…) => …): void

.addCoverage(name: string, report: Artifact)

method

Register a named test coverage output: a CoverageReport block naming what the tests actually executed, indexed by member and then by path within it.

Its own registration rather than an output directory because the difference is what a consumer can do with it. A coverage report has a percentage to show, files to rank by it, and a previous run to compare against; handing back a directory of lcov.info puts every one of those behind a file nobody can read. The value here is the converted block — see coverage.from-* — not the tool's raw output.

addCoverage(name: string, report: Artifact): void

See Artifact.

.addDeployment(name: string, functionName: string, options?: unknown, labels?: Record<string, string>)

method

Register a named deployment: a node function to invoke with options, runnable on its own with bldr deploy <target>.

options is plain data, or an async function producing it. The function form runs only when the deployment is selected, so a deployment whose options embed a built CID (an image push) forces that build on demand rather than every time the member's build file evaluates.

addDeployment(name: string, functionName: string, options?: unknown, labels?: Record<string, string>): void

.addDiagnostics(name: string, diagnostics: Artifact, labels?: Record<string, string>)

method

Register a named diagnostics output: a Diagnostics block holding the messages a tool emitted about code — compiler errors, lints, type errors (see api/diagnostics.ts).

Its own registration for the same reason coverage and metrics are: what a consumer does with findings is rank them by severity, group them by rule and jump to the place each one names. A directory of report.json puts every one of those behind a file nobody reads, and a test target would reduce the set to a single bit. The value here is the converted block — see diagnostics.from-* — not the tool's raw output.

Registering findings does not fail a build. Whether errors should stop anything is a policy question, and this is the report that policy would be applied to; a report that only exists when there is nothing to report is not a report.

addDiagnostics(name: string, diagnostics: Artifact, labels?: Record<string, string>): void

See Artifact.

.addDocumentationDirectory(name: string, dir: Directory, labels?: Record<string, string>)

method

Register an output directory that is documentation, labelled so a consumer can find it without knowing its name.

Goes through addOutputDirectory rather than beside it, so documentation is subject to every rule an ordinary output is, and the role label is the only thing this adds.

addDocumentationDirectory(name: string, dir: Directory, labels?: Record<string, string>): void

See Directory.

.addImage(name: string, image: ContainerImage, labels?: Record<string, string>)

method

Register a named container image output.

labels are target labels, selectable with [key=value] filters — the way a matrix of images (one per architecture, engine, runtime) is built a slice at a time. They are not the image's own OCI labels; set those on the image with ContainerImage.label.

addImage(name: string, image: ContainerImage, labels?: Record<string, string>): void

See ContainerImage.

.addLanguageServer(name: string, server: LanguageServer, labels?: Record<string, string>)

method

Register a named language server: a runnable an editor session can be routed to, marked with the label the daemon scans for.

Goes through addRunnable rather than beside it, so a language server is subject to every rule an ordinary runnable is — and so the well-known label is spelled once, where it cannot be got wrong.

addLanguageServer(name: string, server: LanguageServer, labels?: Record<string, string>): void

See LanguageServer.

.addMetrics(name: string, metrics: Artifact)

method

Register a named metrics output: a blob of InfluxDB line-protocol rows (see api/metrics.ts). Queryable via GetBuildMetrics with this target's name as the target tag, shown by the CLI after a build and in the build page's metrics tab.

Its own registration for the same reason coverage is: what a consumer does with metrics is chart and compare them, and a directory view of a numbers file offers none of that.

Convention: a companion for target <base> is <base>/metrics — scope.childScope(base).addMetrics("metrics", …) — so the companion's key is a tree path under its base. Narrowed builds (--export <full base name>:…) force <base>/metrics along with <base> by that name.

addMetrics(name: string, metrics: Artifact): void

See Artifact.

.addOutputDirectory(name: string, dir: Directory, labels?: Record<string, string>)

method

Register a named output directory — a filesystem tree the build produces, exportable with bldr build --export <filter>:<path>.

labels describe what the tree is, travelling beside its CID so a consumer can select it by role rather than by knowing its name — the same bargain addRunnable makes. Prefer a role-specific wrapper where one exists (addDocumentationDirectory), so the well-known label is spelled in one place instead of at each call site.

An unlabelled output is stored exactly as it was before labels existed: an empty map is not recorded, so its build output is byte-identical.

addOutputDirectory(name: string, dir: Directory, labels?: Record<string, string>): void

See Directory.

.addPeers(name: string, ...peers: string[])

method

Declare that building name should also build peers.

A selection says what was asked for, and some asks are incomplete on their own. bldr build bldr/cli/binary runs a compiler that has just finished saying things about the code — but the target carrying what it said is a different one, so the findings were produced and dropped unless whoever typed the command happened to name the diagnostics target too. Requiring that is the kind of rule that is remembered right up until it matters.

So a target declares its companions once, here, and every selection that takes it takes them. Peers are followed transitively, and a peer that names nothing declared is ignored rather than an error — a companion is an offer, and a build that does not declare one is not malformed.

Peers are not dependencies. Nothing is ordered by this and nothing is forced by it; a peer that fails fails on its own, and a target whose peer fails still succeeds. It only widens what a filter selected.

Each peer is a full target path — what target returns, so a companion registered in a child scope is named childScope(n).target("diagnostics") rather than assembled by hand.

addPeers(name: string, ...peers: string[]): void

.addPipelineTarget(name: string, pipeline: Pipeline, labels?: Record<string, string>)

method

Register a named pipeline target: a Pipeline block holding a declarative pipeline definition, authored in build code (new Pipeline({ … }) — see api/pipeline.ts).

Its own registration because a consumer executes a pipeline rather than browsing it: the block is a definition with a shape of its own, and registering it as a directory would hide that behind a tree with nothing in it to open. Only a Pipeline is accepted — the nominal brand is what stops a directory or a report being registered as one.

addPipelineTarget(name: string, pipeline: Pipeline, labels?: Record<string, string>): void

See Pipeline.

.addRunnable(name: string, run: Runnable, labels?: Record<string, string>)

method

Register a named runnable: networks and long-lived services, launched with bldr run <target>.

labels describe what it is for, travelling beside its CID so a consumer can find one by purpose without fetching every runnable block. Prefer a role-specific wrapper where one exists — addLanguageServer sets the label the daemon looks for.

addRunnable(name: string, run: Runnable, labels?: Record<string, string>): void

See Runnable.

.addTest(name: string, node: Artifact, labels?: Record<string, string>)

method

Register a named test: any build value whose CID resolves. Running the test IS building that CID, so it is cached by its inputs like anything else. It passes if the subgraph builds and — when the built CID is a Report — contains no failed cases.

labels are target labels, selectable with [key=value] filters.

addTest(name: string, node: Artifact, labels?: Record<string, string>): void

See Artifact.

.childScope(name: string, options?: ScopeOptions)

method

A child scope named name, for a subtree of work this scope owns.

Synchronous and free: nothing is created until work actually runs in it.

childScope(name: string, options?: ScopeOptions): Scope

See ScopeOptions.

const cargoScope = scope.childScope("Cargo", { description: "Rust build" });
const tests = cargoScope.childScope("test");

.node()

method

The host task node for this scope, created on first call and memoised.

Internal: build code passes Scope values around and never awaits them; the invocation layer resolves the node when it actually spawns work.

node(): Promise<any>

.path()

method

This scope's ancestors then itself, root first — its path in the tree.

path(): string[]

.target(name: string)

method

The target name name gets when registered here.

The scope's path with the build root dropped — that node is the tree's frame, not part of anything's identity — so a target registered on the tests subtree of member bldr/daemon is bldr/daemon/tests/unit.

A target's name is where it lives. There is no second naming scheme to keep in step, and a reader who can see the tree can predict the name.

target(name: string): string

setWorkspaceOptions(options: unknown)

function

Publish the build's effective options. The driver calls this once per generation, before any member module is imported.

setWorkspaceOptions(options: unknown): void

shquote(s: string)

function

POSIX single-quote a string for safe interpolation into a shell command.

shquote(s: string): string

tailLines(text: string, maxLines: number)

function

The last maxLines lines of text, surrounding blank lines trimmed. When lines were left out, the first line says how many, so a reader knows the quote is not the whole log.

tailLines(text: string, maxLines: number): string

TimeWindowPipelineCondition

class

A cron-style time window: holds when the current time matches every field.

Each field is one cron field expression — *, a value (5, fri), a range (9-17, mon-fri), a step (/15, 9-17/2; a bare /step reads as every step-th from *), or a comma list of those — with standard cron bounds (see TimeWindowProps).

Validated here, in the constructor, and again when a daemon decodes the block (bldr-types/src/pipeline.rs runs the same rules): a window is something a scheduler will evaluate, so an expression that cannot mean anything must fail where it was written, not when the step comes due.

.constructor(props?: TimeWindowProps)

constructor

constructor(props?: TimeWindowProps): TimeWindowPipelineCondition

See TimeWindowProps.

dayOfMonth: string

property

dayOfWeek: string

property

hour: string

property

minute: string

property

month: string

property

.allOf(...conditions: PipelineCondition[])

static method

Holds when every one of conditions holds (none: always holds).

allOf(...conditions: PipelineCondition[]): AllOfPipelineCondition

See AllOfPipelineCondition, PipelineCondition.

.anyOf(...conditions: PipelineCondition[])

static method

Holds when at least one of conditions holds (none: never holds).

anyOf(...conditions: PipelineCondition[]): AnyOfPipelineCondition

See AnyOfPipelineCondition, PipelineCondition.

.noneOf(...conditions: PipelineCondition[])

static method

Holds when none of conditions hold — not(anyOf(…)), spelled the way a reader thinks it.

noneOf(...conditions: PipelineCondition[]): NotPipelineCondition

See NotPipelineCondition, PipelineCondition.

.not(condition: PipelineCondition)

static method

Holds when condition does not.

not(condition: PipelineCondition): NotPipelineCondition

See NotPipelineCondition, PipelineCondition.

toYaml(value: unknown)

function

One YAML document for value, ending in a newline.

toYaml(value: unknown): string

unlink(value: unknown)

function

Extract the CID string from an IPLD link.

unlink(value: unknown): string

Value

class

A lazily-decoded non-CID value read out of the graph — an image's effective config, a parsed manifest. Distinct from Artifact: it does not name a block, so it is not an output, not a mount, and not something a dependency edge can point at.

Note what is deliberately absent: a process exit code is not a Value. Exit codes are assertions, not data (docs/pod-api.md, "Exit codes").

.constructor(compute: (…) => …, labelHint?: string)

constructor

constructor(compute: (…) => …, labelHint?: string): Value<T>
constructor(shape: Shape<T>, labelHint?: string): Value<T>

nodeLabel

accessor

shape

accessor

valueScope

accessor

.dependsOn(...deps: unknown[])

method

Record what this value is waiting on.

Only values that are somewhere in the tree count — something with no node and no scope is nothing a reader could go and look at. Accepts anything so callers can pass a mixed bag of inputs (a CID string, a member) without filtering.

dependsOn(...deps: unknown[]): this

.get(inherited?: TaskSlot)

method

Force the computation (memoized — subsequent calls reuse the result).

inherited is the node of the value being computed from this one. An intermediate step of a composition — the resolve in dir.resolve(…).filterGlob(…).in(scope) — has no scope of its own and is private to that chain, so it belongs wherever its consumer went. A value with its own scope ignores the offer and stays where it was placed.

Since the result is memoised, a shared unscoped value keeps whichever parent forced it first. That is the ambiguity Scope exists to resolve: give a value used in two places its own scope and it stops floating.

get(inherited?: TaskSlot): Promise<T>

.in(scope: Scope)

method

Place this value's work in scope.

Fixed at declaration time, so a value shared by several consumers appears once, where its owner put it — not under whichever consumer forced it first. Derived values (map, artifact composition) inherit it.

in(scope: Scope): this

See Scope.

.map(f: (…) => …)

method

Derive another value from this one without forcing it now.

map(f: (…) => …): Value<U>

.expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …)

static method

A value whose subgraph is decided once deps have resolved: expand is called with their values and returns the value this one stands for. label names the node, since it runs no node function of its own.

expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …): Lazy<T>

.node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …)

static method

The output of node function fn on the input input builds from the resolved deps. The value is the output block's CID; read a field of it with map.

node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …): Lazy<string>

.of(value: T)

static method

Already-resolved value.

of(value: T): Lazy<T>

workspaceOption(name: string)

function

One scalar workspace option as text, or undefined when it is unset, empty, or not a scalar. A build file reads a per-build knob with a default this way:

const kubeconfig = workspaceOption("k8sTestKubeconfig") ?? "secret://k8s/current";

A number or a boolean is returned in its text form. An option set on the command line is text to whoever typed it, but -o k8sTestRun=2 reaches the build as the number 2: a reader that took strings alone would treat the knob as unset, and say nothing.

workspaceOption(name: string): string

workspaceOptions()

function

The build's effective options, as published by the driver. Empty until it has, and under a driver too old to publish them.

workspaceOptions(): Record<string, unknown>

yamlDocuments(values: unknown[])

function

Several values as one multi-document YAML stream: --- before each document after the first, the form k8s.apply splits on.

yamlDocuments(values: unknown[]): string

On this page

ContentsTypesAllOfPipelineCondition.constructor(conditions: PipelineCondition[])conditions: readonly PipelineCondition[].allOf(...conditions: PipelineCondition[]).anyOf(...conditions: PipelineCondition[]).noneOf(...conditions: PipelineCondition[]).not(condition: PipelineCondition)AnyOfPipelineCondition.constructor(conditions: PipelineCondition[])conditions: readonly PipelineCondition[].allOf(...conditions: PipelineCondition[]).anyOf(...conditions: PipelineCondition[]).noneOf(...conditions: PipelineCondition[]).not(condition: PipelineCondition)Artifact.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)buildArchitecture()cacheVolume(name: string, opts?: CacheVolumeOptions)CacheVolume.constructor(name: string, mode: CacheMode, perImage: boolean)mode: CacheModename: stringperImage: booleanexclusiveclaim(path: string, kind: string)Compression: { … }ContainerImage.constructor(image: string | Lazy<string>, arch?: string, overrides?: Overrides)architecturenodeLabelshapevalueScope.add(src: Directory, dest: string).arg(key: string, value?: string).buildArgs().cid(inherited?: TaskSlot).cmd(argv: string[]).config().copy(src: Directory, dest: string).dependsOn(...deps: unknown[]).entrypoint(argv: string[]).env(key: string, value: string).envAll(vars: Record<string, string>).expose(...ports: string[]).fromOci().get(inherited?: TaskSlot).in(scope: Scope).label(key: string, value: string).labels(map: Record<string, string>).layers().map(f: (…) => …).mapConfig(fn: (…) => …).mapLabels(fn: (…) => …).pod().root().run(command: Command, opts?: RunOptions).shell(argv: string[]).stopSignal(signal: string).toOci().user(user: string).volume(...paths: string[]).workdir(dir: string).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).from(image: string | Lazy<string>, arch?: string).fromMember(member: ImageMemberRef, arch?: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)Coverage.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)DebianContainerImage.constructor(image: ContainerImage)architecturenodeLabelshapevalueScope.add(src: Directory, dest: string).aptInstall(packageNames: string | string[], opts?: AptInstallOptions).arg(key: string, value?: string).buildArgs().cid(inherited?: TaskSlot).cmd(argv: string[]).config().copy(src: Directory, dest: string).dependsOn(...deps: unknown[]).entrypoint(argv: string[]).env(key: string, value: string).envAll(vars: Record<string, string>).expose(...ports: string[]).fromOci().get(inherited?: TaskSlot).in(scope: Scope).label(key: string, value: string).labels(map: Record<string, string>).layers().map(f: (…) => …).mapConfig(fn: (…) => …).mapLabels(fn: (…) => …).pod().root().run(command: Command, opts?: RunOptions).shell(argv: string[]).stopSignal(signal: string).toOci().user(user: string).volume(...paths: string[]).workdir(dir: string).aptInstallScript(packageNames: string | string[], opts?: AptInstallOptions).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).from(image: string | Lazy<string>, arch?: string).fromMember(member: ImageMemberRef, arch?: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)DEFAULT_ARCHITECTURE_OPTION: "bldr.default-architecture"defaultArchitecture(options: unknown)Deployment.constructor(source: ArtifactSource | DeploymentSpec, labelHint?: string)COMPOSITE_FUNCTION: "deployment/composite/v0.0.0"nodeLabelshapevalueScope.cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).composite(block: CompositeBlock, labels?: Record<string, string>).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T).parallel(steps: CompositeStep[]).sequence(steps: CompositeStep[]).tryCatch(block: CompositeTry)DeploymentPipelineAction.constructor(environment: Environment, deployment: Deployment)deployment: Deploymentenvironment: EnvironmentDiagnostics.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)Directory.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.at(path: string).cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).diff(other: Directory).entries().file(path: string, opts?: { … }).filterGlob(...patterns: string[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).resolve(path: string, opts?: { … }).setMetadata(path: string, meta: Metadata).sources().tar(compression?: Compression).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).merge(...layers: Directory[]).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T).textFile(text: string, options?: { … })encodeUtf8(text: string)Environment.constructor(props: EnvironmentProps)labels: Readonly<Record<string, string>>name: stringexitedWith(what: string, code: number, log: string, maxLines?: number)File.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.bytes().cid(inherited?: TaskSlot).compress(compression?: Compression).decompress(compression: Compression).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).length().map(f: (…) => …).read(from: number, until?: number).slice(from: number, until?: number).text().untar(compression?: Compression).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)fromBiomeJson(source: Directory, paths: string | string[], opts?: DiagnosticsOptions)fromCargoJson(source: Directory, paths: string | string[], opts?: DiagnosticsOptions)fromJestJson(source: Directory, paths: string | string[])fromLcov(source: Directory, paths: string | string[], opts?: CoverageConvertOptions)fromLibtestJson(source: Directory, paths: string | string[])fromLlvmJson(source: Directory, paths: string | string[], opts?: CoverageConvertOptions)httpGet(url: string, opts?: HttpOptions)httpPost(url: string, body?: File, opts?: HttpOptions)httpPut(url: string, body?: File, opts?: HttpOptions)invoke(fn: string, input: Record<string, unknown>, slot?: TaskSlot)invokeJson(fn: string, input: Record<string, unknown>, slot?: TaskSlot)isArtifact(value: unknown)languageServer(opts: LanguageServerOptions)languageServerLabels(server: LanguageServer, extra?: Record<string, string>)link(cid: string)locate(sources: readonly SourceOrigin[], path: string)LSP_CLAIMS_LABEL: "bldr.runnable.lsp.claims"LSP_PIPE_DIR: "/run/lsp"LSP_PIPE_IN: "/run/lsp/in"LSP_PIPE_OUT: "/run/lsp/out"LSP_PRIORITY_LABEL: "bldr.runnable.lsp.priority"LSP_RUNNABLE_LABEL: "bldr.runnable.lsp"LSP_RUNNABLE_VERSION: "v1"LSP_SERVICE: "lsp"memberScope(name: string)Metrics.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).fromFile(file: Artifact).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)metricsFromCoverage(source: Coverage, opts?: MetricsTagOptions & { … })metricsFromPodDag(result: Artifact, opts?: MetricsTagOptions)metricsFromTestResults(source: Report, opts?: MetricsTagOptions & { … })metricsTransform(sources: Metrics | Metrics[], opts?: MetricsTransformOptions)narrowSources(sources: readonly SourceOrigin[], path: string)nestSources(sources: readonly SourceOrigin[], path: string)nodeCapabilities()nodeSupports(name: string)NoopPipelineAction.constructor()NotPipelineCondition.constructor(condition: PipelineCondition)condition: PipelineCondition.allOf(...conditions: PipelineCondition[]).anyOf(...conditions: PipelineCondition[]).noneOf(...conditions: PipelineCondition[]).not(condition: PipelineCondition)Pipeline.constructor(source: ArtifactSource | PipelineSpec, labelHint?: string)nodeLabelshapevalueScope.addStep(props: PipelineStepProps).cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)PipelineAction.constructor()PipelineCondition.constructor().allOf(...conditions: PipelineCondition[]).anyOf(...conditions: PipelineCondition[]).noneOf(...conditions: PipelineCondition[]).not(condition: PipelineCondition)PipelineStepid: stringPod.constructor(rootfs: string | Directory).allowExit(...codes: number[]).allowRunning(on?: boolean).cache(volume: CacheVolume, dest: string).cacheCapture(volume: CacheVolume, subpath?: string).capture(subpath: string).captureMerged(subpath: string).env(key: string, value: string).envAll(vars: Record<string, string>).envLazy(source: Lazy<Record<string, string>>).exitCode().internet(on?: boolean).keepMtime(on?: boolean).logs().mount(source: string | Directory, dest: string, opts?: MountOptions).mountDelta(id: string, subpath?: string).mountMerged(id: string, subpath?: string).notify(on?: boolean).profile(on?: boolean).resources(resources: DagResources).rootfsDelta().rootfsMerged().run(command: Command).runtime(id: string).sccache(opts?: false | SccacheOptions).service(id: string, pod: Pod).shell(shell: string[]).user(user: string).waitHealthy(probe?: Command, opts?: { … }).workdir(dir: string)registrations()Report.constructor(source: ArtifactSource, labelHint?: string)nodeLabelshapevalueScope.cid(inherited?: TaskSlot).dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).merge(..._others: Report[]).dynamic(this: (…) => …, label: string, deps: readonly Artifact[], expand: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).fromCid(cid: string).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)resetRegistrations()rootScope()runnable()Runnable.constructor().materialize().network(name: string, def?: NetworkDef).service(name: string, def: RunServiceDef)SCCACHE_BIN_DIR: "/usr/local/lib/bldr/sccache"SCCACHE_CAPABILITY: "pod.sccache"SCCACHE_SOCKET: "/run/bldr-sccache.sock"sccacheCargoConfig()sccacheSupported()Scope.constructor(name: string, parent?: Scope, options?: ScopeOptions)name: stringoptions: ScopeOptions.addCompanion(name: string, companion: string, register: (…) => …).addCoverage(name: string, report: Artifact).addDeployment(name: string, functionName: string, options?: unknown, labels?: Record<string, string>).addDiagnostics(name: string, diagnostics: Artifact, labels?: Record<string, string>).addDocumentationDirectory(name: string, dir: Directory, labels?: Record<string, string>).addImage(name: string, image: ContainerImage, labels?: Record<string, string>).addLanguageServer(name: string, server: LanguageServer, labels?: Record<string, string>).addMetrics(name: string, metrics: Artifact).addOutputDirectory(name: string, dir: Directory, labels?: Record<string, string>).addPeers(name: string, ...peers: string[]).addPipelineTarget(name: string, pipeline: Pipeline, labels?: Record<string, string>).addRunnable(name: string, run: Runnable, labels?: Record<string, string>).addTest(name: string, node: Artifact, labels?: Record<string, string>).childScope(name: string, options?: ScopeOptions).node().path().target(name: string)setWorkspaceOptions(options: unknown)shquote(s: string)tailLines(text: string, maxLines: number)TimeWindowPipelineCondition.constructor(props?: TimeWindowProps)dayOfMonth: stringdayOfWeek: stringhour: stringminute: stringmonth: string.allOf(...conditions: PipelineCondition[]).anyOf(...conditions: PipelineCondition[]).noneOf(...conditions: PipelineCondition[]).not(condition: PipelineCondition)toYaml(value: unknown)unlink(value: unknown)Value.constructor(compute: (…) => …, labelHint?: string)nodeLabelshapevalueScope.dependsOn(...deps: unknown[]).get(inherited?: TaskSlot).in(scope: Scope).map(f: (…) => …).expansion(label: string, deps: readonly Lazy<unknown>[], expand: (…) => …).node(fn: string, deps: readonly Lazy<unknown>[], input: (…) => …).of(value: T)workspaceOption(name: string)workspaceOptions()yamlDocuments(values: unknown[])