clone

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

feat/141-demo-review-fixes
lofi / package / runtime / config.ts
config.ts 4.5K
1// Package-owned environment and adapter configuration.23import type { DbConfig, IncompatibleBrowserBrokerConfigurationHandler } from "jazz-tools";4import { getLofiApp } from "./app.ts";5import { anchorAppId, configuredServerUrl, readDeclaredSink } from "./data-sink.ts";67/**8 * The device-local anchor app id: storage keys, the account secret store, and9 * the local database namespace key off this value, so state written before a10 * sink is declared survives the declaration. See `data-sink.ts` for how it is11 * derived.12 */13export const appId = anchorAppId;1415/** The sync location currently in effect and where it came from. */16export type ActiveSink = {17 /** `declared` is a runtime-declared sink; `default` is the compiled managed app. */18 source: "declared" | "default";19 appId: string;20 serverUrl: string;21 label?: string;22 /** Present when the enrolled ticket is possession-bound to this device. */23 pop?: { ticketId: string };24};2526/**27 * Resolves the sync location in effect: a runtime-declared sink wins, the28 * compiled managed app (when the deployment has one) is the default, and29 * `null` means this device is local-only until the user declares one.30 */31export function activeSink(): ActiveSink | null {32 const declared = readDeclaredSink();33 if (declared) {34 return {35 source: "declared",36 appId: declared.appId,37 serverUrl: declared.serverUrl,38 ...(declared.label ? { label: declared.label } : {}),39 ...(declared.pop ? { pop: declared.pop } : {}),40 };41 }42 return configuredServerUrl43 ? { source: "default", appId: anchorAppId, serverUrl: configuredServerUrl }44 : null;45}4647/** The server URL of the sync location in effect, if any. */48export function activeServerUrl(): string | undefined {49 return activeSink()?.serverUrl;50}5152/** The Jazz app id of the sync location in effect, falling back to the anchor. */53export function activeAppId(): string {54 return activeSink()?.appId ?? anchorAppId;55}5657/** Whether a sync location exists (declared or compiled), so sync/backup is possible. */58export function syncAvailable(): boolean {59 return activeSink() !== null;60}6162const syncElectionKey = `lofi:sync-elected:${appId}`;6364/**65 * Whether the user on this device has elected to back up and sync this account.66 * First boot is local-only; the account only reaches the network once the user67 * opts in (see `session.ts`), so nothing leaves the device by default.68 */69export function syncElected(): boolean {70 if (!syncAvailable() || typeof localStorage === "undefined") return false;71 try {72 return localStorage.getItem(syncElectionKey) === "1";73 } catch {74 return false;75 }76}7778/** Records (or clears) the sync election. Ignored when no sync location exists. */79export function setSyncElected(elected: boolean): void {80 if (!syncAvailable() || typeof localStorage === "undefined") return;81 try {82 if (elected) localStorage.setItem(syncElectionKey, "1");83 else localStorage.removeItem(syncElectionKey);84 } catch {85 // A private-mode storage failure must not break account setup.86 }87}8889/** Whether writes actually replicate right now: a sync location exists *and* is elected. */90export function syncing(): boolean {91 return syncAvailable() && syncElected();92}9394export function databaseConfig(95 secret: string,96 accountNamespace: string,97 mode: "local" | "managed" = syncing() ? "managed" : "local",98 connect = mode === "managed" && syncing(),99 onIncompatibleBrowserBrokerConfiguration: IncompatibleBrowserBrokerConfigurationHandler =100 () => {},101 serverUrlOverride?: string,102): DbConfig {103 const app = getLofiApp();104 const sink = activeSink();105 // The local tier stays on the anchor so first-boot data is always found; the106 // managed tier belongs to the active sink's store, so a declared sink gets107 // its own database namespace and connection identity.108 const connectionAppId = mode === "managed" ? (sink?.appId ?? appId) : appId;109 return {110 appId: connectionAppId,111 // The same local-first secret opens the same account whether or not a server112 // is attached, so electing to sync later preserves all existing data.113 // A possession-bound sink connects through its token URL, minted by the114 // proof-of-possession exchange each boot; everything else uses the sink115 // URL verbatim.116 ...(connect && sink ? { serverUrl: serverUrlOverride ?? sink.serverUrl } : {}),117 secret,118 driver: {119 type: "persistent",120 dbName: `${app.databaseName}-${connectionAppId}-${accountNamespace}-${mode}`,121 },122 onIncompatibleBrowserBrokerConfiguration,123 };124}