Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/morning-briefing

src/storage.ts

70 lines · 2.2 KB · TypeScript
import { packageStorage } from 'kody:runtime'
import type { BriefingInput, BriefingReport } from './types.ts'

const CONFIG_KEY = 'briefing'

async function withStore<T>(fallback: T, work: (store: ReturnType<typeof packageStorage>) => Promise<T>): Promise<T> {
	try {
		return await work(packageStorage())
	} catch {
		// packageStorage is only granted in this package's own runtime (jobs / keyless invoke).
		// Static imports from execute still need dryRun smoke without a writable bucket.
		return fallback
	}
}

export async function ensureSchema() {
	return await withStore({ ok: false as const, storage: 'unavailable' }, async (store) => {
		await store.sql(
			'CREATE TABLE IF NOT EXISTS reports (date TEXT PRIMARY KEY, json TEXT NOT NULL, updated_at TEXT NOT NULL)',
			[],
		)
		await store.sql(
			'CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, json TEXT NOT NULL, updated_at TEXT NOT NULL)',
			[],
		)
		return { ok: true as const, storage: 'kv' }
	})
}

export async function saveReport(report: BriefingReport) {
	const stored = await withStore(false, async (store) => {
		await ensureSchema()
		await store.sql('INSERT OR REPLACE INTO reports (date, json, updated_at) VALUES (?, ?, ?)', [
			report.date,
			JSON.stringify(report),
			new Date().toISOString(),
		])
		return true
	})
	return { ok: true as const, date: report.date, stored }
}

export async function readReport(date: string): Promise<BriefingReport | null> {
	return await withStore(null, async (store) => {
		await ensureSchema()
		const result = await store.sql('SELECT json FROM reports WHERE date = ? LIMIT 1', [date])
		const row = Array.isArray(result?.rows) ? result.rows[0] : null
		if (!row?.json) return null
		try {
			return JSON.parse(String(row.json)) as BriefingReport
		} catch {
			return null
		}
	})
}

export async function loadConfig(): Promise<BriefingInput> {
	return await withStore({}, async (store) => {
		await ensureSchema()
		const result = await store.sql('SELECT json FROM config WHERE key = ? LIMIT 1', [CONFIG_KEY])
		const row = Array.isArray(result?.rows) ? result.rows[0] : null
		if (!row?.json) return {}
		try {
			const parsed = JSON.parse(String(row.json)) as BriefingInput
			return parsed && typeof parsed === 'object' ? parsed : {}
		} catch {
			return {}
		}
	})
}