UNPKG

@worker-tools/deno-kv-storage

Version:

An implementation of the StorageArea (1,2,3) interface for Deno with an extensible system for supporting various database backends.

113 lines (95 loc) 2.95 kB
// deno-lint-ignore-file require-await import "../_dnt.polyfills.js"; import { DB } from "../deps/deno.land/x/sqlite@v2.5.0/mod.js"; import type { Adapter, AdapterParams } from './mod.js'; const CREATE = 'CREATE TABLE IF NOT EXISTS [kv-storage] (area TEXT, key TEXT, value TEXT, PRIMARY KEY (area, key))'; const GET = 'SELECT value FROM [kv-storage] WHERE key=:key AND area=:area'; const UPSERT = 'INSERT INTO [kv-storage] (area, key, value) VALUES (:area, :key, :value) ON CONFLICT(area, key) DO UPDATE SET value=:value'; const DELETE = 'DELETE FROM [kv-storage] WHERE key=:key AND area=:area'; const CLEAR = 'DELETE FROM [kv-storage] WHERE area=:area'; const KEYS = 'SELECT key FROM [kv-storage] WHERE area=:area'; const VALUES = 'SELECT value FROM [kv-storage] WHERE area=:area'; const ENTRIES = 'SELECT key, value FROM [kv-storage] WHERE area=:area'; export class SQLiteAdapter implements Adapter { private filename: string; private area: string; private db?: DB; private memory: boolean; private refs = 0 constructor({ area, url }: AdapterParams) { this.area = area; const filename = url.substring('sqlite://'.length); const memory = this.memory = ['', 'memory'].includes(filename ?? ''); const db = this.db = new DB(this.filename = memory ? ':memory:' : filename); [...db.query(CREATE)]; this.keepOpen(); } private keepOpen() { if (!this.memory) queueMicrotask(() => { if (this.refs === 0) { this.db?.close() delete this.db; } }) } private query(query: string, params?: { key?: string, value?: string }) { const db = this.db ||= new DB(this.filename); const rows = db.query(query, { ...params, area: this.area }); return rows; } async get(key: string): Promise<string | undefined> { const res = this.query(GET, { key }).next().value?.[0]; this.keepOpen() return res; } async set(key: string, value: string) { [...this.query(UPSERT, { key, value })]; this.keepOpen() } async delete(key: string) { [...this.query(DELETE, { key })]; this.keepOpen() } async clear() { [...this.query(CLEAR)]; this.keepOpen() } async *keys() { try { this.refs++ for (const [key] of this.query(KEYS)) { yield key; } } finally { this.refs--; this.keepOpen() } } async *values() { try { this.refs++ for (const [value] of this.query(VALUES)) { yield value; } } finally { this.refs-- this.keepOpen() } } async *entries() { try { this.refs++ for (const [key, value] of this.query(ENTRIES)) { yield [key, value] as const; } } finally { this.refs--; this.keepOpen() } } backingStore() { return this.db ?? new DB(this.filename); } } // @ts-ignore: ... (globalThis.deno_storage_area__adapters ||= new Map()).set('sqlite:', SQLiteAdapter);