@molecule/api-upstream-balancer

Core interface · upstream-balancer · API (Node) · v1.1.2 · Apache-2.0

Upstream-balancer core interface for molecule.dev: choose which instance of a multi-instance deployment one request goes to — health-aware round-robin, failure cooldown, retry elsewhere, drain-aware.

npm install @molecule/api-upstream-balancer

npm · Source on GitHub

How it works

@molecule/api-upstream-balancer is the upstream-balancer core interface on the API (Node) side: the API your app calls, with no vendor inside.

Choose the implementation by bonding one of its 1 provider: @molecule/api-upstream-balancer-default.

import { setProvider, pickUpstream, noteUpstreamFailure } from '@molecule/api-upstream-balancer'
import { provider } from '@molecule/api-upstream-balancer-default'

setProvider(provider)

const target = pickUpstream('app.example.com', [primary, replica1, replica2])
try {
  await forward(target)
} catch (error) {
  noteUpstreamFailure(target)
  throw error
}

Providers (1): @molecule/api-upstream-balancer-default

Works with: @molecule/api-bond

Reference

Upstream-balancer core interface for molecule.dev.

A public host resolves to a SET of upstreams; this contract decides which member one request goes to. It is the selection policy an edge uses — health-aware round-robin, a passive failure cooldown, a retry that lands on a different member, and drain-awareness — not a proxy and not a load-balancer appliance.

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.ts JSDoc, not this file.

Quick Start

import { setProvider, pickUpstream, noteUpstreamFailure } from '@molecule/api-upstream-balancer'
import { provider } from '@molecule/api-upstream-balancer-default'

setProvider(provider)

const target = pickUpstream('app.example.com', [primary, replica1, replica2])
try {
  await forward(target)
} catch (error) {
  noteUpstreamFailure(target)
  throw error
}

Type

core

Installation

npm install @molecule/api-upstream-balancer @molecule/api-bond

API

Interfaces

UpstreamMember

One upstream an edge may dial.

host and port are the member's identity: the balancer's round-robin cursor and its failure marks are keyed on host:port, so two members that differ only in metadata are the same member.

interface UpstreamMember {
  /** Upstream hostname — a loopback address (Docker) or the provider's host. */
  host: string
  /** Upstream port. */
  port: number
  /**
   * Set when the deploy/lifecycle marked this instance as draining: it must not
   * receive NEW requests, while requests already in flight finish. Optional; a
   * balancer also accepts the same mark through `markDraining()`.
   */
  draining?: boolean
}

UpstreamBalancerPolicy

How a balancer bounds itself.

interface UpstreamBalancerPolicy {
  /**
   * How long a member stays skipped after a failed attempt. The mark is a
   * cooldown, not a verdict: after this long the member is eligible again even if
   * nothing succeeded against it, so an instance that recovered without another
   * request reaching it is not skipped forever.
   *
   * Defaults to 10 000 ms.
   */
  failureCooldownMs?: number
}

UpstreamBalancerProvider

What an edge needs from an upstream balancer.

Every selection method is scoped to ONE host: the round-robin cursor advances per public host, so two apps sharing an edge do not interleave each other's order.

interface UpstreamBalancerProvider {
  /** A human-readable provider name, for diagnostics. */
  readonly name: string

  /**
   * Choose the upstream for one request.
   *
   * Members marked suspect (a recent failure) or draining are skipped while any
   * other member is eligible. When EVERY member is suspect or draining the cursor's
   * own answer is returned anyway: a stale mark must not turn into "the whole app
   * is down" while every address is merely suspect.
   *
   * @param host - The public host the request arrived for.
   * @param members - The host's upstreams, primary first (never empty).
   * @returns The member to dial.
   */
  pick<U extends UpstreamMember>(host: string, members: readonly U[]): U

  /**
   * Choose the upstream for a RETRY: a different member when one exists, preferring
   * one that is not currently suspect.
   *
   * A retried idempotent request that lands on the same broken instance gains
   * nothing. With a single member there is nothing to choose, and it is returned
   * unchanged.
   *
   * @param host - The public host the request arrived for.
   * @param avoid - The member the failed attempt addressed.
   * @param members - The host's upstreams, primary first.
   * @returns The member for the next attempt.
   */
  pickRetry<U extends UpstreamMember>(host: string, avoid: U, members: readonly U[]): U

  /**
   * Record that an attempt against this member failed, starting its cooldown.
   *
   * @param upstream - The member the attempt addressed.
   */
  noteFailure(upstream: UpstreamMember): void

  /**
   * Record that this member answered, clearing any failure mark.
   *
   * @param upstream - The member that answered.
   */
  noteSuccess(upstream: UpstreamMember): void

  /**
   * Whether a member was marked failed recently enough to skip.
   *
   * @param upstream - The candidate.
   * @returns `true` while its failure mark is fresh.
   */
  isSuspect(upstream: UpstreamMember): boolean

  /**
   * Mark a member as draining by key (`host:port`), so new requests skip it.
   *
   * @param key - The member's `host:port`.
   */
  markDraining(key: string): void

  /**
   * Clear a draining mark.
   *
   * @param key - The member's `host:port`.
   */
  clearDraining(key: string): void

  /**
   * Whether a member is draining, from its own flag or from `markDraining()`.
   *
   * @param upstream - The candidate.
   * @returns `true` while it must receive no new request.
   */
  isDraining(upstream: UpstreamMember): boolean

  /**
   * Drop every cursor and mark. For tests that exercise several edges in one
   * process; application code never calls it.
   */
  reset(): void
}

Functions

setProvider(provider)

Registers an upstream-balancer provider as the active singleton. Called by a bond package during application startup.

function setProvider(provider: UpstreamBalancerProvider): void
  • provider — The provider implementation to bond.

getProvider()

Retrieves the bonded upstream-balancer provider, throwing if none is wired.

function getProvider(): UpstreamBalancerProvider

Returns: The bonded provider.

hasProvider()

Checks whether an upstream-balancer provider is currently bonded.

function hasProvider(): boolean

Returns: true if a provider is bonded.

pickUpstream(host, members)

Chooses the upstream for one request through the bonded provider.

function pickUpstream<U extends UpstreamMember>(host: string, members: readonly U[]): U
  • host — The public host the request arrived for.
  • members — The host's upstreams, primary first.

Returns: The member to dial.

pickRetryUpstream(host, avoid, members)

Chooses the upstream for a retry through the bonded provider.

function pickRetryUpstream<U extends UpstreamMember>(
  host: string,
  avoid: U,
  members: readonly U[],
): U
  • host — The public host the request arrived for.
  • avoid — The member the failed attempt addressed.
  • members — The host's upstreams, primary first.

Returns: The member for the next attempt.

noteUpstreamFailure(upstream)

Records a failed attempt against one upstream.

function noteUpstreamFailure(upstream: UpstreamMember): void
  • upstream — The member the attempt addressed.

noteUpstreamSuccess(upstream)

Records that one upstream answered.

function noteUpstreamSuccess(upstream: UpstreamMember): void
  • upstream — The member that answered.

upstreamSuspect(upstream)

Reports whether an upstream's failure mark is still fresh.

function upstreamSuspect(upstream: UpstreamMember): boolean
  • upstream — The candidate.

Returns: true while it should be skipped.

markUpstreamDraining(key)

Marks an upstream as draining by key (host:port).

function markUpstreamDraining(key: string): void
  • key — The member's host:port.

clearUpstreamDraining(key)

Clears an upstream's draining mark.

function clearUpstreamDraining(key: string): void
  • key — The member's host:port.

upstreamDraining(upstream)

Reports whether an upstream must receive no new request.

function upstreamDraining(upstream: UpstreamMember): boolean
  • upstream — The candidate.

Returns: true while it is draining.

resetUpstreamBalancerForTest()

Drops every cursor and mark. Test-only; application code never calls it.

function resetUpstreamBalancerForTest(): void

Available Providers

ProviderPackage
Default@molecule/api-upstream-balancer-default

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1

Runtime Dependencies

  • @molecule/api-bond

  • A single-member set is returned unchanged. This is what keeps a one-instance deployment on exactly its previous code path.

  • A failure mark is a cooldown, not a verdict. After the policy's cooldown the member is eligible again even if nothing succeeded against it.

  • Never blank the set. When every member is suspect or draining, one is still returned — answering every request with an error is worse than one more try.

  • Draining means no NEW requests. A deploy marks a retiring instance so the edge stops choosing it while its in-flight requests finish.