@molecule/api-deploy-rollout-default
Provider bond · deploy-rollout · API (Node) · v1.1.3 · Apache-2.0
Default deploy-rollout provider for molecule.dev: grow-then-shrink and in-place instance replacement with a capacity floor, continuous soak, reverse rollback and idempotent resume.
npm install @molecule/api-deploy-rollout-defaultnpm · Source on GitHub · Implements @molecule/api-deploy-rollout
How it works
@molecule/api-deploy-rollout-default is a provider bond on the API (Node) side: it implements the deploy-rollout core interface (@molecule/api-deploy-rollout) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider, runRollout } from '@molecule/api-deploy-rollout'
import { provider } from '@molecule/api-deploy-rollout-default'
setProvider(provider)
const outcome = await runRollout({
provider: myInstanceProvider,
current: oldInstances,
policy: { replicas: 2, soakMs: 30_000 },
})Works with: @molecule/api-deploy-rollout
Reference
Default deploy-rollout provider for molecule.dev.
Implements the two real deployment topologies over any instance provider: grow-then-shrink (create the new set, gate it ready, join it, then retire the old one at a time) and in-place (change the same instances one at a time, with a reverse restore on failure). No I/O of its own, no clock other than an injected one, no knowledge of where the instances live.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, runRollout } from '@molecule/api-deploy-rollout'
import { provider } from '@molecule/api-deploy-rollout-default'
setProvider(provider)
const outcome = await runRollout({
provider: myInstanceProvider,
current: oldInstances,
policy: { replicas: 2, soakMs: 30_000 },
})
Type
provider
Installation
npm install @molecule/api-deploy-rollout-default @molecule/api-deploy-rollout
API
Interfaces
ResolvedRolloutPolicy
The resolved policy the state machine runs with.
interface ResolvedRolloutPolicy {
/** Target size of the new set (grow-then-shrink only). */
replicas: number
/** Floor of healthy instances. */
minHealthy: number
/** Continuous healthy time required after a change. */
soakMs: number
/** Probe cadence during the soak. */
soakProbeMs: number
/** Give-up bound for the soak. */
soakGraceMs: number
/** Bounded wait for a retiring instance. */
drainMs: number
/** Readiness budget handed to the provider. */
readinessTimeoutMs: number
/** What an abort does to instances already changed. */
rollback: 'reverse' | 'none'
/** Whether to reconcile against the provider's current instances. */
resume: boolean
}
InstanceProvider
What a grow-then-shrink rollout needs from whatever hosts the instances.
The caller implements this over its hosting provider; the -sandbox bond does it
over @molecule/api-code-sandbox, and tests drive it with a fake that records the
order and can fail any step.
interface InstanceProvider<I extends RolloutInstance = RolloutInstance> {
/** A human-readable provider name, for progress lines. */
readonly name?: string
/**
* Every instance the deployment currently owns, when the provider can enumerate
* them. Used to resume an interrupted rollout idempotently: an instance already
* ready and joined is not created twice.
*/
list?(): Promise<I[]>
/**
* Create the instance at `index` (0-based, above any primary the caller created
* itself). It must not be routable until `InstanceProvider.join`.
*
* @param index - Which instance this is.
* @returns The created instance.
*/
create(index: number): Promise<I>
/**
* Resolve once the instance may take traffic.
*
* @param instance - The instance to gate.
* @param timeoutMs - The policy's readiness budget, for providers whose probe
* accepts one.
* @returns `false` when it never became ready (the rollout aborts).
*/
waitReady(instance: I, timeoutMs?: number): Promise<boolean>
/**
* A single, fast readiness probe, for the soak window. Optional: a caller that
* sets `soakMs` must provide it, because a blocking readiness wait cannot tell a
* warm instance from a recovering one.
*
* @param instance - The instance to probe.
* @returns Whether it is serving right now.
*/
isServing?(instance: I): Promise<boolean>
/**
* Add the instance to the routable set, so the edge may send it new requests.
*
* @param instance - A ready instance.
*/
join(instance: I): Promise<void>
/**
* Remove the instance from the routable set, so no NEW request is routed to it.
* Requests already in flight are untouched.
*
* @param instance - The instance being retired.
*/
leave(instance: I): Promise<void>
/**
* Tell the instance to drain: finish in-flight work and stop accepting new
* connections. Resolve when it may be destroyed, and never later than `drainMs`.
*
* @param instance - The retired instance.
* @param drainMs - The bounded wait.
*/
drain(instance: I, drainMs: number): Promise<void>
/**
* Destroy the instance, releasing whatever it costs.
*
* @param instance - The instance to remove.
*/
destroy(instance: I): Promise<void>
}
Types
RolloutCancelProbe
A cancellation probe: true means stop.
type RolloutCancelProbe = () => Promise<boolean> | boolean
Classes
RolloutAbortedError
A rollout that could not finish, carrying what it did so the caller can report it (and, where the topology allows, undo the rest itself).
The same class serves both topologies: grow-then-shrink fills role, created
and outcome; in-place fills failed, rolledBack and rollbackFailures.
class RolloutAbortedError extends Error {
/** The API layer's key for this abort. */
readonly errorKey = 'deploy-rollout.error.aborted'
/** The step that failed. */
readonly role: RolloutStep | null
/** The instance whose step failed, when one did. */
readonly failed: string | null
/** Instances restored to the old build, newest first. */
readonly rolledBack: string[]
/** Restores that themselves failed. */
readonly rollbackFailures: RolloutFailure[]
/** The grow-then-shrink outcome, when the abort happened there. */
readonly outcome: RolloutOutcome | null
/**
* Build the abort with what the caller must report.
*
* @param message - What happened, in plain words.
* @param details - The role, the failed instance, what was undone and the outcome.
* @param cause - The underlying error, when there was one.
*/
constructor(
message: string,
details: {
role?: RolloutStep | null
failed?: string | null
rolledBack?: string[]
rollbackFailures?: RolloutFailure[]
outcome?: RolloutOutcome | null
} = {},
cause?: unknown,
) {
super(message, cause === undefined ? undefined : { cause })
this.name = 'RolloutAbortedError'
this.role = details.role ?? null
this.failed = details.failed ?? null
this.rolledBack = details.rolledBack ?? []
this.rollbackFailures = details.rollbackFailures ?? []
this.outcome = details.outcome ?? null
}
}
Functions
resolveRolloutPolicy(policy, instanceCount)
Fill the policy defaults.
maxUnavailable derives minHealthy only when it is set; without it the floor
is one healthy instance, which is exactly the rule every existing rolling path
has today.
function resolveRolloutPolicy(
policy: RolloutPolicy | undefined,
instanceCount: number,
): ResolvedRolloutPolicy
policy— The caller's policy, if any.instanceCount— How many instances the topology has, formaxUnavailable.
Returns: The resolved policy.
runGrowThenShrink(options)
Run a grow-then-shrink replacement.
The caller has already created, gated and joined the PRIMARY instance of the new
version when primaryServing is set; replicas is the size of the new set
including that one, and this function creates the remaining replicas - 1.
function runGrowThenShrink<I extends RolloutInstance>(
options: RolloutOptions<I>,
): Promise<RolloutOutcome<I>>
options— The set to build, the old set, and the provider.
Returns: The new set, the retired instances and non-fatal failures.
runInPlace(options)
Run an in-place replacement: the same instances changed one at a time.
The instance list comes from machines, or from the provider's own list().
The operations come from apply/rollback/probeServing, or from the provider's
replace/restore/isServing. Before an instance is taken out of rotation,
every OTHER instance must answer its probe, and the resulting healthy count must
stay at or above the policy floor.
function runInPlace<I extends RolloutInstance>(
options: InPlaceRolloutOptions<I>,
): Promise<InPlaceRolloutOutcome<I>>
options— The instances to roll and the provider or callbacks.
Returns: The instances changed, in roll order.
createProvider()
Create a fresh provider object (the same algorithm; a distinct identity for tests and for apps that register more than one).
function createProvider(): DeployRolloutProvider
Returns: A deploy-rollout provider.
describeRolloutResult(outcome, replicas)
One plain sentence about what a finished rollout did.
function describeRolloutResult<I extends RolloutInstance>(
outcome: Pick<RolloutOutcome<I>, 'retired'>,
replicas: number,
): string | null
outcome— What the rollout returned.replicas— The set size, for the sentence.
Returns: The line, or null when there was nothing to retire or the set is one.
Constants
DEFAULT_ROLLOUT_DRAIN_MS
The default bounded drain wait — matches the lifecycle bond's own budget.
const DEFAULT_ROLLOUT_DRAIN_MS: 25000
DEFAULT_ROLLOUT_READINESS_TIMEOUT_MS
The default readiness budget handed to a provider.
const DEFAULT_ROLLOUT_READINESS_TIMEOUT_MS: 120000
DEFAULT_SOAK_PROBE_MS
The default cadence of the soak probe.
const DEFAULT_SOAK_PROBE_MS: 2000
DEFAULT_MIN_HEALTHY
The default floor of healthy instances: never blank the deployment.
const DEFAULT_MIN_HEALTHY: 1
provider
The provider implementation registered under deploy-rollout.
const provider: DeployRolloutProvider
Core Interface
Implements @molecule/api-deploy-rollout interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-deploy-rollout'
import { provider } from '@molecule/api-deploy-rollout-default'
export function setupDeployRolloutDefault(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-deploy-rollout^1.0.0
Runtime Dependencies
-
@molecule/api-deploy-rollout -
Capacity floor first.
policy.minHealthy(default 1) is checked before every retirement; a step that would go below it aborts instead of running. -
Soak is continuous. With
soakMs > 0the instance must keep answeringisServing; a gap restarts the window (a fresh process can pass one probe while its event loop is still warming). -
Rollback is reverse. Instances this run changed are restored newest first; a restore failure does not stop the remaining restores.
-
Resume is a no-op when complete. A re-run reconciles against the provider's own
list()where it has one, so an interrupted rollout does not create a duplicate set.