summaryrefslogtreecommitdiffhomepage
path: root/packages/storage-sqlite/src/storage.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-04 23:22:03 +0900
committerAdam Malczewski <[email protected]>2026-06-04 23:22:03 +0900
commit9b611d614d123462e50492d78202dae696b99aa2 (patch)
tree7e0c85b60dd0becddbb04c29fdf14855c10c090f /packages/storage-sqlite/src/storage.ts
parent974ce6f46c25a522a42c6bd04fd62ce2d031aad5 (diff)
downloaddispatch-9b611d614d123462e50492d78202dae696b99aa2.tar.gz
dispatch-9b611d614d123462e50492d78202dae696b99aa2.zip
feat(core-ext): storage-sqlite, auth-apikey, provider-openai-compat
- storage-sqlite: bun:sqlite StorageNamespace backend + migrations (21 bun tests) - auth-apikey: pure resolver from env → ApiKeyCredentials (4 tests) - provider-openai-compat: OpenAI-compatible SSE stream → ProviderEvents - orchestrator fixes: provider imports (@dispatch/kernel), missing dep, exactOptionalPropertyTypes (omit-when-undefined), root tsconfig refs - vitest excludes storage-sqlite (bun:sqlite); test:bun runs it under bun
Diffstat (limited to 'packages/storage-sqlite/src/storage.ts')
-rw-r--r--packages/storage-sqlite/src/storage.ts92
1 files changed, 92 insertions, 0 deletions
diff --git a/packages/storage-sqlite/src/storage.ts b/packages/storage-sqlite/src/storage.ts
new file mode 100644
index 0000000..2499664
--- /dev/null
+++ b/packages/storage-sqlite/src/storage.ts
@@ -0,0 +1,92 @@
+import { Database } from "bun:sqlite";
+import type { StorageNamespace } from "@dispatch/kernel";
+import { computePending, type Migration } from "./migrate.js";
+
+export interface SqliteStorageBackend {
+ readonly storage: (namespace: string) => StorageNamespace;
+ readonly migrate: (namespace: string, migrations: readonly Migration[]) => Promise<void>;
+ readonly close: () => void;
+}
+
+export type StorageFactory = (opts: { path: string }) => SqliteStorageBackend;
+
+export function createSqliteStorage(opts: { path: string }): SqliteStorageBackend {
+ const db = new Database(opts.path);
+ db.exec("PRAGMA journal_mode = WAL;");
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS kv (
+ namespace TEXT NOT NULL,
+ key TEXT NOT NULL,
+ value TEXT NOT NULL,
+ PRIMARY KEY (namespace, key)
+ );
+ `);
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS _migrations (
+ namespace TEXT NOT NULL,
+ version INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ applied_at TEXT NOT NULL DEFAULT (datetime('now')),
+ PRIMARY KEY (namespace, version)
+ );
+ `);
+
+ const getStmt = db.prepare("SELECT value FROM kv WHERE namespace = ?1 AND key = ?2");
+ const setStmt = db.prepare(
+ "INSERT OR REPLACE INTO kv (namespace, key, value) VALUES (?1, ?2, ?3)",
+ );
+ const deleteStmt = db.prepare("DELETE FROM kv WHERE namespace = ?1 AND key = ?2");
+ const hasStmt = db.prepare("SELECT 1 FROM kv WHERE namespace = ?1 AND key = ?2");
+ const keysAllStmt = db.prepare("SELECT key FROM kv WHERE namespace = ?1");
+ const keysPrefixStmt = db.prepare("SELECT key FROM kv WHERE namespace = ?1 AND key LIKE ?2");
+ const appliedVersionsStmt = db.prepare("SELECT version FROM _migrations WHERE namespace = ?1");
+ const recordMigrationStmt = db.prepare(
+ "INSERT INTO _migrations (namespace, version, name) VALUES (?1, ?2, ?3)",
+ );
+
+ function storage(namespace: string): StorageNamespace {
+ return {
+ get: async (key: string) => {
+ const row = getStmt.get(namespace, key) as { value: string } | null;
+ return row?.value ?? null;
+ },
+ set: async (key: string, value: string) => {
+ setStmt.run(namespace, key, value);
+ },
+ delete: async (key: string) => {
+ deleteStmt.run(namespace, key);
+ },
+ has: async (key: string) => {
+ return hasStmt.get(namespace, key) !== null;
+ },
+ keys: async (prefix?: string) => {
+ if (prefix !== undefined) {
+ const rows = keysPrefixStmt.all(namespace, `${prefix}%`) as { key: string }[];
+ return rows.map((r) => r.key);
+ }
+ const rows = keysAllStmt.all(namespace) as { key: string }[];
+ return rows.map((r) => r.key);
+ },
+ };
+ }
+
+ async function migrate(namespace: string, migrations: readonly Migration[]): Promise<void> {
+ const rows = appliedVersionsStmt.all(namespace) as { version: number }[];
+ const applied = new Set(rows.map((r) => r.version));
+ const pending = computePending(applied, migrations);
+
+ for (const m of pending) {
+ const runInTransaction = db.transaction(() => {
+ db.exec(m.up);
+ recordMigrationStmt.run(namespace, m.version, m.name);
+ });
+ runInTransaction();
+ }
+ }
+
+ function close(): void {
+ db.close();
+ }
+
+ return { storage, migrate, close };
+}