Files
triggerdotdev--trigger.dev/apps/webapp/app/v3/runOpsMigration/splitMode.server.ts
T
2026-07-13 13:32:57 +08:00

85 lines
2.7 KiB
TypeScript

/**
* isSplitEnabled() is the Wave-0 gate. The entire migration/routing/FK-drop family
* MUST be unreachable when this returns false. Default is false (single-DB). Never
* infer split-vs-single from URL string-equality — distinctness is proven by the
* runtime sentinel.
*/
import { env } from "~/env.server";
import { logger } from "~/services/logger.server";
import { probeDistinctDatabases as defaultProbe } from "./distinctDbSentinel.server";
export type SplitModeConfig = {
flagEnabled: boolean;
legacyUrl?: string;
newUrl?: string;
};
export type SplitModeDeps = {
probe?: typeof defaultProbe;
logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void };
};
export async function computeSplitEnabled(
config: SplitModeConfig,
deps: SplitModeDeps = {}
): Promise<boolean> {
// Hard gate #1: explicit positive opt-in. OFF by default -> never probe.
if (!config.flagEnabled) {
return false;
}
// Both URLs are required to even consider a split.
if (!config.legacyUrl || !config.newUrl) {
deps.logger?.warn(
"RUN_OPS_SPLIT_ENABLED is on but RUN_OPS_LEGACY_DATABASE_URL / RUN_OPS_DATABASE_URL are not both set; staying single-DB."
);
return false;
}
// Hard gate #2: runtime sentinel must confirm physically-distinct DBs.
const probe = deps.probe ?? defaultProbe;
const result = await probe(config.legacyUrl, config.newUrl, { logger: deps.logger });
return result.distinct === true;
}
export type SplitRealtimeInterlockConfig = {
splitEnabled: boolean;
nativeRealtimeEnabled: boolean;
};
/**
* Boot-time realtime interlock (pure predicate). Split mode puts NEW-resident
* (run-ops id) runs on the dedicated run-ops DB, but Electric replicates only from the
* control-plane DB — with the native realtime backend OFF those runs are invisible
* and every realtime subscription hangs. Refuse split unless native is on; split-off
* is always allowed regardless of the realtime backend.
*/
export function assertSplitRealtimeInterlock(config: SplitRealtimeInterlockConfig): void {
if (!config.splitEnabled) {
return;
}
if (!config.nativeRealtimeEnabled) {
throw new Error(
"RUN_OPS_SPLIT_ENABLED is on but the native realtime backend (REALTIME_BACKEND_NATIVE_ENABLED) is not enabled — Electric cannot serve NEW-resident runs; refusing to enable split."
);
}
}
let cached: Promise<boolean> | undefined;
export function isSplitEnabled(): Promise<boolean> {
if (!cached) {
cached = computeSplitEnabled(
{
flagEnabled: env.RUN_OPS_SPLIT_ENABLED,
legacyUrl: env.RUN_OPS_LEGACY_DATABASE_URL,
newUrl: env.RUN_OPS_DATABASE_URL,
},
{ logger }
);
}
return cached;
}
export function __resetSplitModeCacheForTests(): void {
cached = undefined;
}