Overview
Horizontal-scaling policy used by the hosted AbsoluteJS.ai platform and available to any Bun control plane.
@absolutejs/autoscalerv0.2.1betaPlatform & InfraPolicy-driven horizontal autoscaling loop for Bun fleets — pluggable signals, declarative thresholds, and an actuator you supply.
Runs the decision loop of a horizontal autoscaler for infrastructure you operate yourself: it reads pluggable signals (CPU, queue depth, p95 latency), combines them into a pressure score, and asks your actuator to spawn or drain instances within declarative min/max, threshold, and cooldown rules. Because the actuator is caller-supplied, the same loop scales VMs, containers, processes, or isolates — you define what an instance is. It is the same scaling engine that powers AbsoluteJS hosting, packaged for any Bun fleet you run on your own servers.
bun add @absolutejs/autoscalerHorizontal-scaling policy used by the hosted AbsoluteJS.ai platform and available to any Bun control plane.
@absolutejs/autoscaler owns the decision half of an autoscaler. The actuator half — actually provisioning a VM, draining a pod, killing a process — lives in your control plane via an injected Actuator, so the substrate stays cloud-agnostic and runtime-agnostic.
The same substrate fits:
a 10-VM fleet across DigitalOcean / Hetzner / Linode
a 10000-isolate fleet on one box via @absolutejs/isolated-jsc
a pod-per-tenant cluster on Kubernetes
The actuator defines what "instance" means.
A Signal is { name, read: () => SignalReading | Promise<SignalReading> }.
A SignalReading is { score, observed? }. score is normalized: 1.0 = "at the scale-up target." Going past 1.0 represents over- target pressure.
The bundled ratioSignal(name, target, read) helper builds the canonical "observed / target" shape for CPU utilization, memory pressure, queue depth, latency, etc.
A signal that throws is captured as { failed: true, error } in the decision readings and excluded from the combined score. The loop never breaks because one metric source is offline.
Combine readings with 'max' (worst pressure wins — the safe default for elasticity), weighted 'avg', or a custom function over the raw readings.
scaleUp and scaleDown have independent cooldown timers — a fleet that just scaled up can still scale down moments later if the load drops. Default cooldownMs is 60 seconds.
When an audit broker is supplied, every applied decision emits:
autoscaler.scale.up — with { score, currentCount, desiredCount, reason }
autoscaler.scale.down
autoscaler.hold
All three are emitted (even hold) so the audit trail tells the full story of why the fleet is where it is. Broker failures are isolated; they bump the metrics().errors counter but never break the loop.
createPolicy sets min/max instance counts, scale-up and scale-down thresholds, step sizes, and independent cooldown timers — a fleet that just scaled up can still scale down moments later.
The ratioSignal helper normalizes any observed/target metric (CPU utilization, queue depth, latency) to a common score. A signal that throws is excluded from the score, so one offline metric source never breaks the loop.
You supply list, spawn, drain, and terminate callbacks. The decision logic stays cloud-agnostic while your control plane talks to Hetzner, Kubernetes, or a local process pool.
With an optional audit broker attached, every decision emits autoscaler.scale.up, autoscaler.scale.down, or autoscaler.hold events, so the trail explains why the fleet is where it is.
Every evaluation turns provider-neutral signals into an explicit, bounded scaling decision.
Outcomes
Horizontal-scaling policy used by the hosted AbsoluteJS.ai platform and available to any Bun control plane.
A Signal is { name, read: () => SignalReading | Promise<SignalReading> }.
'max' (default) — worst pressure wins. The safe choice for
Hardening checklist
Follow in order
Working example for Loop.
read signals → combine into a score → compare to thresholds →
if past threshold & cooldown elapsed → ask actuator to spawn/drain
(clamped to min/max)Working example for API.
import {
createAutoscaler,
createPolicy,
ratioSignal,
} from "@absolutejs/autoscaler";
const scaler = createAutoscaler({
policy: createPolicy({
min: 1,
max: 20,
scaleUp: { threshold: 0.75, cooldownMs: 60_000, step: 1 },
scaleDown: { threshold: 0.3, cooldownMs: 300_000, step: 1 },
}),
signals: [
ratioSignal("cpu", 0.8, async () => await meter.cpuUtilization()),
ratioSignal("queue", 100, async () => await queue.depth(), {
observedKey: "depth",
}),
ratioSignal("latencyP95", 200, async () => await metrics.p95()),
],
combine: "max", // worst pressure wins. or 'avg', or a custom fn
actuator: {
list: () => fleet.list(),
spawn: () => fleet.provision(),
drain: (id) => loadBalancer.remove(id),
terminate: (id) => fleet.destroy(id),
},
audit: broker, // optional; emits autoscaler.scale.up etc.
intervalMs: 30_000,
});
scaler.start();
// fires every 30s
const reviewedPlan = await scaler.evaluate();
await scaler.applyDecision(reviewedPlan, { maxAgeMs: 300_000 });
// applies that exact plan only while its capacity precondition still holds
const oneShot = await scaler.step();
// { action: 'scale-up' | 'scale-down' | 'hold', score, currentCount,
// desiredCount, reason, readings: [...], at }Wire signals, a policy, and your actuator into a running loop. The actuator callbacks define what an instance is — here a VM fleet behind a load balancer.
import {
createAutoscaler,
createPolicy,
ratioSignal,
} from '@absolutejs/autoscaler';
const scaler = createAutoscaler({
policy: createPolicy({
min: 1,
max: 20,
scaleUp: { threshold: 0.75, cooldownMs: 60_000, step: 1 },
scaleDown: { threshold: 0.30, cooldownMs: 300_000, step: 1 },
}),
signals: [
ratioSignal('cpu', 0.80, async () => await meter.cpuUtilization()),
ratioSignal('queue', 100, async () => await queue.depth(),
{ observedKey: 'depth' }),
ratioSignal('latencyP95', 200, async () => await metrics.p95()),
],
combine: 'max', // worst pressure wins. or 'avg', or a custom fn
actuator: {
list: () => fleet.list(),
spawn: () => fleet.provision(),
drain: (id) => loadBalancer.remove(id),
terminate: (id) => fleet.destroy(id),
},
intervalMs: 30_000,
});
scaler.start();
// evaluates signals and applies a decision every 30sCall step() for a single evaluation instead of the interval loop — useful for cron-driven scaling or testing a policy against live signals.
const decision = await scaler.step();
// {
// action: 'scale-up' | 'scale-down' | 'hold',
// score, currentCount, desiredCount,
// reason, readings: [...], at
// }Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.
@absolutejs/autoscaler — horizontal-scaling policy substrate. The package contributes the decision half of a PaaS autoscaler. The actuator half — actually provisioning a VM / draining a pod / killing a process — lives in the control plane via the Actuator interface, so the substrate stays cloud- agnostic. Loop: read signals → combine into a single score (0..1+) → compare against scaleUp / scaleDown thresholds → if past threshold and cooldown elapsed, ask actuator to spawn N / drain N / terminate
type AutoscalerAuditLike = {
append: (event: {
kind: string;
actor?: string;
target?: string;
metadata?: Record<string, unknown>;
}) => Promise<void> | void;
};@absolutejs/autoscalerCurrent package surface
Import surface · click to copy