chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
createBrowserLifecycleRuntime,
|
||||
type BrowserLifecycleRuntime,
|
||||
} from "../adapters/platform/browser-lifecycle.ts";
|
||||
import type {
|
||||
ResolvedRuntimeCapabilities,
|
||||
RuntimeHealth,
|
||||
RuntimeStopReason,
|
||||
} from "../contracts/runtime-capabilities.ts";
|
||||
import type { ServiceWorkerRuntimeHost } from "../contracts/service-worker.ts";
|
||||
import { createServiceWorkerRuntimeHost } from "./register-service-worker.ts";
|
||||
|
||||
/**
|
||||
* §3.4 / §20.3. Optional runtime host.
|
||||
*
|
||||
* Creating this object has no side effect: no listener, timer, network call,
|
||||
* IndexedDB open or worker is created until `startAfterMount()` runs from the
|
||||
* first committed React effect. `stop()` unwinds in reverse order.
|
||||
*/
|
||||
|
||||
export type OptionalRuntimeHost = Readonly<{
|
||||
readonly realtime: null;
|
||||
readonly webWorkers: null;
|
||||
readonly serviceWorker: ServiceWorkerRuntimeHost | null;
|
||||
readonly offlineCommands: null;
|
||||
browserLifecycle(): BrowserLifecycleRuntime | null;
|
||||
health(): Readonly<Record<string, RuntimeHealth>>;
|
||||
startAfterMount(): Promise<void>;
|
||||
stop(reason?: RuntimeStopReason): Promise<void>;
|
||||
}>;
|
||||
|
||||
export type OptionalRuntimeHostInput = Readonly<{
|
||||
capabilities: ResolvedRuntimeCapabilities;
|
||||
routerBasePath: string;
|
||||
buildId: string;
|
||||
/** Explicit host seam for deterministic lifecycle tests and platform shells. */
|
||||
serviceWorkerHost?: ServiceWorkerRuntimeHost | null;
|
||||
browserLifecycleHost?: Parameters<typeof createBrowserLifecycleRuntime>[0];
|
||||
host?: Parameters<typeof createServiceWorkerRuntimeHost>[0]["host"];
|
||||
blockers?: readonly (() => boolean)[];
|
||||
observe?: (observation: Readonly<{ event: string; outcome: string }>) => void;
|
||||
}>;
|
||||
|
||||
export function createOptionalRuntimeHost(
|
||||
input: OptionalRuntimeHostInput,
|
||||
): OptionalRuntimeHost {
|
||||
const { capabilities } = input;
|
||||
|
||||
const serviceWorker = Object.hasOwn(input, "serviceWorkerHost")
|
||||
? (input.serviceWorkerHost ?? null)
|
||||
: createServiceWorkerRuntimeHost({
|
||||
capabilities,
|
||||
routerBasePath: input.routerBasePath,
|
||||
buildId: input.buildId,
|
||||
...(input.host ? { host: input.host } : {}),
|
||||
...(input.blockers ? { blockers: input.blockers } : {}),
|
||||
...(input.observe ? { observe: input.observe } : {}),
|
||||
});
|
||||
|
||||
let lifecycle: BrowserLifecycleRuntime | null = null;
|
||||
let started = false;
|
||||
let startPromise: Promise<void> | null = null;
|
||||
let stopPromise: Promise<void> | null = null;
|
||||
let stopRequested = false;
|
||||
const health: Record<string, RuntimeHealth> = {
|
||||
realtime: capabilities.realtime.length === 0 ? "DISABLED" : "UNAVAILABLE",
|
||||
webWorkers: capabilities.webWorkers.length === 0 ? "DISABLED" : "UNAVAILABLE",
|
||||
serviceWorker: serviceWorker ? "UNAVAILABLE" : "DISABLED",
|
||||
offlineCommands: capabilities.offlineCommands ? "UNAVAILABLE" : "DISABLED",
|
||||
};
|
||||
|
||||
/**
|
||||
* §3.4 start order:
|
||||
* 1. offline foreground browser lifecycle observer
|
||||
* 2. realtime runtime
|
||||
* 3. no Web Worker prewarm
|
||||
* 4. Service Worker active/cleanup controller
|
||||
*/
|
||||
async function startAfterMount(): Promise<void> {
|
||||
// A stopped host is terminal. Its children (notably the Service Worker page
|
||||
// controller) own one-shot listeners and timers and cannot be resurrected.
|
||||
if (stopPromise) return stopPromise;
|
||||
if (startPromise) return startPromise;
|
||||
startPromise = (async () => {
|
||||
// 1. The lifecycle observer is the single window listener owner. It is
|
||||
// only created when something downstream can actually consume it.
|
||||
if (serviceWorker || capabilities.realtime.length > 0) {
|
||||
lifecycle = createBrowserLifecycleRuntime(input.browserLifecycleHost);
|
||||
}
|
||||
|
||||
// 2. Realtime stays NOT_SELECTED until a product contribution exists
|
||||
// (§13.6), so there is nothing to start and nothing to observe.
|
||||
|
||||
// 3. Web Workers are created lazily on first task; there is no prewarm.
|
||||
|
||||
// 4. Service Worker registration or the exact-owned cleanup action.
|
||||
if (serviceWorker) {
|
||||
const outcome = await serviceWorker.start();
|
||||
// `stop()` may have fenced this generation while start was awaiting a
|
||||
// browser operation. A late result must never reactivate health.
|
||||
if (!stopRequested) {
|
||||
health.serviceWorker =
|
||||
outcome.kind === "ACTIVE"
|
||||
? "AVAILABLE"
|
||||
: outcome.kind === "DISABLED"
|
||||
? "DISABLED"
|
||||
: outcome.kind === "INCOMPATIBLE"
|
||||
? "INCOMPATIBLE"
|
||||
: "DEGRADED";
|
||||
}
|
||||
}
|
||||
if (!stopRequested) started = true;
|
||||
})();
|
||||
return startPromise;
|
||||
}
|
||||
|
||||
async function stop(
|
||||
reason: RuntimeStopReason = "APPLICATION_SHUTDOWN",
|
||||
): Promise<void> {
|
||||
void reason;
|
||||
stopRequested = true;
|
||||
if (stopPromise) return stopPromise;
|
||||
stopPromise = (async () => {
|
||||
// Serialize teardown behind any in-flight browser registration. Cleanup
|
||||
// then observes the final acquired resources and unwinds them exactly once.
|
||||
await startPromise?.catch(() => {});
|
||||
// Reverse of the start order.
|
||||
if (serviceWorker && (started || startPromise)) {
|
||||
await serviceWorker.stop().catch(() => {});
|
||||
}
|
||||
if (serviceWorker) {
|
||||
health.serviceWorker = "DISABLED";
|
||||
}
|
||||
lifecycle?.dispose();
|
||||
lifecycle = null;
|
||||
started = false;
|
||||
startPromise = null;
|
||||
})();
|
||||
return stopPromise;
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
realtime: null,
|
||||
webWorkers: null,
|
||||
serviceWorker,
|
||||
offlineCommands: null,
|
||||
browserLifecycle: () => lifecycle,
|
||||
health: () => Object.freeze({ ...health }),
|
||||
startAfterMount,
|
||||
stop,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user