clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/fix-sync-enrollment-startup
lofi / package / runtime / probe.ts
probe.ts 4.7K
1import { activeServerUrl, syncing } from "./config.ts";2// Package-owned development probe.3import { readDeviceCapabilityReport } from "./device-capabilities.ts";4import { type InspectorAdapter, type InspectorSnapshot, mountInspector } from "./inspector.ts";5import { getForegroundRecoveryState, subscribeForegroundRecovery } from "./lifecycle.ts";6import {7 getRuntime,8 getRuntimeDiagnostics,9 reloadBrowserRuntime,10 subscribeRuntimeDiagnostics,11} from "./runtime.ts";12import { describeStoreStatus } from "./store-status.ts";13import { isTransportPausedByInspector, setTransportPausedByInspector } from "./transport-gate.ts";1415export type LofiDevelopmentBridge = InspectorAdapter;1617type LofiDevelopmentGlobal = typeof globalThis & {18 __LOFI_INSPECTOR__?: LofiDevelopmentBridge;19};2021const stateListeners = new Set<() => void>();2223function notifyState(): void {24 for (const listener of stateListeners) listener();25}2627async function readSnapshot(): Promise<InspectorSnapshot> {28 const [runtime, device] = await Promise.all([29 getRuntime(),30 readDeviceCapabilityReport(),31 ]);32 const diagnostics = getRuntimeDiagnostics();33 const lifecycle = getForegroundRecoveryState();34 const authMode = runtime.db.getAuthState().authMode;35 return {36 identity: {37 state: authMode === "local-first"38 ? "device-local key active"39 : authMode === "external"40 ? "external"41 : authMode === "anonymous"42 ? "anonymous"43 : "unavailable",44 backup: syncing() ? "recovery phrase" : "local-only",45 },46 storage: {47 driver: diagnostics.storageState === "persistent-driver-open"48 ? "persistent open"49 : diagnostics.storageState === "persistent-requested"50 ? "persistent requested"51 : "failed",52 persistence: device.persistentPermission === "not-granted"53 ? "not granted"54 : device.persistentPermission,55 fallback: diagnostics.storageState === "failed" ? "unavailable" : "none",56 startupFailure: diagnostics.startupFailure?.code ?? "none",57 },58 sync: {59 mode: activeServerUrl() ? "managed configured" : "local-only",60 transport: activeServerUrl()61 ? isTransportPausedByInspector() ? "paused by inspector" : "live detail unavailable"62 : "not configured",63 store: describeStoreStatus(diagnostics.storeStatus),64 pendingLocalWrites: diagnostics.pendingLocalWrites,65 pendingGlobalWrites: diagnostics.pendingGlobalWrites,66 lastWrite: diagnostics.lastWriteDurability,67 },68 runtime: {69 clients: diagnostics.activeClients,70 consumers: diagnostics.activeConsumers,71 vendorSubscriptions: diagnostics.activeVendorSubscriptions,72 mutationListeners: diagnostics.activeMutationListeners,73 mutationErrors: diagnostics.mutationErrors,74 },75 lifecycle: {76 ...lifecycle,77 transportDetail: activeServerUrl() ? "live detail unavailable" : "not configured",78 },79 multiTab: {80 role: "unavailable",81 detail: "Jazz alpha.53 exposes no supported leader/follower signal",82 },83 };84}8586const bridge: LofiDevelopmentBridge = {87 readSnapshot,88 subscribe(listener) {89 stateListeners.add(listener);90 const unsubscribeRuntime = subscribeRuntimeDiagnostics(listener);91 const unsubscribeLifecycle = subscribeForegroundRecovery(listener);92 let active = true;93 return () => {94 if (!active) return;95 active = false;96 stateListeners.delete(listener);97 unsubscribeRuntime();98 unsubscribeLifecycle();99 };100 },101 async setTransportPaused(paused) {102 if (!activeServerUrl()) {103 throw new Error("Cloud transport pause is unavailable in local-only mode");104 }105 const runtime = await getRuntime();106 // Record the pause before touching the transport so foreground recovery107 // cannot race a reconnect in between and silently override the inspector.108 setTransportPausedByInspector(paused);109 try {110 if (paused) await runtime.db.disconnect();111 else await runtime.db.reconnect();112 } catch (error) {113 setTransportPausedByInspector(!paused);114 throw error;115 }116 notifyState();117 },118 restartClient() {119 // The shutdown-then-navigate boundary is the supported clean-runtime path120 // for the pinned Jazz alpha; reloading without it leaves the persistent121 // worker attached during navigation.122 return reloadBrowserRuntime();123 },124 async clearLocalReplica() {125 const runtime = await getRuntime();126 await runtime.db.deleteClientStorage();127 return await reloadBrowserRuntime();128 },129};130131if (typeof document !== "undefined") {132 (globalThis as LofiDevelopmentGlobal).__LOFI_INSPECTOR__ = bridge;133 const mounted = mountInspector(bridge);134 import.meta.hot?.dispose(() => {135 mounted.dispose();136 delete (globalThis as LofiDevelopmentGlobal).__LOFI_INSPECTOR__;137 });138}