mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 21:54:56 +00:00
Merge pull request #28 from johndoe6345789/codex/extract-adapter-classes-to-separate-files
refactor(unified-storage): extract adapters into separate files
This commit is contained in:
77
src/lib/unified-storage-adapters/flask-backend-adapter.ts
Normal file
77
src/lib/unified-storage-adapters/flask-backend-adapter.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { StorageAdapter } from './types'
|
||||
|
||||
export class FlaskBackendAdapter implements StorageAdapter {
|
||||
private baseUrl: string
|
||||
private readonly TIMEOUT_MS = 2000
|
||||
|
||||
constructor(baseUrl?: string) {
|
||||
this.baseUrl = baseUrl || localStorage.getItem('codeforge-flask-url') || import.meta.env.VITE_FLASK_BACKEND_URL || 'http://localhost:5001'
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT_MS)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: response.statusText }))
|
||||
throw new Error(error.error || `HTTP ${response.status}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
} catch (error: any) {
|
||||
clearTimeout(timeoutId)
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error(`Request timeout after ${this.TIMEOUT_MS}ms`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
try {
|
||||
const result = await this.request<{ value: T }>(`/api/storage/${encodeURIComponent(key)}`)
|
||||
return result.value
|
||||
} catch (error: any) {
|
||||
if (error.message?.includes('404') || error.message?.includes('not found')) {
|
||||
return undefined
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
await this.request(`/api/storage/${encodeURIComponent(key)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ value }),
|
||||
})
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.request(`/api/storage/${encodeURIComponent(key)}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
const result = await this.request<{ keys: string[] }>('/api/storage/keys')
|
||||
return result.keys
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.request('/api/storage/clear', {
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/lib/unified-storage-adapters/index.ts
Normal file
5
src/lib/unified-storage-adapters/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { FlaskBackendAdapter } from './flask-backend-adapter'
|
||||
export { IndexedDBAdapter } from './indexeddb-adapter'
|
||||
export { SparkKVAdapter } from './spark-kv-adapter'
|
||||
export { SQLiteAdapter } from './sqlite-adapter'
|
||||
export type { StorageAdapter, StorageBackend } from './types'
|
||||
110
src/lib/unified-storage-adapters/indexeddb-adapter.ts
Normal file
110
src/lib/unified-storage-adapters/indexeddb-adapter.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { StorageAdapter } from './types'
|
||||
|
||||
export class IndexedDBAdapter implements StorageAdapter {
|
||||
private db: IDBDatabase | null = null
|
||||
private readonly dbName = 'CodeForgeDB'
|
||||
private readonly storeName = 'keyvalue'
|
||||
private readonly version = 2
|
||||
|
||||
private async init(): Promise<void> {
|
||||
if (this.db) return
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, this.version)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result
|
||||
resolve()
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result
|
||||
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
db.createObjectStore(this.storeName, { keyPath: 'key' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readonly')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.get(key)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => {
|
||||
const result = request.result
|
||||
resolve(result ? result.value : undefined)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readwrite')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.put({ key, value })
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readwrite')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.delete(key)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readonly')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.getAllKeys()
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve(request.result as string[])
|
||||
})
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readwrite')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.clear()
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.db) {
|
||||
this.db.close()
|
||||
this.db = null
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/lib/unified-storage-adapters/spark-kv-adapter.ts
Normal file
29
src/lib/unified-storage-adapters/spark-kv-adapter.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { StorageAdapter } from './types'
|
||||
|
||||
export class SparkKVAdapter implements StorageAdapter {
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
return await window.spark.kv.get<T>(key)
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
await window.spark.kv.set(key, value)
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
await window.spark.kv.delete(key)
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
return await window.spark.kv.keys()
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
const allKeys = await window.spark.kv.keys()
|
||||
await Promise.all(allKeys.map(key => window.spark.kv.delete(key)))
|
||||
}
|
||||
}
|
||||
123
src/lib/unified-storage-adapters/sqlite-adapter.ts
Normal file
123
src/lib/unified-storage-adapters/sqlite-adapter.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { StorageAdapter } from './types'
|
||||
|
||||
export class SQLiteAdapter implements StorageAdapter {
|
||||
private db: any = null
|
||||
private SQL: any = null
|
||||
private initPromise: Promise<void> | null = null
|
||||
|
||||
private async loadSQLiteWASM(): Promise<any> {
|
||||
const moduleName = 'sql.js'
|
||||
try {
|
||||
return await import(/* @vite-ignore */ moduleName)
|
||||
} catch {
|
||||
throw new Error(`${moduleName} not installed. Run: npm install ${moduleName}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
if (this.db) return
|
||||
if (this.initPromise) return this.initPromise
|
||||
|
||||
this.initPromise = (async () => {
|
||||
try {
|
||||
const sqlJsModule = await this.loadSQLiteWASM()
|
||||
const initSqlJs = sqlJsModule.default
|
||||
|
||||
this.SQL = await initSqlJs({
|
||||
locateFile: (file: string) => `https://sql.js.org/dist/${file}`
|
||||
})
|
||||
|
||||
const data = localStorage.getItem('codeforge-sqlite-db')
|
||||
if (data) {
|
||||
const buffer = new Uint8Array(JSON.parse(data))
|
||||
this.db = new this.SQL.Database(buffer)
|
||||
} else {
|
||||
this.db = new this.SQL.Database()
|
||||
}
|
||||
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS keyvalue (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
} catch (error) {
|
||||
console.error('SQLite initialization failed:', error)
|
||||
throw error
|
||||
}
|
||||
})()
|
||||
|
||||
return this.initPromise
|
||||
}
|
||||
|
||||
private persist(): void {
|
||||
if (!this.db) return
|
||||
try {
|
||||
const data = this.db.export()
|
||||
const buffer = Array.from(data)
|
||||
localStorage.setItem('codeforge-sqlite-db', JSON.stringify(buffer))
|
||||
} catch (error) {
|
||||
console.error('Failed to persist SQLite database:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
await this.init()
|
||||
const stmt = this.db.prepare('SELECT value FROM keyvalue WHERE key = ?')
|
||||
stmt.bind([key])
|
||||
|
||||
if (stmt.step()) {
|
||||
const row = stmt.getAsObject()
|
||||
stmt.free()
|
||||
return JSON.parse(row.value as string) as T
|
||||
}
|
||||
|
||||
stmt.free()
|
||||
return undefined
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
await this.init()
|
||||
this.db.run(
|
||||
'INSERT OR REPLACE INTO keyvalue (key, value) VALUES (?, ?)',
|
||||
[key, JSON.stringify(value)]
|
||||
)
|
||||
this.persist()
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.init()
|
||||
this.db.run('DELETE FROM keyvalue WHERE key = ?', [key])
|
||||
this.persist()
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
await this.init()
|
||||
const stmt = this.db.prepare('SELECT key FROM keyvalue')
|
||||
const keys: string[] = []
|
||||
|
||||
while (stmt.step()) {
|
||||
const row = stmt.getAsObject()
|
||||
keys.push(row.key as string)
|
||||
}
|
||||
|
||||
stmt.free()
|
||||
return keys
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.init()
|
||||
this.db.run('DELETE FROM keyvalue')
|
||||
this.persist()
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.db) {
|
||||
this.persist()
|
||||
this.db.close()
|
||||
this.db = null
|
||||
this.SQL = null
|
||||
this.initPromise = null
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/lib/unified-storage-adapters/types.ts
Normal file
10
src/lib/unified-storage-adapters/types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type StorageBackend = 'flask' | 'indexeddb' | 'sqlite' | 'sparkkv'
|
||||
|
||||
export interface StorageAdapter {
|
||||
get<T>(key: string): Promise<T | undefined>
|
||||
set<T>(key: string, value: T): Promise<void>
|
||||
delete(key: string): Promise<void>
|
||||
keys(): Promise<string[]>
|
||||
clear(): Promise<void>
|
||||
close?(): Promise<void>
|
||||
}
|
||||
@@ -1,350 +1,9 @@
|
||||
/// <reference path="../global.d.ts" />
|
||||
|
||||
export type StorageBackend = 'flask' | 'indexeddb' | 'sqlite' | 'sparkkv'
|
||||
import type { StorageAdapter, StorageBackend } from './unified-storage-adapters'
|
||||
import { FlaskBackendAdapter, IndexedDBAdapter, SparkKVAdapter, SQLiteAdapter } from './unified-storage-adapters'
|
||||
|
||||
export interface StorageAdapter {
|
||||
get<T>(key: string): Promise<T | undefined>
|
||||
set<T>(key: string, value: T): Promise<void>
|
||||
delete(key: string): Promise<void>
|
||||
keys(): Promise<string[]>
|
||||
clear(): Promise<void>
|
||||
close?(): Promise<void>
|
||||
}
|
||||
|
||||
class FlaskBackendAdapter implements StorageAdapter {
|
||||
private baseUrl: string
|
||||
private readonly TIMEOUT_MS = 2000
|
||||
|
||||
constructor(baseUrl?: string) {
|
||||
this.baseUrl = baseUrl || localStorage.getItem('codeforge-flask-url') || import.meta.env.VITE_FLASK_BACKEND_URL || 'http://localhost:5001'
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT_MS)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: response.statusText }))
|
||||
throw new Error(error.error || `HTTP ${response.status}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
} catch (error: any) {
|
||||
clearTimeout(timeoutId)
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error(`Request timeout after ${this.TIMEOUT_MS}ms`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
try {
|
||||
const result = await this.request<{ value: T }>(`/api/storage/${encodeURIComponent(key)}`)
|
||||
return result.value
|
||||
} catch (error: any) {
|
||||
if (error.message?.includes('404') || error.message?.includes('not found')) {
|
||||
return undefined
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
await this.request(`/api/storage/${encodeURIComponent(key)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ value }),
|
||||
})
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.request(`/api/storage/${encodeURIComponent(key)}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
const result = await this.request<{ keys: string[] }>('/api/storage/keys')
|
||||
return result.keys
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.request('/api/storage/clear', {
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class IndexedDBAdapter implements StorageAdapter {
|
||||
private db: IDBDatabase | null = null
|
||||
private readonly dbName = 'CodeForgeDB'
|
||||
private readonly storeName = 'keyvalue'
|
||||
private readonly version = 2
|
||||
|
||||
private async init(): Promise<void> {
|
||||
if (this.db) return
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, this.version)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result
|
||||
resolve()
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result
|
||||
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
db.createObjectStore(this.storeName, { keyPath: 'key' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readonly')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.get(key)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => {
|
||||
const result = request.result
|
||||
resolve(result ? result.value : undefined)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readwrite')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.put({ key, value })
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readwrite')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.delete(key)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readonly')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.getAllKeys()
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve(request.result as string[])
|
||||
})
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.init()
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction(this.storeName, 'readwrite')
|
||||
const store = transaction.objectStore(this.storeName)
|
||||
const request = store.clear()
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.db) {
|
||||
this.db.close()
|
||||
this.db = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SparkKVAdapter implements StorageAdapter {
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
return await window.spark.kv.get<T>(key)
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
await window.spark.kv.set(key, value)
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
await window.spark.kv.delete(key)
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
return await window.spark.kv.keys()
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
if (!window.spark?.kv) throw new Error('Spark KV not available')
|
||||
const allKeys = await window.spark.kv.keys()
|
||||
await Promise.all(allKeys.map(key => window.spark.kv.delete(key)))
|
||||
}
|
||||
}
|
||||
|
||||
class SQLiteAdapter implements StorageAdapter {
|
||||
private db: any = null
|
||||
private SQL: any = null
|
||||
private initPromise: Promise<void> | null = null
|
||||
|
||||
private async loadSQLiteWASM(): Promise<any> {
|
||||
const moduleName = 'sql.js'
|
||||
try {
|
||||
return await import(/* @vite-ignore */ moduleName)
|
||||
} catch {
|
||||
throw new Error(`${moduleName} not installed. Run: npm install ${moduleName}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
if (this.db) return
|
||||
if (this.initPromise) return this.initPromise
|
||||
|
||||
this.initPromise = (async () => {
|
||||
try {
|
||||
const sqlJsModule = await this.loadSQLiteWASM()
|
||||
const initSqlJs = sqlJsModule.default
|
||||
|
||||
this.SQL = await initSqlJs({
|
||||
locateFile: (file: string) => `https://sql.js.org/dist/${file}`
|
||||
})
|
||||
|
||||
const data = localStorage.getItem('codeforge-sqlite-db')
|
||||
if (data) {
|
||||
const buffer = new Uint8Array(JSON.parse(data))
|
||||
this.db = new this.SQL.Database(buffer)
|
||||
} else {
|
||||
this.db = new this.SQL.Database()
|
||||
}
|
||||
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS keyvalue (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
} catch (error) {
|
||||
console.error('SQLite initialization failed:', error)
|
||||
throw error
|
||||
}
|
||||
})()
|
||||
|
||||
return this.initPromise
|
||||
}
|
||||
|
||||
private persist(): void {
|
||||
if (!this.db) return
|
||||
try {
|
||||
const data = this.db.export()
|
||||
const buffer = Array.from(data)
|
||||
localStorage.setItem('codeforge-sqlite-db', JSON.stringify(buffer))
|
||||
} catch (error) {
|
||||
console.error('Failed to persist SQLite database:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
await this.init()
|
||||
const stmt = this.db.prepare('SELECT value FROM keyvalue WHERE key = ?')
|
||||
stmt.bind([key])
|
||||
|
||||
if (stmt.step()) {
|
||||
const row = stmt.getAsObject()
|
||||
stmt.free()
|
||||
return JSON.parse(row.value as string) as T
|
||||
}
|
||||
|
||||
stmt.free()
|
||||
return undefined
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T): Promise<void> {
|
||||
await this.init()
|
||||
this.db.run(
|
||||
'INSERT OR REPLACE INTO keyvalue (key, value) VALUES (?, ?)',
|
||||
[key, JSON.stringify(value)]
|
||||
)
|
||||
this.persist()
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await this.init()
|
||||
this.db.run('DELETE FROM keyvalue WHERE key = ?', [key])
|
||||
this.persist()
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
await this.init()
|
||||
const stmt = this.db.prepare('SELECT key FROM keyvalue')
|
||||
const keys: string[] = []
|
||||
|
||||
while (stmt.step()) {
|
||||
const row = stmt.getAsObject()
|
||||
keys.push(row.key as string)
|
||||
}
|
||||
|
||||
stmt.free()
|
||||
return keys
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.init()
|
||||
this.db.run('DELETE FROM keyvalue')
|
||||
this.persist()
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.db) {
|
||||
this.persist()
|
||||
this.db.close()
|
||||
this.db = null
|
||||
this.SQL = null
|
||||
this.initPromise = null
|
||||
}
|
||||
}
|
||||
}
|
||||
export type { StorageAdapter, StorageBackend } from './unified-storage-adapters'
|
||||
|
||||
class UnifiedStorage {
|
||||
private adapter: StorageAdapter | null = null
|
||||
|
||||
Reference in New Issue
Block a user