Skip to content

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

Package listing

@kody/planetscale

src/loop-storage.ts

55 lines · 1.7 KB · TypeScript
import { packageStorage } from 'kody:runtime'
import { asLoopConfig } from './loop-helpers.ts'
import type { LoopConfig } from './types.ts'

const CONFIG_KEY = 'loop'

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.
		return fallback
	}
}

async function ensureConfigTable(store: ReturnType<typeof packageStorage>) {
	await store.sql(
		'CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, json TEXT NOT NULL, updated_at TEXT NOT NULL)',
		[],
	)
}

export async function readLoopConfig(): Promise<LoopConfig> {
	return await withStore({}, async (store) => {
		await ensureConfigTable(store)
		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 {
			return asLoopConfig(JSON.parse(String(row.json)))
		} catch {
			return {}
		}
	})
}

export async function writeLoopConfig(next: LoopConfig): Promise<LoopConfig> {
	let persisted = false
	const saved = await withStore(next, async (store) => {
		await ensureConfigTable(store)
		await store.sql('INSERT OR REPLACE INTO config (key, json, updated_at) VALUES (?, ?, ?)', [
			CONFIG_KEY,
			JSON.stringify(next),
			new Date().toISOString(),
		])
		persisted = true
		return next
	})
	if (!persisted) {
		throw new Error(
			'packageStorage is only available when this package is running as itself (a job, webhook, or an export in your fork). Import configure-loop from the fork to save targets, or pass organization/database/repo on each run-loop call.',
		)
	}
	return saved
}