{"version":3,"file":"snapshot-live.mjs","names":[],"sources":["../../src/db/snapshot-live.ts"],"sourcesContent":["/**\n * Live-snapshot runtime adapter — connect to a deployed backend like the\n * platform's builds and previews do, with nothing materialized on disk.\n *\n * Instead of pointing getDb() at a snapshot.db file, this dialect fetches the\n * backend's portable content snapshot (`GET /_emdash/api/snapshot`, frontend\n * service-account Bearer token) and loads it into an IN-MEMORY better-sqlite3\n * database. Queries run through a delegating handle; whenever the loaded\n * snapshot is older than `refreshMs` the handle re-fetches in the background\n * and atomically swaps in a fresh database (stale-while-revalidate). A\n * long-running `astro dev` therefore always renders the backend's current\n * content — publish in the admin, reload the page — with no pull step and no\n * snapshot file.\n *\n * Git-backed collections (`storage: \"git\"`) keep their entries in the site\n * repo itself as content/<collection>/<slug>.json — the backend only holds\n * their schema — so those entries are merged from the local working tree on\n * every (re)load, mirroring bin/snapshot-to-sqlite.mjs in the static-frontend\n * template. Local edits to git content show up on the next refresh too.\n *\n * Node-only (better-sqlite3), like the plain sqlite adapter.\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport BetterSqlite3 from \"better-sqlite3\";\nimport { type Dialect, SqliteDialect } from \"kysely\";\n\nimport type { SnapshotLiveConfig } from \"./adapters.js\";\n\ntype Db = InstanceType<typeof BetterSqlite3>;\ntype SqliteValue = string | number | bigint | Buffer | null;\n\ninterface SnapshotTableSchema {\n\tcolumns: string[];\n\ttypes?: Record<string, string>;\n}\n\ninterface SnapshotPayload {\n\ttables: Record<string, Array<Record<string, unknown>>>;\n\tschema: Record<string, SnapshotTableSchema>;\n\tgeneratedAt?: string;\n}\n\n/**\n * Tables the public render path queries but the snapshot deliberately omits\n * (comments carry commenter PII and hydrate client-side; cron is runtime\n * bookkeeping). Empty stand-ins keep queries from throwing on them.\n */\nconst STUB_TABLES: Record<string, string> = {\n\t_emdash_comments:\n\t\t\"id text primary key, collection text, content_id text, parent_id text, author_name text, author_email text, author_url text, author_user_id text, body text, status text, ip_hash text, user_agent text, moderation_metadata text, created_at text, updated_at text\",\n\t_emdash_comment_reactions:\n\t\t\"id text primary key, comment_id text, reaction text, voter_hash text, created_at text\",\n\t_emdash_cron_tasks:\n\t\t\"id text primary key, plugin_id text, task_name text, schedule text, is_oneshot integer, data text, next_run_at text, last_run_at text, status text, locked_at text, enabled integer, created_at text\",\n};\n\n/** Field types stored as JSON strings in content tables. */\nconst TRAILING_SLASHES = /\\/+$/;\n\nconst JSON_FIELD_TYPES = new Set([\n\t\"portableText\",\n\t\"json\",\n\t\"multiSelect\",\n\t\"repeater\",\n\t\"media\",\n\t\"relation\",\n\t\"file\",\n]);\n\nfunction toSqliteValue(value: unknown): SqliteValue {\n\tif (value === undefined || value === null) return null;\n\tif (typeof value === \"boolean\") return value ? 1 : 0;\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"bigint\") {\n\t\treturn value;\n\t}\n\tif (Buffer.isBuffer(value)) return value;\n\treturn JSON.stringify(value);\n}\n\nasync function fetchSnapshot(\n\tbackendUrl: string,\n\ttoken: string,\n\tincludeDrafts: boolean,\n): Promise<SnapshotPayload> {\n\tconst url = `${backendUrl}/_emdash/api/snapshot${includeDrafts ? \"?drafts=true\" : \"\"}`;\n\tconst res = await fetch(url, {\n\t\theaders: { Authorization: `Bearer ${token}`, \"X-EmDash-Request\": \"1\" },\n\t});\n\tif (!res.ok) {\n\t\tconst detail = (await res.text().catch(() => \"\")).slice(0, 200);\n\t\tthrow new Error(`snapshot fetch failed: ${res.status} ${detail}`);\n\t}\n\tconst body: unknown = await res.json();\n\tconst snap =\n\t\tbody && typeof body === \"object\" && \"data\" in body\n\t\t\t? (body as { data: unknown }).data\n\t\t\t: body;\n\tconst payload = snap as SnapshotPayload;\n\tif (!payload || typeof payload !== \"object\" || !payload.tables || !payload.schema) {\n\t\tthrow new Error(\"snapshot response missing tables/schema\");\n\t}\n\treturn payload;\n}\n\n/**\n * Merge git-backed collection entries (content/<collection>/<slug>.json in the\n * site repo) into their ec_* tables. The backend holds only their schema.\n */\nfunction mergeGitContent(\n\tdb: Db,\n\tsnap: SnapshotPayload,\n\tcontentDir: string,\n\tincludeDrafts: boolean,\n): number {\n\tconst collections = (snap.tables._emdash_collections ?? []).filter(\n\t\t(c) => c.storage === \"git\",\n\t);\n\tif (collections.length === 0) return 0;\n\n\tconst fieldsByCollection = new Map<unknown, Array<Record<string, unknown>>>();\n\tfor (const f of snap.tables._emdash_fields ?? []) {\n\t\tconst list = fieldsByCollection.get(f.collection_id) ?? [];\n\t\tlist.push(f);\n\t\tfieldsByCollection.set(f.collection_id, list);\n\t}\n\n\tlet merged = 0;\n\tconst insertAll = db.transaction(() => {\n\t\tfor (const collection of collections) {\n\t\t\tconst slugValue = typeof collection.slug === \"string\" ? collection.slug : \"\";\n\t\t\tif (!slugValue) continue;\n\t\t\tconst table = `ec_${slugValue}`;\n\t\t\tconst cols = snap.schema[table]?.columns;\n\t\t\tif (!cols) continue;\n\t\t\tconst dir = path.join(contentDir, slugValue);\n\t\t\tif (!existsSync(dir)) continue;\n\t\t\tconst fields = fieldsByCollection.get(collection.id) ?? [];\n\t\t\tconst stmt = db.prepare(\n\t\t\t\t`INSERT OR REPLACE INTO \"${table}\" (${cols.map((c) => `\"${c}\"`).join(\",\")}) VALUES (${cols.map(() => \"?\").join(\",\")})`,\n\t\t\t);\n\t\t\tfor (const file of readdirSync(dir)) {\n\t\t\t\tif (!file.endsWith(\".json\")) continue;\n\t\t\t\tlet entry: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tentry = JSON.parse(readFileSync(path.join(dir, file), \"utf8\")) as Record<\n\t\t\t\t\t\tstring,\n\t\t\t\t\t\tunknown\n\t\t\t\t\t>;\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst status = typeof entry.status === \"string\" ? entry.status : \"published\";\n\t\t\t\tif (status !== \"published\" && !includeDrafts) continue;\n\t\t\t\tconst slug = typeof entry.slug === \"string\" ? entry.slug : file.slice(0, -5);\n\t\t\t\tconst updatedAt =\n\t\t\t\t\ttypeof entry.updatedAt === \"string\" ? entry.updatedAt : new Date().toISOString();\n\t\t\t\tconst row: Record<string, unknown> = {\n\t\t\t\t\tid: entry.id ?? slug,\n\t\t\t\t\tslug,\n\t\t\t\t\tstatus,\n\t\t\t\t\tlocale: entry.locale ?? \"en\",\n\t\t\t\t\ttranslation_group: entry.translationGroup ?? slug,\n\t\t\t\t\tcreated_at: entry.createdAt ?? updatedAt,\n\t\t\t\t\tupdated_at: updatedAt,\n\t\t\t\t\tpublished_at: entry.publishedAt ?? updatedAt,\n\t\t\t\t\tversion: 1,\n\t\t\t\t};\n\t\t\t\tconst data = (entry.data ?? {}) as Record<string, unknown>;\n\t\t\t\tfor (const field of fields) {\n\t\t\t\t\tconst fieldSlug = typeof field.slug === \"string\" ? field.slug : \"\";\n\t\t\t\t\tif (!fieldSlug) continue;\n\t\t\t\t\tconst value = data[fieldSlug];\n\t\t\t\t\tif (value === undefined) continue;\n\t\t\t\t\trow[fieldSlug] =\n\t\t\t\t\t\tJSON_FIELD_TYPES.has(String(field.type)) ||\n\t\t\t\t\t\t(value !== null && typeof value === \"object\")\n\t\t\t\t\t\t\t? JSON.stringify(value)\n\t\t\t\t\t\t\t: value;\n\t\t\t\t}\n\t\t\t\tstmt.run(cols.map((c) => toSqliteValue(row[c])));\n\t\t\t\tmerged++;\n\t\t\t}\n\t\t}\n\t});\n\tinsertAll();\n\treturn merged;\n}\n\n/** Build an in-memory database from a snapshot payload + local git content. */\nfunction buildDatabase(\n\tsnap: SnapshotPayload,\n\tcontentDir: string,\n\tincludeDrafts: boolean,\n): Db {\n\tconst db = new BetterSqlite3(\":memory:\");\n\tdb.pragma(\"foreign_keys = OFF\");\n\n\tfor (const [table, info] of Object.entries(snap.schema)) {\n\t\tconst cols = info.columns\n\t\t\t.map((c) => `\"${c}\" ${info.types?.[c] ?? \"\"}`.trim())\n\t\t\t.join(\", \");\n\t\tdb.exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${cols})`);\n\t}\n\tfor (const [table, cols] of Object.entries(STUB_TABLES)) {\n\t\tif (!snap.schema[table]) db.exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${cols})`);\n\t}\n\n\tconst insertAll = db.transaction(() => {\n\t\tfor (const [table, rows] of Object.entries(snap.tables)) {\n\t\t\tif (!Array.isArray(rows) || rows.length === 0) continue;\n\t\t\tconst cols = snap.schema[table]?.columns;\n\t\t\tif (!cols) continue;\n\t\t\tconst stmt = db.prepare(\n\t\t\t\t`INSERT OR IGNORE INTO \"${table}\" (${cols.map((c) => `\"${c}\"`).join(\",\")}) VALUES (${cols.map(() => \"?\").join(\",\")})`,\n\t\t\t);\n\t\t\tfor (const row of rows) {\n\t\t\t\tstmt.run(cols.map((c) => toSqliteValue(row[c])));\n\t\t\t}\n\t\t}\n\t});\n\tinsertAll();\n\n\tmergeGitContent(db, snap, contentDir, includeDrafts);\n\treturn db;\n}\n\n/**\n * Create a live-snapshot dialect: in-memory SQLite, continuously refreshed\n * from the backend at `config.url`.\n */\nexport function createDialect(config: SnapshotLiveConfig): Dialect {\n\tconst backendUrl = (config.url ?? \"\").replace(TRAILING_SLASHES, \"\");\n\tconst token = config.token || process.env.EMDASH_API_TOKEN || \"\";\n\tif (!backendUrl) {\n\t\tthrow new Error(\"snapshot-live: `url` (the backend origin) is required\");\n\t}\n\tif (!token) {\n\t\tthrow new Error(\n\t\t\t\"snapshot-live: no API token — set EMDASH_API_TOKEN (or pass `token`); admins: <backend>/_emdash/api/settings/frontend-token\",\n\t\t);\n\t}\n\tconst includeDrafts = config.includeDrafts ?? false;\n\tconst contentDir = path.resolve(config.contentDir ?? \"content\");\n\tconst envRefresh = Number(process.env.EMDASH_LIVE_REFRESH_MS);\n\tconst refreshMs =\n\t\tconfig.refreshMs ?? (Number.isFinite(envRefresh) && envRefresh !== 0 ? envRefresh : 2000);\n\n\tlet current: Db | null = null;\n\tlet loadedAt = 0;\n\tlet inflight: Promise<void> | null = null;\n\tlet announced = false;\n\n\tconst load = async (): Promise<void> => {\n\t\tconst snap = await fetchSnapshot(backendUrl, token, includeDrafts);\n\t\tconst next = buildDatabase(snap, contentDir, includeDrafts);\n\t\tconst prev = current;\n\t\tcurrent = next;\n\t\tloadedAt = Date.now();\n\t\tif (prev) {\n\t\t\t// Delay closing so any statement still draining from the old handle\n\t\t\t// (e.g. a streamed query crossing ticks) finishes safely. Dev-only\n\t\t\t// memory cost, bounded by the refresh interval.\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\ttry {\n\t\t\t\t\tprev.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// already closed\n\t\t\t\t}\n\t\t\t}, 30_000);\n\t\t\ttimer.unref?.();\n\t\t}\n\t\tif (!announced) {\n\t\t\tannounced = true;\n\t\t\tconsole.log(\n\t\t\t\t`[emdash] live content from ${backendUrl} (refresh ${refreshMs > 0 ? `${refreshMs}ms` : \"off\"}${includeDrafts ? \", drafts\" : \"\"})`,\n\t\t\t);\n\t\t}\n\t};\n\n\tconst ensureLoaded = async (): Promise<void> => {\n\t\tif (current) return;\n\t\tinflight ??= load().finally(() => {\n\t\t\tinflight = null;\n\t\t});\n\t\tawait inflight;\n\t\tif (!current) throw new Error(\"snapshot-live: initial snapshot load failed\");\n\t};\n\n\tconst maybeRefresh = (): void => {\n\t\tif (refreshMs <= 0 || inflight || Date.now() - loadedAt < refreshMs) return;\n\t\tinflight = load()\n\t\t\t.catch((err: unknown) => {\n\t\t\t\t// Keep serving the last good snapshot; surface the failure once per attempt.\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tconsole.warn(`[emdash] live snapshot refresh failed: ${message}`);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tinflight = null;\n\t\t\t});\n\t};\n\n\tconst handle = {\n\t\tprepare(sql: string) {\n\t\t\tif (!current) throw new Error(\"snapshot-live: database not initialized\");\n\t\t\tmaybeRefresh(); // background; `current` stays valid for this call\n\t\t\treturn current.prepare(sql);\n\t\t},\n\t\tclose(): void {\n\t\t\tcurrent?.close();\n\t\t\tcurrent = null;\n\t\t},\n\t};\n\n\treturn new SqliteDialect({\n\t\tdatabase: async () => {\n\t\t\tawait ensureLoaded();\n\t\t\treturn handle;\n\t\t},\n\t});\n}\n"],"mappings":"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"}