first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
# `limits/outstanding-cap`
## Purpose
Admission control for in-flight 2PC transactions. The coordinator
holds a fixed-size budget of "outstanding prepared transactions"; once
the budget is exhausted new `run2pc` calls refuse rather than queue.
The default is 256 (design doc §14's open question, pinned by this
package's spec) and operators can tune it via host config.
Refusing rather than queuing is intentional: Postgres holds row-level
locks for every prepared transaction, so a backed-up coordinator
would otherwise convert a transient overload into a global lock
storm. Failing fast lets callers route to a degraded path
(degraded-write, retry queue, etc.).
## Features
- `OutstandingPreparedCap` — synchronous counter with `admit()`,
`release()`, `snapshot()`, `capacity()`.
- Constructor validates that the cap is a positive integer (rejects
zero, negatives, fractions, `NaN`).
- `release()` is idempotent below zero — a buggy double-release in
caller code cannot silently expand the real cap.
- Synchronous-only — relies on the event-loop atomicity guarantee
between `admit` / `release` pairs in JavaScript runtimes.
## Usage: Basic
```ts
import { OutstandingPreparedCap } from "@suaveplan/api-2pc/limits";
const cap = new OutstandingPreparedCap(256);
cap.admit();
try {
// run the 2PC transaction
} finally {
cap.release();
}
```
## Usage: Advanced
Wrap admission inside the coordinator's public entry to centralise the
release in one `finally`. The snapshot fuels the OTel gauge:
```ts
import { TWOPC_CODES, TwoPcError } from "@suaveplan/api-2pc/error-codes";
import { OutstandingPreparedCap } from "@suaveplan/api-2pc/limits";
class MiniCoordinator {
private readonly cap = new OutstandingPreparedCap(256);
snapshot() {
return this.cap.snapshot();
}
async run<T>(fn: () => Promise<T>): Promise<T> {
try {
this.cap.admit();
} catch (err) {
if (
err instanceof TwoPcError &&
err.twoPcCode === TWOPC_CODES.OUTSTANDING_LIMIT_EXCEEDED
) {
// route to degraded path
throw err;
}
throw err;
}
try {
return await fn();
} finally {
this.cap.release();
}
}
}
```
## API Reference
- `class OutstandingPreparedCap`
- `constructor(cap: number)` — throws
`TWOPC_OUTSTANDING_LIMIT_EXCEEDED` on invalid `cap`.
- `admit(): void` — throws
`TWOPC_OUTSTANDING_LIMIT_EXCEEDED` when at capacity.
- `release(): void` — idempotent below zero.
- `snapshot(): number` — current outstanding count.
- `capacity(): number` — configured cap.
## Implementation Notes
Construction-time validation re-uses the same error code as the
runtime cap-exceeded path. This is deliberate: operators tuning the
cap via host config want one error string to alert on, regardless of
whether the misconfiguration was a fractional value at startup or a
real overload at steady state. The context object on the thrown
`TwoPcError` distinguishes the two cases (`cap` only vs.
`cap` + `outstanding`).
The class is intentionally tiny — no async, no internal queue, no
external dependencies. Adding a queue here would re-introduce the
lock-storm risk the cap exists to prevent.

View File

@@ -0,0 +1,77 @@
# `limits/timeout-ceiling`
## Purpose
Enforces the hard 30 000 ms ceiling on caller-supplied `timeoutMs`
values. The check fires at the public entry point of `run2pc` so
above-ceiling calls never contact a participant; the operational
warning in design doc §9.2 is that long prepares hold row-level locks,
so surfacing the misuse loudly is more useful than silently clamping.
This module is intentionally tiny — one function — because the
behaviour is small but the gate is one of the most-asserted contracts
in the spec.
## Features
- `assertTimeoutBelowCeiling(timeoutMs)` — throws
`TWOPC_TIMEOUT_CEILING_EXCEEDED` for above-ceiling, non-positive,
non-finite, or `NaN` inputs.
- Re-uses the `TIMEOUT_CEILING_MS` constant from `options/` so the
ceiling stays a single source of truth.
## Usage: Basic
```ts
import { assertTimeoutBelowCeiling } from "@suaveplan/api-2pc/limits";
assertTimeoutBelowCeiling(5000); // ok
assertTimeoutBelowCeiling(30001); // throws TWOPC_TIMEOUT_CEILING_EXCEEDED
```
## Usage: Advanced
The function is intentionally side-effect-free so callers can wrap it
in a per-tenant override path:
```ts
import { TIMEOUT_CEILING_MS } from "@suaveplan/api-2pc/options";
import { assertTimeoutBelowCeiling } from "@suaveplan/api-2pc/limits";
function resolveTimeout(
requested: number | undefined,
tenantMax: number
): number {
const effective = requested ?? DEFAULT_TIMEOUT_MS;
assertTimeoutBelowCeiling(effective);
if (effective > tenantMax) {
return tenantMax;
}
if (effective > TIMEOUT_CEILING_MS) {
// unreachable given the assert above — included only to
// illustrate that the ceiling acts as the strict upper bound.
return TIMEOUT_CEILING_MS;
}
return effective;
}
```
## API Reference
- `function assertTimeoutBelowCeiling(timeoutMs: number): void`
- Throws `TwoPcError(TWOPC_CODES.TIMEOUT_CEILING_EXCEEDED)` for any
of: `NaN`, `Infinity`, `-Infinity`, values `<= 0`, values
`> TIMEOUT_CEILING_MS`.
## Implementation Notes
The check rejects non-positive values for the same reason it rejects
above-ceiling ones: an immediate or backwards-in-time deadline would
collapse phase 1 to a single tick and the rollback path would fire
before any participant could finish a real prepare. Treating that as
the same misuse class as "30 minutes" is a deliberate ergonomic
choice — operators get one error code to grep for.
`Number.isFinite` is used in preference to `Number.isNaN` because the
former covers `Infinity` and `-Infinity` in addition to `NaN`. The
explicit `<= 0` check then excludes zero and negative-finite values.