import { PgPoolLike } from './postgres.cjs'; import { G as GlobalCoordinator, a as RegionalEscrow, R as Region, C as CoordinatorOutageMode, F as FederatedStoreOptions } from './types-BSVR-zyA.cjs'; import { R as RedisClientLike } from './store-BZNM-FbH.cjs'; import { S as Store, T as Transform, d as Strategy, C as Clock, L as Limiter } from './types-DKirIBQt.cjs'; /** * `PostgresCoordinator` — alternative production-ready `GlobalCoordinator` * backed by a single Postgres primary. Drop-in replacement for * `RedisCoordinator` (same interface, same semantics, same window-coupling * guarantee — see `research/postgres-coordinator/DESIGN.md` for the full * design + the lift argument from the federation TLA⁺ proof). * * Layout — one row per coordinator key in `tk_fed_state`: * * key : `:` (PK) * budget : remaining global budget for the active window * expires_at : when the active window ends (epoch-ms) * reconciled_markers : bigint[] of windowStart's reconciled (idempotency) * updated_at : last-touch epoch-ms (debug + GC) * * Atomicity via single-transaction `INSERT ON CONFLICT … DO UPDATE` + `SELECT * FOR UPDATE` + `UPDATE`, all on one row. Window roll is handled in-place: * when `expires_at` differs from the stored value the budget resets to * `perKeyBudget` and reconciled markers clear. Mirrors the Redis HASH + * PEXPIRE pattern without needing a TTL primitive. * * Server-time anchoring via `clock_timestamp()` (NOT `current_timestamp` — * the latter returns the transaction's start time, which can drift if the * transaction is long-running). Equivalent to Redis's `TIME` command — * node clock skew is irrelevant for the bound. * * **SPOF.** A single Postgres primary IS a single point of failure. * Mitigations: synchronous replication + automated failover (Patroni, * pg_auto_failover); during the failover window, regions fall back to * fail-closed (the Δ = 0 bound is preserved). * * See `research/postgres-coordinator/DESIGN.md` §§3-10. */ interface PostgresCoordinatorOptions { /** * A `pg` (node-postgres) pool — or any object satisfying `PgPoolLike` * from `src/postgres/store.ts`. ThrottleKit holds the pool but does NOT * close it; call `pool.end()` in your shutdown hook. */ pool: PgPoolLike; /** * Window length in ms — MUST match the strategy's `windowMs` you * federate. Used to derive the active window's `expiresAt` from server * time so reconcile can detect window roll and reinitialize. */ windowMs: number; /** Default per-window budget for any key without an override. Default 1000. */ budgetPerWindow?: number; /** Postgres table name (created on first use). Default `"tk_fed_state"`. */ tableName?: string; /** Key prefix prepended to every coordinator key. Default `"tk:fed"`. */ prefix?: string; /** * Background-GC sweep interval in ms. Default 60_000. Pass 0 to disable * the timer (useful in tests or when running pg_cron server-side). */ gcIntervalMs?: number; /** * Dormancy threshold for GC: rows untouched for this long are deleted. * Default 86_400_000 (24h). */ gcRetentionMs?: number; /** * Use Postgres `clock_timestamp()` for the `now` anchoring. Default * `true` — node clock skew is then irrelevant for the federation bound. * Set `false` in deterministic tests that pass an explicit `now` via * `setNowForTest`. */ useServerTime?: boolean; } /** * The Postgres-backed `GlobalCoordinator`. Mirrors `RedisCoordinator`'s * surface 1:1 — same `lease` / `reconcile` semantics, same `setBudget` * override knob. The only operational difference is `close()`, which stops * the background GC interval (no analog on the Redis side because PEXPIRE * handles cleanup). */ declare class PostgresCoordinator implements GlobalCoordinator { #private; constructor(options: PostgresCoordinatorOptions); /** Override the per-window budget for a specific key. In-memory only. */ setBudget(key: string, budgetPerWindow: number): void; /** The configured per-key budget (override > default). */ budgetFor(key: string): number; lease(key: string, tokens: number, _expiresAt: number): Promise; /** * Lease + return the authoritative window boundary the budget drained against (the * `clock_timestamp()`-derived `expiresAt`, NOT a node-clock value), so a Tier-2 client discards leftover * credits at exactly that instant — closing the node↔store skew gap {@link lease}'s ignored `expiresAt` leaves. */ leaseWindowed(key: string, tokens: number): Promise<{ granted: number; expiresAt: number; }>; reconcile(key: string, leftover: number, windowStart: number): Promise; isHealthy(): Promise; /** Stop the background GC interval. Idempotent. */ close(): void; } /** * `RedisCoordinator` — the production-ready `GlobalCoordinator`. Backed by * a single global Redis instance; **documented SPOF** until alternative * impls (`PostgresCoordinator`, Raft-via-etcd) land in 0.9.x. * * Layout — one HASH per coordinator key (`:`): * * budget : remaining global budget for the active window * expiresAt : when the active window ends (epoch-ms) * rec_: idempotency markers for reconcile (per windowStart) * * The HASH has PEXPIRE set to the window boundary, so a window roll auto- * drops the prior window's state with no extra bookkeeping. * * Lease is one EVALSHA per cross-region trip. Reconcile is one EVALSHA; * idempotency is enforced inside the script via the `rec_` * field (so retries through a partition converge to the correct global * state — DESIGN.md §3.1 / §5.5). * * `useServerTime: true` (the default) makes the script read Redis's `TIME` * for the `now` value used in PEXPIRE math, so node-clock skew can never * shorten a lease's lifetime below the formal window boundary. * * **windowMs** is taken at construction. The Lua scripts use it to derive * the active window's `expiresAt` from `now`, so reconcile can correctly * initialize a fresh window if the prior window's HASH has already TTL'd * out (the race between the engine's fire-and-forget reconcile and its * next lease, which arrive in Redis in unspecified order). */ interface RedisCoordinatorOptions { /** An `ioredis` (or compatible) client. Use the adapters in `throttlekit/redis` for other clients. */ client: RedisClientLike; /** * Window length in ms — MUST match the strategy's `windowMs` you federate. * Used by the Lua scripts to derive the active window's `expiresAt` from * `now` (`floor(now/windowMs)·windowMs + windowMs`), so reconcile can * initialize a fresh window if the prior window's HASH has TTL'd out. */ windowMs: number; /** Default budget per window for any key without an override. Default 1000. */ budgetPerWindow?: number; /** Redis key prefix. Default `"tk:fed"`. */ prefix?: string; /** * Use the Redis server clock (TIME) for the `now` used in PEXPIRE math. * Default true — protects against node clock skew shortening leases * (which would tighten the federation bound but cost availability). * Set false in deterministic tests that pass an explicit `now`. */ useServerTime?: boolean; } declare class RedisCoordinator implements GlobalCoordinator { #private; constructor(options: RedisCoordinatorOptions); /** Override the per-window budget for a specific key. In-memory only. */ setBudget(key: string, budgetPerWindow: number): void; /** The configured per-key budget (override > default). Used internally and by tests. */ budgetFor(key: string): number; lease(key: string, tokens: number, _expiresAt: number): Promise; /** * Lease + return the authoritative window boundary the budget drained against (the Redis-`TIME`-derived * `expiresAt`, NOT a node-clock value), so a Tier-2 client can discard leftover credits at exactly that * instant — closing the node↔store skew gap that {@link lease}'s ignored `expiresAt` argument leaves open. */ leaseWindowed(key: string, tokens: number): Promise<{ granted: number; expiresAt: number; }>; reconcile(key: string, leftover: number, windowStart: number): Promise; isHealthy(): Promise; } /** * `RedisRegionalEscrow` — the production-ready {@link RegionalEscrow} (the * federation L2 between the engine's in-process L1 cache and the global L3 * coordinator). Backed by a regional Redis instance; same atomic-Lua pattern * as {@link RedisCoordinator}, mirrored one layer down. * * Layout — one HASH per (region, federation-key): * * balance : remaining L2 escrow for the active window * expires_at : when the active window ends (epoch-ms) * source_lease : the L3 coordinator's windowStart this balance is from * * The HASH has PEXPIRE set to the window boundary, so a window roll auto- * drops the prior window's state with no extra bookkeeping. * * Three EVALSHA scripts (one trip each, with EVAL fallback on NOSCRIPT): * - REGIONAL_LEASE — consume from balance; returns granted (0..tokens) * - REGIONAL_REFILL — add to balance from an L3 grant; idempotent on * sourceWindowStart (drops stale-window grants) * - REGIONAL_RELEASE — capture and zero the balance at window roll; only * the first caller per (key, sourceWindowStart) sees the non-zero value * * `useServerTime: true` (the default) makes scripts read Redis's `TIME` so * node-clock skew never shortens a lease's lifetime below the formal window * boundary (same rationale as RedisCoordinator). */ interface RedisRegionalEscrowOptions { /** An `ioredis` (or compatible) client. Use the adapters in `throttlekit/redis` for other clients. */ client: RedisClientLike; /** * Window length in ms — MUST match the strategy's `windowMs` you federate. * Used by the Lua scripts to derive the active window's `expiresAt` from * `now` (`floor(now/windowMs)·windowMs + windowMs`), so window-coupling * works regardless of when the HASH was last touched. */ windowMs: number; /** * Region identity — distinguishes L2 escrows when two regions point at the * same regional Redis (rare but possible). Embedded in the HASH key as * `::`. Should equal the `region` passed to * `federate(...)`. */ region: string; /** Redis key prefix. Default `"tk:l2"`. */ prefix?: string; /** * Use the Redis server clock (TIME) for the `now` used in PEXPIRE math. * Default true — protects against node clock skew shortening leases. * Set false in deterministic tests that pass an explicit `now`. */ useServerTime?: boolean; } declare class RedisRegionalEscrow implements RegionalEscrow { #private; constructor(options: RedisRegionalEscrowOptions); lease(key: string, tokens: number): Promise; refill(key: string, granted: number, sourceWindowStart: number): Promise; release(key: string, sourceWindowStart: number): Promise; isHealthy(): Promise; } /** * `FederatedStore` — a `Store` that fronts a regional `Store` with a * cross-region `GlobalCoordinator` (the "L3" of the recursive twoTier stack). * * As of TK-904 this class is a Store wrapper around the same federation * engine that backs `federate(...)` — `apply()` runs the engine's lease * logic and synthesizes Decisions, ignoring the strategy embedded in the * caller's transform (the engine's own strategy, supplied at construction, * is authoritative). * * Two equivalent surfaces: * - `federate({ strategy, coordinator, region, batch })` → Limiter * (primary API; parallel to rateLimit / twoTier). * - `new FederatedStore({ strategy, coordinator, regional, region, batch })` * → Store (composes with twoTier(leased) for the recursive-twoTier * in-process L1 + regional escrow + global L3 stack). * * Both share `createFederationEngine` internally so they are bit-identical * for any given configuration. * * `applySync` and `resetSync` are deliberately ABSENT: federated * coordination always crosses a region boundary, which is intrinsically * async (cross-region RTT 80–150 ms). Callers needing sync use a * non-federated store. */ declare class FederatedStore implements Store { #private; /** This region's identity. Exposed for telemetry + tests. */ readonly region: Region; /** The default escrow lease size (overridden by `sizer.recommend()` when present). */ readonly batch: number; /** Outage mode — what happens when the coordinator is unreachable. */ readonly onCoordinatorOutage: CoordinatorOutageMode; constructor(options: FederatedStoreOptions); /** * Federated apply — runs the federation engine and synthesizes a * Decision. The cost is extracted from the caller's transform (its * attached `lua.cost`, populated by `decisionTransform(...)`); a transform * without that hint is treated as cost = 1. * * IMPORTANT: the strategy in the caller's transform is ignored — the * federation's own strategy (passed at construction) is authoritative * for window boundaries and the Decision's `limit`. This is consistent * with how the L3 enforces the global bound in DESIGN.md §3.2; the * caller-supplied transform is consulted only for the cost. */ apply(key: string, transform: Transform): Promise; /** * Forget a key in the federation's per-process state AND in the regional * store (when wired). The coordinator's global counter is NOT reset — * that's an administrative action, not a per-key one, because resetting * global state without coordination would race other regions. */ reset(key: string): Promise; /** * Release resources this FederatedStore *owns*. The regional store and * coordinator are caller-provided; they are NOT closed here. The engine's * per-key entries are dropped. */ close(): Promise; /** * The coordinator instance, for tests + telemetry that need to assert * coordinator state. Not part of the `Store` contract. */ get coordinator(): GlobalCoordinator; /** * The regional store, for tests + telemetry. Not part of the `Store` contract. */ get regional(): Store; /** * The regional escrow (L2), for tests + telemetry. `undefined` when the * engine is running in the legacy in-process-only mode. Not part of the * `Store` contract. */ get regionalEscrow(): RegionalEscrow | undefined; /** The federated strategy, for tests + telemetry. */ get strategy(): Strategy; /** * The current adaptive lease size (or {@link FederatedStore.batch} when no * sizer is configured). The engine reads this at lease time. */ recommendedBatch(): number; } /** * Static-partition baseline — the simplest correct federation scheme: * split the global budget evenly across regions, no coordination, no pooling. * * **This is the BASELINE that window-coupled federation (TK-904) improves * on.** It gives Δ = 0 trivially (by construction — each region's slice is * independent of every other region's), but loses pooling under skew: a hot * region binds at `L/K` while idle regions sit on un-used capacity. The * window-coupled federation in TK-904 pools dynamically while preserving * the same Δ = 0 bound. * * Why a factory rather than a Strategy transform: Strategy is opaque * (the internal Lua + state encoding aren't generically re-parametrisable * by `limit`). Asking the caller to supply a closure that captures the * other strategy params is the simplest portable interface; it works for * every built-in strategy and any custom one. See DESIGN.md §4.2. * * Usage: * * import { gcra } from "throttlekit"; * import { staticPartition } from "throttlekit/federation"; * * const perRegion = staticPartition({ * globalLimit: 1000, * regions: ["us-east", "eu-west", "ap-south"], * strategyFactory: (limit) => gcra({ limit, periodMs: 60_000 }), * }); * // perRegion["us-east"] -> gcra({ limit: 334, periodMs: 60_000 }) (remainder) * // perRegion["eu-west"] -> gcra({ limit: 333, periodMs: 60_000 }) * // perRegion["ap-south"] -> gcra({ limit: 333, periodMs: 60_000 }) * * Then each region wires its own `rateLimit(...)` with its slice. * * Remainder distribution: when `globalLimit` doesn't divide evenly across * `regions.length`, the leftover units go to the EARLIEST regions in the * list (regions[0..remainder-1] each get +1). This makes the partition * SUM-PRESERVING — `Σ forRegion(r) === globalLimit` exactly. Lopsided * allocation isn't an issue because the static partition is the baseline * (the federation scheme will pool the unused capacity anyway). */ interface StaticPartitionOptions { /** The total budget to split across regions. */ globalLimit: number; /** The set of regions to partition into. Non-empty; iteration order matters for remainder. */ regions: readonly Region[]; /** * Strategy factory parameterised by the per-region limit. Called once per * region with that region's slice as the argument. The factory MUST capture * any other strategy params (periodMs, burst, etc.) in its closure. */ strategyFactory: (perRegionLimit: number) => Strategy; } /** What {@link staticPartition} returns alongside the per-region strategies. */ interface StaticPartitionResult { /** * Per-region strategies. Use these to construct each region's * `rateLimit(...)` / `twoTier(...)`. The map preserves the input ordering * of `regions` (so iterating `Object.entries(strategies)` is deterministic). */ strategies: Record>; /** * The slice assigned to each region, in input order. `sum(slices) === globalLimit`. * Useful for telemetry, asserts, and the skew analysis. */ slices: Record; } /** * Partition a global budget across regions and produce a per-region strategy * for each slice. See file-level docs for semantics + remainder rule. * * Throws: * - `RangeError` if `globalLimit` is < 1 or not finite. * - `RangeError` if `regions` is empty. * - `RangeError` if `globalLimit < regions.length` (a slice would round to 0, * which is degenerate — every region would always deny). Operators with * `globalLimit < K` should not be using a static partition. * - `TypeError` if `regions` has duplicates. */ declare function staticPartition(options: StaticPartitionOptions): StaticPartitionResult; /** * `TestCoordinator` — an in-memory, deterministic `GlobalCoordinator` for * tests + examples. Models the same window-coupled lease semantics the * RedisCoordinator will (TK-906): leases granted up to a per-key per-window * budget; expired leases discarded at the window boundary; idempotent * reconciliation on `windowStart`. * * Intentionally separate from `MemoryStore` (the regional store) because * they serve different roles — MemoryStore plays the regional L2; this * plays the cross-region coordinator. Tests typically wire them together: * * const regional = new MemoryStore({ ... }); * const coordinator = new TestCoordinator({ budgetPerWindow: 1000 }); * const fed = new FederatedStore({ regional, coordinator, region: "us-east" }); * * No timers; no I/O. Deterministic under an injected clock. */ /** Options for {@link TestCoordinator}. */ interface TestCoordinatorOptions { /** * Default global budget granted per window for any key with no override. * The coordinator's safety check: any `lease()` may at most drain this * budget to zero. Set per-key budgets via {@link TestCoordinator.setBudget}. */ budgetPerWindow?: number; /** * When `false`, `lease()` and `reconcile()` reject with * `StoreUnavailableError`. Useful for simulating coordinator partitions. * Defaults to `true`. */ healthy?: boolean; /** * Window length in ms. When set, {@link TestCoordinator.reconcile} models the production * **window-coupling** guard (`RedisCoordinator`/`PostgresCoordinator`): leftover is credited back ONLY * if it belongs to the still-active window (`windowStart === activeExpiresAt − windowMs`); leftover from * an already-rolled window is FORFEIT, exactly as the formal `Roll` expires escrow — so a rolled window's * leftover can never inflate a later window past `budgetPerWindow` cumulative admissions. When omitted, * reconcile keeps the legacy unconditional credit (for unit tests that don't model window boundaries). */ windowMs?: number; } declare class TestCoordinator implements GlobalCoordinator { #private; constructor(options?: TestCoordinatorOptions); /** Override the per-window budget for a specific key. */ setBudget(key: string, budgetPerWindow: number): void; /** Simulate a coordinator partition. New leases throw until `setHealthy(true)`. */ setHealthy(healthy: boolean): void; /** For tests: snapshot the remaining budget of `key`'s active window. */ remainingFor(key: string, now: number): number; lease(key: string, tokens: number, expiresAt: number): Promise; reconcile(key: string, leftover: number, windowStart: number): Promise; isHealthy(): Promise; } /** * `TestRegionalEscrow` — an in-memory, deterministic {@link RegionalEscrow} * for tests + examples. Models the same window-coupled L2 semantics that * {@link RedisRegionalEscrow} implements atomically in Lua: window-coupled * balance keyed on `source_lease`; additive refills within a window; * idempotent release at window roll. * * Mirrors {@link TestCoordinator} one layer down — no timers, no I/O, * deterministic under an injected clock. Tests typically wire both: * * const clock = new ManualClock(0); * const coord = new TestCoordinator({ budgetPerWindow: 100 }); * const l2 = new TestRegionalEscrow({ windowMs: 60_000, clock }); * const fed = federate({ coordinator: coord, regionalEscrow: l2, ... }); */ /** Options for {@link TestRegionalEscrow}. */ interface TestRegionalEscrowOptions { /** Window length in ms — MUST match the strategy's `windowMs` you federate. */ windowMs: number; /** Injected clock for deterministic tests. Defaults to {@link systemClock}. */ clock?: Clock; /** * When `false`, `lease()` / `refill()` / `release()` reject with * `StoreUnavailableError`. Useful for simulating regional Redis outage. * Defaults to `true`. */ healthy?: boolean; } declare class TestRegionalEscrow implements RegionalEscrow { #private; constructor(options: TestRegionalEscrowOptions); /** Simulate a regional Redis partition. Operations throw until `setHealthy(true)`. */ setHealthy(healthy: boolean): void; /** For tests: snapshot the current balance of `key` (0 if no entry or expired). */ balanceFor(key: string): number; lease(key: string, tokens: number): Promise; refill(key: string, granted: number, sourceWindowStart: number): Promise; release(key: string, sourceWindowStart: number): Promise; isHealthy(): Promise; } /** * Window-coupled federated leasing — the headline contribution of bet #77. * * Implements the formal model from `spec/GaleFederatedLeasing.tla` and the * full check path of `research/bigger-bets/federation/DESIGN.md` §3.2: * each region holds an in-process escrow lease drawn from a global * coordinator; the escrow expires at the window boundary; uncommitted * escrow forfeits and reconciles back to the coordinator. * * Proves (under the formal model): * admitted_per_global_window ≤ Limit (Δ = 0, independent of K) * * Composition surfaces: * - `federate(...)` → returns a `Limiter` (parallel to `rateLimit` / `twoTier`). * - The same engine backs `FederatedStore.apply()` so users who prefer the * Store contract (for layering inside `twoTier(leased)` or any other * `Store`-consuming code) get identical semantics. * * Multi-process atomicity (TK-1306, 0.8.5): when a {@link RegionalEscrow} is * provided, the engine routes leases through it as an L2 cache between the * in-process L1 and the coordinator (L3). Multiple processes in the same * region share the L2 escrow atomically, bounding in-flight per-region * escrow by `perKeyBudget` rather than `M × batch`. When `regionalEscrow` is * undefined, the engine uses the legacy 0.8.4 in-process-only flow * (backward-compat). * * Scope at this commit (TK-1306): * - Strategies with `windowMs` defined (`fixedWindow`, `slidingWindow`, * `quota` with fixed cadence). Pure-rate strategies (gcra/tokenBucket) * aren't supported here because the window-coupling rule needs a * discrete window boundary. * - Lazy reconcile: when the next request after a window boundary lands, * we reconcile the prior window's leftover (best-effort; failure cannot * violate the bound — only forfeits next window's would-be capacity). */ interface FederateOptions { /** * The federated strategy — its `limit` defines the global per-window * budget; its `windowMs` defines the window boundary the escrow couples * to. The strategy MUST have `windowMs` defined; pure-rate strategies * (gcra, tokenBucket) are unsupported at this commit (DESIGN.md §4.3). */ strategy: Strategy; /** Cross-region lease coordinator (the "L3"). */ coordinator: GlobalCoordinator; /** This region's identity — used in coordinator keys + telemetry. */ region: Region; /** * Escrow lease size per global window per region. Default 16. Larger * batch = fewer cross-region RTTs at the cost of `(batch - 1) * (K - 1)` * worst-case unused capacity under skew. Under federated window-coupling * that unused capacity does NOT contribute to overshoot (Δ = 0) — it is * purely a utilization concern (DESIGN.md §6). */ batch?: number; /** * Soft-deprecated as of 0.8.5: kept accepted for backward compat with the * 0.8.3+ `FederatedStore` API which used it for `reset()` plumbing, but * the engine no longer consults this field. Pass {@link regionalEscrow} * instead for the new multi-process per-region escrow path (TK-1306). */ regional?: Store; /** * Regional escrow (L2) for multi-process per-region atomicity (TK-1306, * 0.8.5). When provided, the engine routes leases through this layer * between the in-process L1 and the coordinator (L3) — multiple * processes in the same region share it atomically, bounding in-flight * per-region escrow by `perKeyBudget` instead of `M × batch`. * * Also enables the `"regional-only"` outage mode: when the coordinator * is unreachable, the engine continues serving from the L2 balance until * depleted (availability-over-precision opt-in). * * When undefined, the engine uses in-process escrow only (legacy 0.8.4 * behavior, fully backward-compatible). * * See {@link RegionalEscrow} and `research/regional-escrow/DESIGN.md`. */ regionalEscrow?: RegionalEscrow; /** * Behavior when `coordinator.lease()` throws. Default `"fail-closed"` * (safety > availability). `"regional-only"` (TK-1306, 0.8.5) requires * a {@link regionalEscrow} — the engine continues serving from the L2 * balance during the outage; once coordinator recovers, normal lease * + reconcile resumes. * * Without a `regionalEscrow`, `"regional-only"` degrades silently to * `"fail-closed"` (no L2 to serve from). The federation bound is * preserved in both modes. */ onCoordinatorOutage?: CoordinatorOutageMode; /** * How often to re-probe `coordinator.isHealthy()` while in `"regional-only"` * mode after a coordinator failure. Default 5000 ms. Only consulted when * the outage mode is `"regional-only"` and the coordinator exposes an * `isHealthy()` method. The probe is clock-driven (lazy, on `check()`), * not a background timer — so deterministic tests with `ManualClock` * advance the clock to trigger probes. */ coordinatorHealthCheckMs?: number; /** Injected clock for deterministic tests. Defaults to {@link systemClock}. */ clock?: Clock; /** Key namespace. */ prefix?: string; } /** * Create a federated Limiter that shares its global budget across regions * via a {@link GlobalCoordinator}. Parallel to {@link rateLimit} and * {@link twoTier} — the cross-region analog. * * Quick start: * * import { fixedWindow } from "throttlekit"; * import { federate, TestCoordinator } from "throttlekit/federation"; * * const coordinator = new TestCoordinator({ budgetPerWindow: 1000 }); * const limiter = federate({ * strategy: fixedWindow({ limit: 1000, windowMs: 60_000 }), * coordinator, * region: "us-east", * batch: 16, * }); * const decision = await limiter.check("user:42"); * * For multi-process regions, compose with twoTier(leased) — the * recursive twoTier pattern (DESIGN.md §2.2): * * const federated = federate({ ... }); // returns Limiter, not Store * // For now (TK-904), wrap in twoTier yourself only if you need per-process * // in-memory L1 caching on top of federation; the Store-shape composition * // lands fully in TK-906 with RedisCoordinator. */ declare function federate(options: FederateOptions): Limiter; export { CoordinatorOutageMode, type FederateOptions, FederatedStore, FederatedStoreOptions, GlobalCoordinator, PostgresCoordinator, type PostgresCoordinatorOptions, RedisCoordinator, type RedisCoordinatorOptions, RedisRegionalEscrow, type RedisRegionalEscrowOptions, Region, RegionalEscrow, type StaticPartitionOptions, type StaticPartitionResult, TestCoordinator, type TestCoordinatorOptions, TestRegionalEscrow, type TestRegionalEscrowOptions, federate, staticPartition };