Import source: src/kv
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
export interface KVNamespace {
|
||||
get(key: string): Promise<string | null>;
|
||||
get(key: string, type: 'text'): Promise<string | null>;
|
||||
get<T = unknown>(key: string, type: 'json'): Promise<T | null>;
|
||||
put(key: string, value: string, options?: unknown): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
list(options?: { prefix?: string; cursor?: string; limit?: number }): Promise<{
|
||||
keys: Array<{ name: string }>;
|
||||
list_complete: boolean;
|
||||
cursor?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class SqliteKV implements KVNamespace {
|
||||
private db: InstanceType<typeof Database>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private getStmt: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private putStmt: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private deleteStmt: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private listStmt: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private cleanupStmt: any;
|
||||
|
||||
constructor(path: string) {
|
||||
this.db = new Database(path);
|
||||
this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS kv_store (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
expires_at INTEGER
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_kv_prefix ON kv_store(key);
|
||||
`);
|
||||
this.getStmt = this.db.prepare('SELECT value, expires_at FROM kv_store WHERE key = ?');
|
||||
this.putStmt = this.db.prepare('INSERT OR REPLACE INTO kv_store (key, value, expires_at) VALUES (?, ?, ?)');
|
||||
this.deleteStmt = this.db.prepare('DELETE FROM kv_store WHERE key = ?');
|
||||
this.listStmt = this.db.prepare('SELECT key FROM kv_store WHERE key LIKE ? ESCAPE \'\\\' AND (expires_at IS NULL OR expires_at >= ?) ORDER BY key LIMIT ? OFFSET ?');
|
||||
this.cleanupStmt = this.db.prepare('DELETE FROM kv_store WHERE expires_at IS NOT NULL AND expires_at < ?');
|
||||
setInterval(() => this.cleanupStmt.run(Date.now()), 60_000);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async get(key: string, type?: 'text' | 'json'): Promise<any> {
|
||||
const row = this.getStmt.get(key) as { value: string; expires_at: number | null } | undefined;
|
||||
if (!row) return null;
|
||||
if (row.expires_at !== null && row.expires_at < Date.now()) {
|
||||
this.deleteStmt.run(key);
|
||||
return null;
|
||||
}
|
||||
if (type === 'json') {
|
||||
try { return JSON.parse(row.value); } catch { return null; }
|
||||
}
|
||||
return row.value;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async put(key: string, value: string, options?: any): Promise<void> {
|
||||
let expires_at: number | null = null;
|
||||
if (options?.expirationTtl) expires_at = Date.now() + options.expirationTtl * 1000;
|
||||
else if (options?.expiration) expires_at = options.expiration * 1000;
|
||||
this.putStmt.run(key, value, expires_at);
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
this.deleteStmt.run(key);
|
||||
}
|
||||
|
||||
async list(options?: { prefix?: string; cursor?: string; limit?: number }): Promise<{ keys: Array<{ name: string }>; list_complete: boolean; cursor?: string }> {
|
||||
const prefix = (options?.prefix ?? '').replace(/[%_\\]/g, '\\$&');
|
||||
const limit = Math.min(options?.limit ?? 1000, 1000);
|
||||
const offset = options?.cursor ? parseInt(Buffer.from(options.cursor, 'base64url').toString(), 10) : 0;
|
||||
const rows = this.listStmt.all(`${prefix}%`, Date.now(), limit + 1, offset) as { key: string }[];
|
||||
const hasMore = rows.length > limit;
|
||||
const keys = rows.slice(0, limit).map(r => ({ name: r.key }));
|
||||
return {
|
||||
keys,
|
||||
list_complete: !hasMore,
|
||||
cursor: hasMore ? Buffer.from(String(offset + keys.length)).toString('base64url') : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user