clone

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

main
lofi / docs / spikes / jazz2-baseline / src / main.ts
main.ts 7.3K
1import { BrowserAuthSecretStore, createDb, type Db, type DbConfig } from "jazz-tools";2import { app } from "../schema.ts";3import "./style.css";45const BUILD_ONLY_APP_ID = "00000000-0000-0000-0000-00000000f153";6const APP_ID = import.meta.env.VITE_JAZZ_APP_ID ?? BUILD_ONLY_APP_ID;7const SERVER_URL = import.meta.env.VITE_JAZZ_SERVER_URL;89type Note = {10 id: string;11 body: string;12 createdAt: Date;13};1415type Probe = {16 appId: string;17 serverConfigured: boolean;18 storage: "opfs-requested";19 identity: "local-first-secret";20 ready: boolean;21 activeSubscriptions: number;22 lastDurability: "none" | "local" | "global" | "failed";23};2425declare global {26 interface Window {27 __LOFI_JAZZ2_PROBE__?: {28 snapshot(): Probe;29 create(body: string): Promise<string>;30 update(id: string, body: string): Promise<void>;31 awaitGlobal(): Promise<boolean>;32 disconnect(): Promise<void>;33 reconnect(): Promise<void>;34 shutdown(): Promise<void>;35 };36 }37}3839function databaseConfig(secret: string): DbConfig {40 return {41 appId: APP_ID,42 ...(SERVER_URL ? { serverUrl: SERVER_URL } : {}),43 secret,44 driver: { type: "persistent", dbName: `lofi-jazz2-alpha53-${APP_ID}` },45 };46}4748function capabilitySummary(): string {49 const browserNavigator = navigator as Navigator & {50 storage?: { getDirectory?: unknown };51 locks?: { request?: unknown };52 };53 const browserGlobal = globalThis as typeof globalThis & { SharedWorker?: unknown };54 const capabilities = {55 opfs: typeof browserNavigator.storage?.getDirectory === "function",56 sharedWorker: typeof browserGlobal.SharedWorker === "function",57 messageChannel: typeof MessageChannel === "function",58 webLocks: typeof browserNavigator.locks?.request === "function",59 };60 return Object.entries(capabilities)61 .map(([name, available]) => `${name}=${available ? "yes" : "no"}`)62 .join(", ");63}6465function renderShell(root: HTMLElement) {66 root.innerHTML = `67 <header>68 <p class="eyebrow">Exact vendor control</p>69 <h1>Jazz 2.0.0-alpha.53</h1>70 <p>This intentionally thin app measures the package without a lofi abstraction.</p>71 </header>72 <dl class="status">73 <div><dt>Storage</dt><dd id="storage-state">persistent/OPFS requested</dd></div>74 <div><dt>Identity</dt><dd>local-first secret</dd></div>75 <div><dt>Sync</dt><dd id="sync-state">${76 SERVER_URL ? "configured" : "not configured"77 }</dd></div>78 <div><dt>Capabilities</dt><dd id="capabilities"></dd></div>79 <div><dt>Last write</dt><dd id="durability-state">none</dd></div>80 </dl>81 <form id="note-form">82 <label for="note-body">Retained note</label>83 <div class="composer">84 <input id="note-body" name="body" autocomplete="off" required />85 <button type="submit">Add locally</button>86 </div>87 </form>88 <p id="message" role="status">Opening the durable database…</p>89 <ul id="notes" aria-label="Retained notes"></ul>90 `;91 const capabilities = root.querySelector<HTMLElement>("#capabilities");92 if (capabilities) capabilities.textContent = capabilitySummary();93}9495function renderNotes(root: HTMLElement, notes: Note[]) {96 const list = root.querySelector<HTMLUListElement>("#notes");97 if (!list) return;98 list.replaceChildren(99 ...notes.map((note) => {100 const item = document.createElement("li");101 item.dataset.noteId = note.id;102 item.textContent = note.body;103 return item;104 }),105 );106}107108function setText(root: HTMLElement, selector: string, text: string) {109 const element = root.querySelector<HTMLElement>(selector);110 if (element) element.textContent = text;111}112113async function boot() {114 const root = document.querySelector<HTMLElement>("#app");115 if (!root) throw new Error("The #app mount point is missing.");116 const mount = root;117 renderShell(mount);118119 const secret = await BrowserAuthSecretStore.getOrCreateSecret({ appId: APP_ID });120 let db: Db;121 try {122 db = await createDb(databaseConfig(secret));123 setText(mount, "#storage-state", "persistent/OPFS driver opened");124 } catch (error) {125 setText(mount, "#storage-state", "unsupported");126 setText(127 mount,128 "#message",129 `Durable storage failed: ${error instanceof Error ? error.message : String(error)}`,130 );131 throw error;132 }133134 let activeSubscriptions = 0;135 let lastDurability: Probe["lastDurability"] = "none";136 let lastGlobalConfirmation = Promise.resolve(false);137 const unsubscribe = db.subscribeAll(app.notes, (delta) => {138 renderNotes(mount, delta.all as Note[]);139 setText(mount, "#message", `${delta.all.length} retained note(s)`);140 });141 activeSubscriptions += 1;142143 async function createNote(body: string): Promise<string> {144 const write = db.insert(app.notes, { body, createdAt: new Date() });145 lastDurability = "none";146 setText(mount, "#durability-state", "local write visible; confirming OPFS…");147 try {148 const retained = await write.wait({ tier: "local" });149 lastDurability = "local";150 setText(mount, "#durability-state", "confirmed local");151 if (SERVER_URL) {152 lastGlobalConfirmation = write.wait({ tier: "global" }).then(153 () => {154 lastDurability = "global";155 setText(mount, "#durability-state", "confirmed global");156 setText(mount, "#sync-state", "global write confirmed");157 return true;158 },159 (error) => {160 setText(161 mount,162 "#sync-state",163 `global confirmation failed: ${164 error instanceof Error ? error.message : String(error)165 }`,166 );167 return false;168 },169 );170 }171 return retained.id;172 } catch (error) {173 lastDurability = "failed";174 setText(175 mount,176 "#durability-state",177 `failed: ${error instanceof Error ? error.message : String(error)}`,178 );179 throw error;180 }181 }182183 async function updateNote(id: string, body: string): Promise<void> {184 const write = db.update(app.notes, id, { body });185 setText(mount, "#durability-state", "update visible; confirming OPFS…");186 await write.wait({ tier: "local" });187 lastDurability = "local";188 setText(mount, "#durability-state", "update confirmed local");189 }190191 const form = mount.querySelector<HTMLFormElement>("#note-form");192 form?.addEventListener("submit", (event) => {193 event.preventDefault();194 const input = mount.querySelector<HTMLInputElement>("#note-body");195 const body = input?.value.trim() ?? "";196 if (!body) return;197 if (input) input.value = "";198 void createNote(body);199 });200201 (globalThis as typeof globalThis & Window).__LOFI_JAZZ2_PROBE__ = {202 snapshot: () => ({203 appId: APP_ID,204 serverConfigured: Boolean(SERVER_URL),205 storage: "opfs-requested",206 identity: "local-first-secret",207 ready: true,208 activeSubscriptions,209 lastDurability,210 }),211 create: createNote,212 update: updateNote,213 awaitGlobal: () => lastGlobalConfirmation,214 disconnect: async () => {215 await db.disconnect();216 setText(mount, "#sync-state", "manually disconnected");217 },218 reconnect: async () => {219 await db.reconnect();220 setText(mount, "#sync-state", "reconnected (transport signal unavailable)");221 },222 shutdown: async () => {223 unsubscribe();224 activeSubscriptions -= 1;225 await db.shutdown();226 },227 };228}229230void boot();