open, interoperable sandbox platform for agents and humans 📦 ✨ pocketenv.io
claude-code atproto sandbox openclaw agent
at main 173 lines 4.2 kB view raw
1import Database from "better-sqlite3"; 2import { Kysely, SqliteDialect } from "kysely"; 3import { defineDriver } from "unstorage"; 4 5interface TableSchema { 6 [k: string]: { 7 id: string; 8 value: string; 9 created_at: string; 10 updated_at: string; 11 }; 12} 13 14export type KvDb = Kysely<TableSchema>; 15 16const DRIVER_NAME = "sqlite"; 17 18export default defineDriver< 19 { 20 location?: string; 21 table: string; 22 getDb?: () => KvDb; 23 }, 24 KvDb 25>( 26 ({ 27 location, 28 table = "kv", 29 getDb = (): KvDb => { 30 let _db: KvDb | null = null; 31 32 return (() => { 33 if (_db) { 34 return _db; 35 } 36 37 if (!location) { 38 throw new Error("SQLite location is required"); 39 } 40 41 const sqlite = new Database(location, { fileMustExist: false }); 42 43 // Enable WAL mode 44 sqlite.pragma("journal_mode = WAL"); 45 46 _db = new Kysely<TableSchema>({ 47 dialect: new SqliteDialect({ 48 database: sqlite, 49 }), 50 }); 51 52 // Create table if not exists 53 _db.schema 54 .createTable(table) 55 .ifNotExists() 56 .addColumn("id", "text", (col) => col.primaryKey()) 57 .addColumn("value", "text", (col) => col.notNull()) 58 .addColumn("created_at", "text", (col) => col.notNull()) 59 .addColumn("updated_at", "text", (col) => col.notNull()) 60 .execute(); 61 62 return _db; 63 })(); 64 }, 65 }) => { 66 return { 67 name: DRIVER_NAME, 68 options: { location, table }, 69 getInstance: getDb, 70 71 async hasItem(key) { 72 const result = await getDb() 73 .selectFrom(table) 74 .select(["id"]) 75 .where("id", "=", key) 76 .executeTakeFirst(); 77 return !!result; 78 }, 79 80 async getItem(key) { 81 const result = await getDb() 82 .selectFrom(table) 83 .select(["value"]) 84 .where("id", "=", key) 85 .executeTakeFirst(); 86 return result?.value ?? null; 87 }, 88 89 async setItem(key: string, value: string) { 90 const now = new Date().toISOString(); 91 await getDb() 92 .insertInto(table) 93 .values({ 94 id: key, 95 value, 96 created_at: now, 97 updated_at: now, 98 }) 99 .onConflict((oc) => 100 oc.column("id").doUpdateSet({ 101 value, 102 updated_at: now, 103 }), 104 ) 105 .execute(); 106 }, 107 108 async setItems(items) { 109 const now = new Date().toISOString(); 110 111 await getDb() 112 .transaction() 113 .execute(async (trx) => { 114 await Promise.all( 115 items.map(({ key, value }) => { 116 return trx 117 .insertInto(table) 118 .values({ 119 id: key, 120 value, 121 created_at: now, 122 updated_at: now, 123 }) 124 .onConflict((oc) => 125 oc.column("id").doUpdateSet({ 126 value, 127 updated_at: now, 128 }), 129 ) 130 .execute(); 131 }), 132 ); 133 }); 134 }, 135 136 async removeItem(key: string) { 137 await getDb().deleteFrom(table).where("id", "=", key).execute(); 138 }, 139 140 async getMeta(key: string) { 141 const result = await getDb() 142 .selectFrom(table) 143 .select(["created_at", "updated_at"]) 144 .where("id", "=", key) 145 .executeTakeFirst(); 146 if (!result) { 147 return null; 148 } 149 return { 150 birthtime: new Date(result.created_at), 151 mtime: new Date(result.updated_at), 152 }; 153 }, 154 155 async getKeys(base = "") { 156 const results = await getDb() 157 .selectFrom(table) 158 .select(["id"]) 159 .where("id", "like", `${base}%`) 160 .execute(); 161 return results.map((r) => r.id); 162 }, 163 164 async clear() { 165 await getDb().deleteFrom(table).execute(); 166 }, 167 168 async dispose() { 169 await getDb().destroy(); 170 }, 171 }; 172 }, 173);