Files
tech-log-frontend/src/adapters/realtime/reconnect-coordinator.ts
T

1797 lines
48 KiB
TypeScript

import type { ClockPort } from "../../application/ports/clock-port.ts";
import type {
RealtimeTransportEventOutcome,
} from "../../application/ports/realtime/event-authority.ts";
import {
type RealtimeFailureKind,
type RealtimeResult,
} from "../../application/ports/realtime/shared.ts";
import { systemClock } from "../platform/system-clock.ts";
import {
calculateReconnectDelay,
defineReconnectPolicy,
isReconnectAttemptResetEligible,
REALTIME_RECONNECT_CEILINGS,
reconnectBudgetRemaining,
type ReconnectPolicy,
} from "./reconnect-policy.ts";
import {
isRealtimeResult,
isRealtimeTransportEventOutcome,
realtimeFailure,
} from "./result.ts";
type FailureResult = Extract<RealtimeResult<never>, { ok: false }>;
type SuccessResult<Value> = Extract<
RealtimeResult<Value>,
{ ok: true }
>;
export type RealtimeCommittedRecovery = Extract<
RealtimeTransportEventOutcome,
{ kind: "RECOVERY_COMMITTED" }
>;
/**
* The hint is coordinator-private metadata. A terminal RealtimeResult can
* therefore be returned without rewriting its failure.
*/
export type RealtimeReconnectOutcome<Value> =
| Readonly<{
result: SuccessResult<Value>;
}>
| Readonly<{
result: FailureResult;
serverNotBeforeMs?: number | null;
}>;
export type RealtimeReconnectAttemptContext = Readonly<{
signal: AbortSignal;
pendingRecovery: RealtimeCommittedRecovery | null;
isCurrent(): boolean;
/**
* Call only after a protocol-valid heartbeat or event.
*/
markValidHeartbeatOrEvent(): void;
/**
* A transport may expose readiness and then wait here before admitting
* inbound events. With no pending recovery the call fails closed.
*/
waitForRecoveryBarrierConfirmation(): Promise<
RealtimeResult<void>
>;
}>;
export type RealtimeReconnectAttemptSuccess<ClosedReceipt> =
Readonly<{
session: RealtimeReconnectSession<ClosedReceipt>;
establishedRecoveryBarrier?: RealtimeCommittedRecovery;
}>;
export type RealtimeRecoveryReconnectDirective = Readonly<{
kind: "RECOVERY_RECONNECT";
recovery: RealtimeCommittedRecovery;
terminalResult: FailureResult;
serverNotBeforeMs?: number | null;
}>;
export type RealtimeReconnectCloseClassification =
| RealtimeReconnectOutcome<void>
| RealtimeRecoveryReconnectDirective;
/**
* The receipt stays strongly typed until `classifyClosed`. `close` must be
* terminal and idempotent.
*/
export type RealtimeReconnectSession<
ClosedReceipt = RealtimeReconnectOutcome<void>,
> = Readonly<{
waitClosed():
| ClosedReceipt
| Promise<ClosedReceipt>;
classifyClosed(
receipt: ClosedReceipt,
): RealtimeReconnectCloseClassification;
close(): void;
}>;
export type RealtimeReconnectEnvironment = Readonly<{
online(): boolean;
subscribeOnline(listener: (online: boolean) => void): () => void;
}>;
export type RealtimeReconnectRunInput = Readonly<{
signal?: AbortSignal;
initialRecovery?: RealtimeRecoveryReconnectDirective;
}>;
export type RealtimeReconnectCoordinator = Readonly<{
run(input?: RealtimeReconnectRunInput): Promise<
RealtimeResult<void>
>;
getState(): "CLOSED" | "DRAINING" | "IDLE" | "RUNNING";
close(): void;
}>;
export type RealtimeReconnectCoordinatorDependencies<
ClosedReceipt = RealtimeReconnectOutcome<void>,
> = Readonly<{
policy: ReconnectPolicy;
environment: RealtimeReconnectEnvironment;
attempt(
context: RealtimeReconnectAttemptContext,
):
| RealtimeReconnectOutcome<
RealtimeReconnectAttemptSuccess<ClosedReceipt>
>
| Promise<
RealtimeReconnectOutcome<
RealtimeReconnectAttemptSuccess<ClosedReceipt>
>
>;
confirmTransportBarrier(
recovery: RealtimeCommittedRecovery,
): RealtimeResult<void>;
clock?: ClockPort;
random?: () => number;
isCurrent?: () => boolean;
}>;
const RETRYABLE_KINDS = Object.freeze([
"CONNECT_TIMEOUT",
"IDLE_TIMEOUT",
"OFFLINE",
"PROVIDER_UNAVAILABLE",
"RATE_LIMITED",
] as const satisfies readonly RealtimeFailureKind[]);
type Retry = Readonly<{
result: FailureResult;
terminalResult: FailureResult;
at: number;
notBeforeMs: number | null;
}>;
type Settled<Value> =
| Readonly<{ kind: "VALUE"; value: Value }>
| Readonly<{ kind: "THREW" }>;
type AwaitedTask<Value> =
| Settled<Value>
| Readonly<{ kind: "INTERRUPTED" }>;
type DrainedTask<Value> =
| AwaitedTask<Value>
| Readonly<{ kind: "TIMED_OUT" }>;
type Phase = Readonly<{
controller: AbortController;
offlineVersion: number;
dispose(): void;
}>;
type BarrierGate = Readonly<{
promise: Promise<RealtimeResult<void>>;
settle(result: RealtimeResult<void>): void;
}>;
type FailureSnapshot = Readonly<{
result: FailureResult;
kind: RealtimeFailureKind;
retryable: boolean;
}>;
type ParsedResult<Value> =
| Readonly<{
ok: true;
result: SuccessResult<unknown>;
value: Value;
}>
| Readonly<{
ok: false;
failure: FailureSnapshot;
}>;
type ParsedOutcome<Value> =
| Readonly<{
ok: true;
result: SuccessResult<unknown>;
value: Value;
}>
| Readonly<{
ok: false;
failure: FailureSnapshot;
serverNotBeforeMs: number | null;
}>;
type ParsedAttemptSuccess<ClosedReceipt> = Readonly<{
session: RealtimeReconnectSession<ClosedReceipt>;
hasRecoveryBarrier: boolean;
establishedRecoveryBarrier: unknown;
}>;
type ParsedRecoveryDirective = Readonly<{
recovery: RealtimeCommittedRecovery;
terminalFailure: FailureSnapshot;
serverNotBeforeMs: number | null;
}>;
type ParsedCloseClassification =
| Readonly<{
kind: "OUTCOME";
outcome: ParsedOutcome<void>;
}>
| Readonly<{
kind: "RECOVERY_RECONNECT";
directive: ParsedRecoveryDirective;
}>;
type DataSnapshot = Readonly<{
source: object;
keys: readonly string[];
values: Readonly<Record<string, unknown>>;
}>;
type ParsedRunInput = Readonly<{
signal: AbortSignal | undefined;
initialRecovery: ParsedRecoveryDirective | null;
}>;
type ParsedValue<Value> = Readonly<{ value: Value }>;
export function createRealtimeReconnectCoordinator<ClosedReceipt>(
dependencies: RealtimeReconnectCoordinatorDependencies<ClosedReceipt>,
): RealtimeReconnectCoordinator {
const attempt = dependencies?.attempt;
const confirmTransportBarrier =
dependencies?.confirmTransportBarrier;
const environment = dependencies?.environment;
if (
typeof attempt !== "function" ||
typeof confirmTransportBarrier !== "function" ||
typeof environment?.online !== "function" ||
typeof environment.subscribeOnline !== "function"
) {
throw new TypeError("Invalid realtime reconnect dependencies.");
}
const policy = defineReconnectPolicy(dependencies.policy);
const clock = dependencies.clock ?? systemClock;
const random = dependencies.random ?? Math.random;
const scopeCurrent = dependencies.isCurrent ?? (() => true);
let state: "CLOSED" | "DRAINING" | "IDLE" | "RUNNING" = "IDLE";
let generation = 0;
let activeTask: Promise<Settled<unknown>> | null = null;
let runController: AbortController | null = null;
let phaseController: AbortController | null = null;
let session: RealtimeReconnectSession<ClosedReceipt> | null = null;
let activeRecoveryGate: BarrierGate | null = null;
function retainTracked<Value>(
task: Promise<Settled<Value>>,
onSettled?: (outcome: Settled<Value>) => void,
): Promise<Settled<Value>> {
const retained = onSettled
? task.then((outcome) => {
onSettled(outcome);
return outcome;
})
: task;
const tracked = retained as Promise<Settled<unknown>>;
activeTask = tracked;
void retained.then(() => {
if (activeTask === tracked) {
activeTask = null;
if (state === "DRAINING") state = "IDLE";
}
});
return retained;
}
function track<Value>(
task: () => Value | Promise<Value>,
): Promise<Settled<Value>> {
let execution: Promise<Value>;
try {
execution = Promise.resolve(task());
} catch {
execution = Promise.reject();
}
const settled = execution.then<Settled<Value>, Settled<Value>>(
(value) => Object.freeze({ kind: "VALUE", value }),
() => Object.freeze({ kind: "THREW" }),
);
return retainTracked(settled);
}
async function run(
input: RealtimeReconnectRunInput = {},
): Promise<RealtimeResult<void>> {
if (state === "CLOSED") return failure("CLOSED");
if (state !== "IDLE") return failure("PROTOCOL_MISMATCH");
const parsedInput = parseRunInput(input);
if (!parsedInput) return failure("PROTOCOL_MISMATCH");
const inputSignal = parsedInput.signal;
const initiallyAborted = safeSignalAborted(inputSignal);
if (initiallyAborted === null) {
return failure("PROTOCOL_MISMATCH");
}
if (initiallyAborted) return failure("ABORTED");
if (!safeCurrent(scopeCurrent)) return failure("SCOPE_FENCED");
const initialNow = safeNow(clock);
if (initialNow === null) return failure("PROVIDER_UNAVAILABLE");
state = "RUNNING";
const runGeneration = ++generation;
const controller = new AbortController();
runController = controller;
let requested: RealtimeFailureKind | null = null;
let online = false;
let onlineVersion = 0;
let offlineVersion = 0;
let requiredOnlineVersion: number | null = null;
let wakeOnline: (() => void) | null = null;
let unsubscribe: (() => void) | undefined;
let pendingRecovery = parsedInput.initialRecovery;
let retry =
pendingRecovery === null
? null
: captureRetry(
pendingRecovery.terminalFailure.result,
pendingRecovery.terminalFailure.result,
initialNow,
pendingRecovery.serverNotBeforeMs,
);
let retriesUsed = 0;
let budgetStartedAt = initialNow;
const currentFailure = (): RealtimeFailureKind | null => {
if (state === "CLOSED" || generation !== runGeneration) {
return "CLOSED";
}
if (requested) return requested;
return safeCurrent(scopeCurrent) ? null : "SCOPE_FENCED";
};
const settleActiveGate = (result: RealtimeResult<void>) => {
activeRecoveryGate?.settle(result);
};
const stop = (kind: RealtimeFailureKind) => {
requested ??= kind;
settleActiveGate(recoveryFailure(kind));
controller.abort();
phaseController?.abort();
safelyClose(session);
wakeOnline?.();
};
const requireOnline = () => {
requiredOnlineVersion = Math.max(
requiredOnlineVersion ?? 0,
onlineVersion + 1,
);
};
const observeOnline = (value: boolean) => {
if (typeof value !== "boolean") {
stop("PROVIDER_UNAVAILABLE");
return;
}
online = value;
if (value) {
onlineVersion += 1;
if (
requiredOnlineVersion !== null &&
onlineVersion >= requiredOnlineVersion
) {
requiredOnlineVersion = null;
}
if (requiredOnlineVersion === null) wakeOnline?.();
} else {
offlineVersion += 1;
requireOnline();
settleActiveGate(recoveryFailure("OFFLINE"));
phaseController?.abort();
safelyClose(session);
}
};
const waitOnline = async (): Promise<boolean> => {
if (online && requiredOnlineVersion === null) return true;
await new Promise<void>((resolve) => {
const wake = () => {
if (wakeOnline === wake) wakeOnline = null;
controller.signal.removeEventListener("abort", wake);
resolve();
};
wakeOnline = wake;
controller.signal.addEventListener("abort", wake, {
once: true,
});
if (
controller.signal.aborted ||
(online && requiredOnlineVersion === null)
) {
wake();
}
});
return (
currentFailure() === null &&
online &&
requiredOnlineVersion === null
);
};
const beginPhase = (): Phase => {
const phase = new AbortController();
const abort = () => phase.abort();
controller.signal.addEventListener("abort", abort, {
once: true,
});
if (controller.signal.aborted || !online) phase.abort();
phaseController = phase;
const capturedOfflineVersion = offlineVersion;
return Object.freeze({
controller: phase,
offlineVersion: capturedOfflineVersion,
dispose() {
controller.signal.removeEventListener("abort", abort);
if (phaseController === phase) phaseController = null;
},
});
};
const wentOffline = (phase: Phase) =>
phase.offlineVersion !== offlineVersion || !online;
const onAbort = () => stop("ABORTED");
if (!safelyAddAbortListener(inputSignal, onAbort)) {
stop("PROVIDER_UNAVAILABLE");
}
const abortedAfterRegistration = safeSignalAborted(inputSignal);
if (abortedAfterRegistration === null) {
stop("PROVIDER_UNAVAILABLE");
} else if (abortedAfterRegistration) {
onAbort();
}
try {
try {
unsubscribe = environment.subscribeOnline(observeOnline);
if (typeof unsubscribe !== "function") {
stop("PROVIDER_UNAVAILABLE");
}
} catch {
stop("PROVIDER_UNAVAILABLE");
}
const initiallyOnline = safeOnline(environment);
if (initiallyOnline === null) {
stop("PROVIDER_UNAVAILABLE");
} else {
online = initiallyOnline;
if (!online) requireOnline();
}
while (true) {
const stopped = currentFailure();
if (stopped) return failure(stopped);
if (retry) {
let now = safeNow(clock);
if (
now === null ||
!canRetry(
retry,
retriesUsed,
budgetStartedAt,
now,
policy,
)
) {
return retry.terminalResult;
}
if (
(!online || requiredOnlineVersion !== null) &&
!(await waitOnline())
) {
return failure(currentFailure() ?? "ABORTED");
}
now = safeNow(clock);
if (now === null) return retry.terminalResult;
const delay = nextDelay(
retry,
retriesUsed,
budgetStartedAt,
now,
policy,
random,
);
if (delay === null) return retry.terminalResult;
const phase = beginPhase();
const sleeping = track(
() => clock.sleep(delay, phase.controller.signal),
);
const slept = await race(sleeping, phase.controller.signal);
const offline = wentOffline(phase);
if (slept.kind === "INTERRUPTED") {
if (offline) {
const drained = await drainWithinCeiling(
sleeping,
controller.signal,
);
phase.dispose();
const afterDrain = currentFailure();
if (drained.kind === "INTERRUPTED" || afterDrain) {
return failure(afterDrain ?? "ABORTED");
}
if (drained.kind === "TIMED_OUT") {
return failure("PROVIDER_UNAVAILABLE");
}
continue;
}
phase.dispose();
return failure(currentFailure() ?? "ABORTED");
}
phase.dispose();
if (offline) continue;
if (slept.kind === "THREW") return retry.terminalResult;
now = safeNow(clock);
if (
now === null ||
reconnectBudgetRemaining(
policy,
budgetStartedAt,
now,
) <= 0
) {
return retry.terminalResult;
}
retriesUsed += 1;
} else if (!online || requiredOnlineVersion !== null) {
retry = offlineRetry(initialNow, "CONNECT");
continue;
}
const beforeAttempt = safeNow(clock);
if (
beforeAttempt === null ||
reconnectBudgetRemaining(
policy,
budgetStartedAt,
beforeAttempt,
) <= 0
) {
return (
retry?.terminalResult ??
failure("PROVIDER_UNAVAILABLE")
);
}
const phase = beginPhase();
let validSignalObserved = false;
const attemptRecovery = pendingRecovery?.recovery ?? null;
const attemptGate =
attemptRecovery === null ? null : createBarrierGate();
if (attemptGate) activeRecoveryGate = attemptGate;
const noPendingRecovery = recoveryFailure(
"PROTOCOL_MISMATCH",
);
const settleAttemptGate = (
result: RealtimeResult<void>,
) => {
attemptGate?.settle(result);
if (activeRecoveryGate === attemptGate) {
activeRecoveryGate = null;
}
};
const isAttemptCurrent = () =>
state === "RUNNING" &&
generation === runGeneration &&
phaseController === phase.controller &&
!phase.controller.signal.aborted &&
online &&
safeCurrent(scopeCurrent);
const context =
Object.freeze<RealtimeReconnectAttemptContext>({
signal: phase.controller.signal,
pendingRecovery: attemptRecovery,
isCurrent: isAttemptCurrent,
markValidHeartbeatOrEvent() {
if (isAttemptCurrent()) validSignalObserved = true;
},
waitForRecoveryBarrierConfirmation() {
return (
attemptGate?.promise ??
Promise.resolve(noPendingRecovery)
);
},
});
const attempting = track(
() => attempt(context),
);
const attempted = await race(
attempting,
phase.controller.signal,
);
const attemptOffline = wentOffline(phase);
if (attempted.kind === "INTERRUPTED") {
const retainedAttempt = retainTracked(
attempting,
closeLateSession,
);
if (attemptOffline) {
const offlineFailure = recoveryFailure("OFFLINE");
settleAttemptGate(offlineFailure);
const drained = await drainWithinCeiling(
retainedAttempt,
controller.signal,
);
phase.dispose();
const afterDrain = currentFailure();
if (drained.kind === "INTERRUPTED" || afterDrain) {
return failure(afterDrain ?? "ABORTED");
}
if (drained.kind === "TIMED_OUT") {
return failure("PROVIDER_UNAVAILABLE");
}
const drainedAt = safeNow(clock);
if (drainedAt === null) {
return failure("PROVIDER_UNAVAILABLE");
}
retry = offlineRetry(
drainedAt,
"CONNECT",
pendingRecovery?.terminalFailure.result,
);
continue;
}
const interrupted = currentFailure() ?? "ABORTED";
settleAttemptGate(recoveryFailure(interrupted));
phase.dispose();
return failure(interrupted);
}
const afterAttempt = currentFailure();
if (afterAttempt) {
closeLateSession(attempted);
settleAttemptGate(recoveryFailure(afterAttempt));
phase.controller.abort();
phase.dispose();
return failure(afterAttempt);
}
if (attemptOffline) {
closeLateSession(attempted);
settleAttemptGate(recoveryFailure("OFFLINE"));
phase.controller.abort();
phase.dispose();
retry = offlineRetry(
safeNow(clock) ?? beforeAttempt,
"CONNECT",
pendingRecovery?.terminalFailure.result,
);
continue;
}
if (attempted.kind === "THREW") {
const nextRetry = providerRetry(
safeNow(clock) ?? beforeAttempt,
"CONNECT",
pendingRecovery?.terminalFailure.result,
);
settleAttemptGate(nextRetry.result);
phase.controller.abort();
phase.dispose();
retry = nextRetry;
continue;
}
const opened = parseAttemptOutcome<ClosedReceipt>(
attempted.value,
);
if (!opened) {
const mismatch = failure("PROTOCOL_MISMATCH");
settleAttemptGate(mismatch);
phase.controller.abort();
phase.dispose();
return mismatch;
}
const afterAttemptValidation = currentFailure();
if (afterAttemptValidation) {
if (opened.ok) safelyClose(opened.value.session);
settleAttemptGate(
recoveryFailure(afterAttemptValidation),
);
phase.controller.abort();
phase.dispose();
return failure(afterAttemptValidation);
}
if (!opened.ok) {
settleAttemptGate(opened.failure.result);
phase.controller.abort();
phase.dispose();
if (
!reconnectable(
opened.failure,
opened.serverNotBeforeMs,
)
) {
return opened.failure.result;
}
const failedAt = safeNow(clock);
if (failedAt === null) return opened.failure.result;
retry = captureRetry(
opened.failure.result,
pendingRecovery?.terminalFailure.result ??
opened.failure.result,
failedAt,
opened.serverNotBeforeMs,
);
if (opened.failure.kind === "OFFLINE") requireOnline();
continue;
}
const active = opened.value.session;
if (pendingRecovery) {
if (
!opened.value.hasRecoveryBarrier ||
opened.value.establishedRecoveryBarrier !==
pendingRecovery.recovery
) {
const mismatch = recoveryFailure(
"PROTOCOL_MISMATCH",
);
settleAttemptGate(mismatch);
safelyClose(active);
phase.controller.abort();
phase.dispose();
return mismatch;
}
const beforeConfirmation = currentFailure();
if (beforeConfirmation) {
settleAttemptGate(
recoveryFailure(beforeConfirmation),
);
safelyClose(active);
phase.controller.abort();
phase.dispose();
return failure(beforeConfirmation);
}
let rawConfirmation: unknown;
try {
rawConfirmation =
confirmTransportBarrier(pendingRecovery.recovery);
} catch {
rawConfirmation = null;
}
const confirmation = parseVoidResult(rawConfirmation);
if (!confirmation) {
const mismatch = recoveryFailure(
"PROTOCOL_MISMATCH",
);
settleAttemptGate(mismatch);
safelyClose(active);
phase.controller.abort();
phase.dispose();
return mismatch;
}
const afterConfirmation = currentFailure();
if (afterConfirmation) {
settleAttemptGate(
recoveryFailure(afterConfirmation),
);
safelyClose(active);
phase.controller.abort();
phase.dispose();
return failure(afterConfirmation);
}
if (!confirmation.ok) {
settleAttemptGate(confirmation.failure.result);
safelyClose(active);
phase.controller.abort();
phase.dispose();
return confirmation.failure.result;
}
settleAttemptGate(
confirmation.result as RealtimeResult<void>,
);
pendingRecovery = null;
} else {
settleAttemptGate(noPendingRecovery);
if (opened.value.hasRecoveryBarrier) {
safelyClose(active);
phase.controller.abort();
phase.dispose();
return noPendingRecovery;
}
}
const openedAt = safeNow(clock);
if (
openedAt === null ||
reconnectBudgetRemaining(
policy,
budgetStartedAt,
openedAt,
) <= 0
) {
safelyClose(active);
phase.controller.abort();
phase.dispose();
return (
retry?.terminalResult ??
failure("PROVIDER_UNAVAILABLE")
);
}
retry = null;
session = active;
const waiting = track(() => active.waitClosed());
const closed = await race(
waiting,
phase.controller.signal,
);
const closeOffline = wentOffline(phase);
const afterWaitClosed = currentFailure();
if (afterWaitClosed) {
safelyClose(active);
session = null;
phase.controller.abort();
phase.dispose();
return failure(afterWaitClosed);
}
if (closed.kind === "INTERRUPTED") {
safelyClose(active);
if (closeOffline) {
const drained = await drainWithinCeiling(
waiting,
controller.signal,
);
session = null;
phase.dispose();
const afterDrain = currentFailure();
if (drained.kind === "INTERRUPTED" || afterDrain) {
return failure(afterDrain ?? "ABORTED");
}
if (drained.kind === "TIMED_OUT") {
return failure("PROVIDER_UNAVAILABLE");
}
const drainedAt = safeNow(clock);
if (drainedAt === null) {
return failure("PROVIDER_UNAVAILABLE");
}
retry = offlineRetry(
drainedAt,
"RECEIVE",
);
continue;
}
session = null;
phase.dispose();
return failure(currentFailure() ?? "ABORTED");
}
if (closeOffline) {
safelyClose(active);
session = null;
phase.controller.abort();
phase.dispose();
retry = offlineRetry(
safeNow(clock) ?? openedAt,
"RECEIVE",
);
continue;
}
if (closed.kind === "THREW") {
safelyClose(active);
session = null;
phase.controller.abort();
phase.dispose();
retry = providerRetry(
safeNow(clock) ?? openedAt,
"RECEIVE",
);
continue;
}
let rawClassification: unknown;
try {
rawClassification = active.classifyClosed(closed.value);
} catch {
rawClassification = null;
}
const afterClassification = currentFailure();
safelyClose(active);
session = null;
phase.controller.abort();
phase.dispose();
if (afterClassification) {
return failure(afterClassification);
}
const classified = parseCloseClassification(
rawClassification,
);
if (!classified) return failure("PROTOCOL_MISMATCH");
const afterClassificationValidation = currentFailure();
if (afterClassificationValidation) {
return failure(afterClassificationValidation);
}
if (classified.kind === "OUTCOME") {
if (classified.outcome.ok) {
return classified.outcome.result as RealtimeResult<void>;
}
if (
!reconnectable(
classified.outcome.failure,
classified.outcome.serverNotBeforeMs,
)
) {
return classified.outcome.failure.result;
}
const closedAt = safeNow(clock);
if (closedAt === null) {
return classified.outcome.failure.result;
}
if (
isReconnectAttemptResetEligible({
policy,
openedAtMs: openedAt,
nowMs: closedAt,
observedValidHeartbeatOrEvent:
validSignalObserved,
})
) {
retriesUsed = 0;
budgetStartedAt = closedAt;
}
retry = captureRetry(
classified.outcome.failure.result,
classified.outcome.failure.result,
closedAt,
classified.outcome.serverNotBeforeMs,
);
if (
classified.outcome.failure.kind === "OFFLINE"
) {
requireOnline();
}
continue;
}
const closedAt = safeNow(clock);
if (closedAt === null) {
return classified.directive.terminalFailure.result;
}
if (
isReconnectAttemptResetEligible({
policy,
openedAtMs: openedAt,
nowMs: closedAt,
observedValidHeartbeatOrEvent: validSignalObserved,
})
) {
retriesUsed = 0;
budgetStartedAt = closedAt;
}
pendingRecovery = classified.directive;
retry = captureRetry(
classified.directive.terminalFailure.result,
classified.directive.terminalFailure.result,
closedAt,
classified.directive.serverNotBeforeMs,
);
}
} finally {
safelyRemoveAbortListener(inputSignal, onAbort);
safelyUnsubscribe(unsubscribe);
controller.abort();
phaseController?.abort();
safelyClose(session);
session = null;
activeRecoveryGate?.settle(
recoveryFailure(currentFailure() ?? "CLOSED"),
);
activeRecoveryGate = null;
safelyWake(wakeOnline);
if (runController === controller) runController = null;
if (generation === runGeneration) {
state = activeTask === null ? "IDLE" : "DRAINING";
}
}
}
function close(): void {
if (state === "CLOSED") return;
state = "CLOSED";
generation += 1;
activeRecoveryGate?.settle(recoveryFailure("CLOSED"));
activeRecoveryGate = null;
runController?.abort();
phaseController?.abort();
safelyClose(session);
}
return Object.freeze({
run,
getState: () => state,
close,
});
}
async function race<Value>(
task: Promise<Settled<Value>>,
signal: AbortSignal,
): Promise<AwaitedTask<Value>> {
if (signal.aborted) return Object.freeze({ kind: "INTERRUPTED" });
let cleanup: () => void = () => undefined;
const interrupted = new Promise<
Readonly<{ kind: "INTERRUPTED" }>
>((resolve) => {
const abort = () =>
resolve(Object.freeze({ kind: "INTERRUPTED" }));
signal.addEventListener("abort", abort, { once: true });
cleanup = () => signal.removeEventListener("abort", abort);
if (signal.aborted) abort();
});
try {
return await Promise.race([task, interrupted]);
} finally {
cleanup();
}
}
async function drainWithinCeiling<Value>(
task: Promise<Settled<Value>>,
signal: AbortSignal,
): Promise<DrainedTask<Value>> {
if (signal.aborted) return Object.freeze({ kind: "INTERRUPTED" });
let timer: ReturnType<typeof setTimeout> | undefined;
let cleanup: () => void = () => undefined;
const boundary = new Promise<
| Readonly<{ kind: "INTERRUPTED" }>
| Readonly<{ kind: "TIMED_OUT" }>
>((resolve) => {
const abort = () =>
resolve(Object.freeze({ kind: "INTERRUPTED" }));
cleanup = () => safelyRemoveAbortListener(signal, abort);
try {
signal.addEventListener("abort", abort, { once: true });
timer = setTimeout(
() => resolve(Object.freeze({ kind: "TIMED_OUT" })),
REALTIME_RECONNECT_CEILINGS.drainTimeoutMs,
);
} catch {
resolve(Object.freeze({ kind: "TIMED_OUT" }));
}
if (signal.aborted) abort();
});
try {
return await Promise.race([task, boundary]);
} finally {
cleanup();
if (timer !== undefined) {
try {
clearTimeout(timer);
} catch {
// The generation remains fenced if host timer cleanup fails.
}
}
}
}
function createBarrierGate(): BarrierGate {
let settled = false;
let resolveGate:
| ((result: RealtimeResult<void>) => void)
| undefined;
const promise = new Promise<RealtimeResult<void>>((resolve) => {
resolveGate = resolve;
});
return Object.freeze({
promise,
settle(result) {
if (settled) return;
settled = true;
resolveGate?.(result);
resolveGate = undefined;
},
});
}
function parseRunInput(value: unknown): ParsedRunInput | null {
const snapshot = captureDataSnapshot(value);
if (
!snapshot ||
!hasOneExactKeySet(snapshot, [
[],
["signal"],
["initialRecovery"],
["initialRecovery", "signal"],
])
) {
return null;
}
const signalValue = snapshot.values.signal;
if (
signalValue !== undefined &&
(signalValue === null ||
(typeof signalValue !== "object" &&
typeof signalValue !== "function"))
) {
return null;
}
const initialValue = snapshot.values.initialRecovery;
const initialRecovery =
initialValue === undefined
? null
: parseRecoveryDirectiveSnapshot(
captureDataSnapshot(initialValue),
);
if (initialValue !== undefined && !initialRecovery) return null;
return Object.freeze({
signal: signalValue as AbortSignal | undefined,
initialRecovery,
});
}
function parseAttemptOutcome<ClosedReceipt>(
value: unknown,
): ParsedOutcome<ParsedAttemptSuccess<ClosedReceipt>> | null {
return parseOutcomeSnapshot(
captureDataSnapshot(value),
(candidate) =>
parseAttemptSuccess<ClosedReceipt>(
captureDataSnapshot(candidate),
),
);
}
function parseAttemptSuccess<ClosedReceipt>(
snapshot: DataSnapshot | null,
): ParsedValue<ParsedAttemptSuccess<ClosedReceipt>> | null {
if (
!snapshot ||
!hasOneExactKeySet(snapshot, [
["session"],
["establishedRecoveryBarrier", "session"],
])
) {
return null;
}
const session = parseSession<ClosedReceipt>(
captureDataSnapshot(snapshot.values.session),
);
if (!session) return null;
const hasRecoveryBarrier = snapshot.keys.includes(
"establishedRecoveryBarrier",
);
return Object.freeze({
value: Object.freeze({
session,
hasRecoveryBarrier,
establishedRecoveryBarrier:
snapshot.values.establishedRecoveryBarrier,
}),
});
}
function parseSession<ClosedReceipt>(
snapshot: DataSnapshot | null,
): RealtimeReconnectSession<ClosedReceipt> | null {
if (
!snapshot ||
!hasExactKeys(snapshot, [
"classifyClosed",
"close",
"waitClosed",
])
) {
return null;
}
const waitClosed = snapshot.values.waitClosed;
const classifyClosed = snapshot.values.classifyClosed;
const close = snapshot.values.close;
if (
typeof waitClosed !== "function" ||
typeof classifyClosed !== "function" ||
typeof close !== "function"
) {
return null;
}
return Object.freeze({
waitClosed: () =>
Reflect.apply(waitClosed, snapshot.source, []) as
| ClosedReceipt
| Promise<ClosedReceipt>,
classifyClosed: (receipt: ClosedReceipt) =>
Reflect.apply(classifyClosed, snapshot.source, [
receipt,
]) as RealtimeReconnectCloseClassification,
close: () => {
Reflect.apply(close, snapshot.source, []);
},
});
}
function parseCloseClassification(
value: unknown,
): ParsedCloseClassification | null {
const snapshot = captureDataSnapshot(value);
if (!snapshot) return null;
if (snapshot.values.kind === "RECOVERY_RECONNECT") {
const directive = parseRecoveryDirectiveSnapshot(snapshot);
return directive
? Object.freeze({
kind: "RECOVERY_RECONNECT",
directive,
})
: null;
}
const outcome = parseOutcomeSnapshot(
snapshot,
(candidate) =>
candidate === undefined
? Object.freeze({ value: undefined })
: null,
);
return outcome
? Object.freeze({ kind: "OUTCOME", outcome })
: null;
}
function parseRecoveryDirectiveSnapshot(
snapshot: DataSnapshot | null,
): ParsedRecoveryDirective | null {
if (
!snapshot ||
!hasOneExactKeySet(snapshot, [
["kind", "recovery", "terminalResult"],
[
"kind",
"recovery",
"serverNotBeforeMs",
"terminalResult",
],
]) ||
snapshot.values.kind !== "RECOVERY_RECONNECT"
) {
return null;
}
const recovery = parseCommittedRecovery(
captureDataSnapshot(snapshot.values.recovery),
);
const terminalFailure = parseFailureResult(
snapshot.values.terminalResult,
);
const serverNotBeforeMs = parseNotBefore(
snapshot.values.serverNotBeforeMs,
);
if (
!recovery ||
!terminalFailure ||
serverNotBeforeMs === undefined
) {
return null;
}
return Object.freeze({
recovery,
terminalFailure,
serverNotBeforeMs,
});
}
function parseCommittedRecovery(
snapshot: DataSnapshot | null,
): RealtimeCommittedRecovery | null {
if (
!snapshot ||
!hasExactKeys(snapshot, [
"checkpoint",
"kind",
"streamId",
]) ||
snapshot.values.kind !== "RECOVERY_COMMITTED" ||
!safeFrozen(snapshot.source)
) {
return null;
}
const checkpoint = captureDataSnapshot(
snapshot.values.checkpoint,
);
if (
!checkpoint ||
!hasExactKeys(checkpoint, [
"lastAppliedSequence",
"recoveryMode",
"resumeCursor",
"streamEpoch",
]) ||
!safeFrozen(checkpoint.source)
) {
return null;
}
const canonicalCheckpoint = Object.freeze({
lastAppliedSequence:
checkpoint.values.lastAppliedSequence,
recoveryMode: checkpoint.values.recoveryMode,
resumeCursor: checkpoint.values.resumeCursor,
streamEpoch: checkpoint.values.streamEpoch,
});
const canonicalRecovery = Object.freeze({
checkpoint: canonicalCheckpoint,
kind: snapshot.values.kind,
streamId: snapshot.values.streamId,
});
if (
!isRealtimeTransportEventOutcome(canonicalRecovery) ||
canonicalRecovery.kind !== "RECOVERY_COMMITTED"
) {
return null;
}
return snapshot.source as RealtimeCommittedRecovery;
}
function parseVoidResult(
value: unknown,
): ParsedResult<void> | null {
return parseResult(value, (candidate) =>
candidate === undefined
? Object.freeze({ value: undefined })
: null,
);
}
function parseOutcomeSnapshot<Value>(
snapshot: DataSnapshot | null,
parseValue: (candidate: unknown) => ParsedValue<Value> | null,
): ParsedOutcome<Value> | null {
if (
!snapshot ||
!hasOneExactKeySet(snapshot, [
["result"],
["result", "serverNotBeforeMs"],
])
) {
return null;
}
const result = parseResult(snapshot.values.result, parseValue);
if (!result) return null;
if (result.ok) {
return hasExactKeys(snapshot, ["result"])
? Object.freeze({
ok: true,
result: result.result,
value: result.value,
})
: null;
}
const serverNotBeforeMs = parseNotBefore(
snapshot.values.serverNotBeforeMs,
);
if (serverNotBeforeMs === undefined) return null;
return Object.freeze({
ok: false,
failure: result.failure,
serverNotBeforeMs,
});
}
function parseResult<Value>(
value: unknown,
parseValue: (candidate: unknown) => ParsedValue<Value> | null,
): ParsedResult<Value> | null {
const snapshot = captureDataSnapshot(value);
if (!snapshot || !safeFrozen(snapshot.source)) return null;
if (snapshot.values.ok === true) {
if (!hasExactKeys(snapshot, ["ok", "value"])) return null;
const parsedValue = parseValue(snapshot.values.value);
const canonical = Object.freeze({
ok: true,
value: snapshot.values.value,
});
if (
!parsedValue ||
!isRealtimeResult(
canonical,
(candidate: unknown): candidate is unknown => true,
)
) {
return null;
}
return Object.freeze({
ok: true,
result: snapshot.source as SuccessResult<unknown>,
value: parsedValue.value,
});
}
const failure = parseFailureResultSnapshot(snapshot);
return failure
? Object.freeze({ ok: false, failure })
: null;
}
function parseFailureResult(
value: unknown,
): FailureSnapshot | null {
return parseFailureResultSnapshot(captureDataSnapshot(value));
}
function parseFailureResultSnapshot(
snapshot: DataSnapshot | null,
): FailureSnapshot | null {
if (
!snapshot ||
!hasExactKeys(snapshot, ["error", "ok"]) ||
snapshot.values.ok !== false ||
!safeFrozen(snapshot.source)
) {
return null;
}
const error = captureDataSnapshot(snapshot.values.error);
if (
!error ||
!hasExactKeys(error, [
"kind",
"operation",
"retryable",
]) ||
!safeFrozen(error.source)
) {
return null;
}
const canonicalError = Object.freeze({
kind: error.values.kind,
operation: error.values.operation,
retryable: error.values.retryable,
});
const canonical = Object.freeze({
error: canonicalError,
ok: false,
});
if (
!isRealtimeResult(
canonical,
(candidate: unknown): candidate is never => false,
)
) {
return null;
}
const validated =
canonical as Extract<RealtimeResult<never>, { ok: false }>;
return Object.freeze({
result: snapshot.source as FailureResult,
kind: validated.error.kind,
retryable: validated.error.retryable,
});
}
function captureDataSnapshot(value: unknown): DataSnapshot | null {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
}
try {
const prototype = Object.getPrototypeOf(value);
if (
prototype !== Object.prototype &&
prototype !== null
) {
return null;
}
const descriptors = Object.getOwnPropertyDescriptors(value);
const ownKeys = Reflect.ownKeys(descriptors);
if (ownKeys.some((key) => typeof key !== "string")) {
return null;
}
const keys = (ownKeys as string[]).sort();
const values = Object.create(null) as Record<string, unknown>;
for (const key of keys) {
const descriptor = descriptors[key];
if (
!descriptor ||
!("value" in descriptor) ||
descriptor.enumerable !== true
) {
return null;
}
Object.defineProperty(values, key, {
configurable: false,
enumerable: true,
value: descriptor.value,
writable: false,
});
}
return Object.freeze({
source: value,
keys: Object.freeze(keys),
values: Object.freeze(values),
});
} catch {
return null;
}
}
function hasOneExactKeySet(
snapshot: DataSnapshot,
expected: readonly (readonly string[])[],
): boolean {
return expected.some((keys) => hasExactKeys(snapshot, keys));
}
function hasExactKeys(
snapshot: DataSnapshot,
expected: readonly string[],
): boolean {
const sorted = [...expected].sort();
return (
snapshot.keys.length === sorted.length &&
snapshot.keys.every((key, index) => key === sorted[index])
);
}
function parseNotBefore(
value: unknown,
): number | null | undefined {
if (value === undefined || value === null) return null;
return typeof value === "number" &&
Number.isSafeInteger(value) &&
value >= 0
? value
: undefined;
}
function captureRetry(
result: FailureResult,
terminalResult: FailureResult,
at: number,
serverNotBeforeMs: number | null = null,
): Retry {
return Object.freeze({
result,
terminalResult,
at,
notBeforeMs: serverNotBeforeMs,
});
}
function canRetry(
retry: Retry,
attempts: number,
startedAt: number,
now: number,
policy: ReconnectPolicy,
): boolean {
if (attempts >= policy.maxAttempts) return false;
const remaining = reconnectBudgetRemaining(
policy,
startedAt,
now,
);
const serverDelay = remainingServerDelay(retry, now);
return (
remaining > 0 &&
serverDelay !== null &&
serverDelay <= policy.maxDelayMs &&
serverDelay < remaining
);
}
function nextDelay(
retry: Retry,
attempts: number,
startedAt: number,
now: number,
policy: ReconnectPolicy,
random: () => number,
): number | null {
const serverNotBeforeMs = remainingServerDelay(retry, now);
if (serverNotBeforeMs === null) return null;
return calculateReconnectDelay({
policy,
attemptIndex: attempts,
remainingElapsedMs: reconnectBudgetRemaining(
policy,
startedAt,
now,
),
random,
serverNotBeforeMs,
});
}
function remainingServerDelay(
retry: Retry,
now: number,
): number | null {
if (!Number.isFinite(now) || now < retry.at) return null;
return retry.notBeforeMs === null
? 0
: Math.max(
0,
Math.ceil(retry.notBeforeMs - (now - retry.at)),
);
}
function offlineRetry(
at: number,
operation: "CONNECT" | "RECEIVE",
terminalResult?: FailureResult,
): Retry {
const result = realtimeFailure("OFFLINE", operation, true);
return captureRetry(
result,
terminalResult ?? result,
at,
);
}
function providerRetry(
at: number,
operation: "CONNECT" | "RECEIVE",
terminalResult?: FailureResult,
): Retry {
const result = realtimeFailure(
"PROVIDER_UNAVAILABLE",
operation,
true,
);
return captureRetry(
result,
terminalResult ?? result,
at,
);
}
function reconnectable(
failureSnapshot: FailureSnapshot,
serverNotBeforeMs: number | null,
): boolean {
if (
serverNotBeforeMs === null &&
(failureSnapshot.kind === "RATE_LIMITED" ||
failureSnapshot.kind === "PROVIDER_UNAVAILABLE")
) {
return false;
}
return (
failureSnapshot.retryable &&
RETRYABLE_KINDS.includes(
failureSnapshot.kind as (typeof RETRYABLE_KINDS)[number],
)
);
}
function closeLateSession(
outcome: Settled<unknown>,
): void {
if (outcome.kind !== "VALUE") return;
const parsed = parseAttemptOutcome<unknown>(outcome.value);
if (parsed?.ok) safelyClose(parsed.value.session);
}
function safeNow(clock: ClockPort): number | null {
try {
const value = clock.now();
return Number.isFinite(value) ? value : null;
} catch {
return null;
}
}
function safeOnline(
environment: RealtimeReconnectEnvironment,
): boolean | null {
try {
const value = environment.online();
return typeof value === "boolean" ? value : null;
} catch {
return null;
}
}
function safeCurrent(check: () => boolean): boolean {
try {
return check() === true;
} catch {
return false;
}
}
function safeSignalAborted(
signal: AbortSignal | undefined,
): boolean | null {
if (!signal) return false;
try {
const aborted = signal.aborted;
return typeof aborted === "boolean" ? aborted : null;
} catch {
return null;
}
}
function safeFrozen(value: object): boolean {
try {
return Object.isFrozen(value);
} catch {
return false;
}
}
function safelyAddAbortListener(
signal: AbortSignal | undefined,
listener: () => void,
): boolean {
if (!signal) return true;
try {
signal.addEventListener("abort", listener, { once: true });
return true;
} catch {
return false;
}
}
function safelyRemoveAbortListener(
signal: AbortSignal | undefined,
listener: () => void,
): void {
try {
signal?.removeEventListener("abort", listener);
} catch {
// Cleanup is best effort for a hostile signal implementation.
}
}
function safelyClose<ClosedReceipt>(
active: RealtimeReconnectSession<ClosedReceipt> | null,
): void {
try {
active?.close();
} catch {
// The generation is fenced even if host cleanup throws.
}
}
function safelyUnsubscribe(
unsubscribe: (() => void) | undefined,
): void {
try {
unsubscribe?.();
} catch {
// Cleanup cannot reopen a terminal run.
}
}
function safelyWake(wake: (() => void) | null): void {
try {
wake?.();
} catch {
// Wake-up remains best effort during terminal cleanup.
}
}
function recoveryFailure(
kind: RealtimeFailureKind,
): FailureResult {
return realtimeFailure(kind, "RECOVER");
}
function failure(kind: RealtimeFailureKind): FailureResult {
return realtimeFailure(kind, "CONNECT", false);
}