Standalone client for the public.hmsh_escalations signal-pause surface.

Requires NO dependency on services/durable/. Any HotMesh consumer — AI agent, YAML DAG worker, REST API — can interact with the escalation queue directly with just a Postgres connection.

Signal delivery (for resolve() / resolveByMetadata()) uses HotMesh's engine.signal() internally. The engine is initialised lazily on first use and cached for the lifetime of the process.

import { Escalations } from '@hotmeshio/hotmesh';
import { Client as Postgres } from 'pg';

const client = new Escalations.Client({
connection: {
class: Postgres,
options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' },
},
});

// Claim the next available approval for the 'manager' role
const result = await client.claimByMetadata({
key: 'orderId', value: 'order-123',
assignee: 'alice@company.com',
roles: ['manager'],
});

if (result.ok) {
await client.resolve({ id: result.entry.id, resolverPayload: { approved: true } });
}

Constructors

Properties

instances: Map<string, HotMesh | Promise<HotMesh>> = ...

Methods

  • Cancels a pending escalation and delivers a cancellation signal to the waiting workflow so that condition() returns null. Terminal rows return already-terminal. The cancellation wake commits inside the cancel transaction (same durability contract as resolve()); when no hook rule is deployed for any candidate topic, delivery falls back to a best-effort post-commit publish.

    Parameters

    • id: string
    • Optionalnamespace: string

    Returns Promise<CancelEscalationResult>

  • Bulk-claims up to ids.length pending escalations in one statement. Returns { claimed, skipped } — skipped rows are either already claimed by another assignee or non-existent. Implicit-claim semantics apply.

    Parameters

    Returns Promise<{
        claimed: number;
        skipped: number;
    }>

  • Emits local cancelled events for a batch of already-cancelled escalation entries. Called by WorkflowHandleService.terminate() after the single atomic transaction that interrupts the workflow and cancels its escalations has committed. Fire-and-forget via the configured events.publish sink (e.g. NATS) — instance-local, never broadcast via Postgres LISTEN/NOTIFY.

    Parameters

    Returns void

  • Returns the sorted list of distinct type values in the escalations table. Useful for populating filter dropdowns.

    Parameters

    • Optionalnamespace: string

    Returns Promise<string[]>

  • Retention: deletes terminal escalation rows (resolved/cancelled/expired) whose updated_at is older than the horizon (a Postgres interval string, e.g. '90 days'). Terminal rows are inert — every engine state transition guards on status = 'pending' — so pruning them is safe for live waiters, claims, and signal delivery; this call is the engine-blessed way to age out the audit backlog. Each call deletes at most limit rows (default 10,000) in one atomic statement; loop until deleted is 0 to drain a large backlog. list()/get()/stats() reads over windows older than the pruning horizon reflect only the rows retained.

    Parameters

    Returns Promise<PruneEscalationsResult>

  • No-op in the implicit claim model — availability is computed at query time from assigned_until. Kept for API compatibility.

    Parameters

    • Optionalnamespace: string

    Returns Promise<number>

  • Resolves a pending escalation by UUID. Uses an explicit Postgres transaction with FOR UPDATE + WHERE guard: only one concurrent caller can commit the status change; the committed resolved row with its signal_key is the durable proof. Signal delivery is best-effort post-commit — the resolved row is the recovery record for any missed delivery. Returns the updated row as entry on success.

    Pass params.metadata to merge the resolution outcome ("what actually happened") into the row's GIN-indexed metadata in the same atomic UPDATE, making it @>-queryable alongside the creation metadata ("what was intended"). This is distinct from resolverPayload, which is delivered to the waiting workflow as condition()'s return value and is not GIN-indexed.

    Parameters

    Returns Promise<ResolveEscalationResult>

  • All-or-none bulk resolve with per-row payloads. Every item's escalation must be pending (and, when assertAssignee is set, currently assigned to that assignee) or NOTHING resolves — one atomic SQL statement locks the rows in deterministic order, applies each row's own resolverPayload, and commits each waiter's wake WITH its resolve (same durability contract as resolve()). Unlike resolveMany(), rows backing a live condition() waiter are first-class here: each receives its own payload as condition()'s return value.

    On ok: false no row was written and failed lists exactly the blocking rows with reasons (not-found, already-resolved, already-cancelled, already-expired, assignee-mismatch) — rows that were themselves resolvable stay pending and are not listed.

    Pass params.metadata to merge one shared outcome patch into every row's GIN-indexed metadata in the same statement. Ids must be unique; an empty items array returns ok: true with no entries.

    Parameters

    Returns Promise<ResolveAllOrNoneResult>

  • Resolves the highest-priority matching escalation by metadata filter, then delivers its signal. Same transaction + WHERE guard semantics as resolve().

    The key/value are the selector used to find the row; pass params.metadata to additionally merge a resolution patch into that row's GIN-indexed metadata in the same atomic UPDATE. See resolve.

    Parameters

    Returns Promise<ResolveEscalationResult>

  • Bulk-resolves standalone escalations (rows with signal_key = null). Rows backing a live condition() waiter are excluded — they stay pending so a targeted resolve()/cancel() can deliver their wake; bulk resolution carries no wake and would strand the workflow.

    Parameters

    Returns Promise<EscalationEntry[]>