132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
// src/core/io/KGStorage.ts
|
|
|
|
import { openDB } from 'idb'
|
|
import type { IDBPDatabase } from 'idb'
|
|
import { plainToInstance, instanceToPlain } from 'class-transformer'
|
|
import { DB_CONSTANTS } from '../../constants/coreConstants'
|
|
|
|
export interface StorageEntry {
|
|
name: string
|
|
data: Record<string, unknown>
|
|
lastModified: number
|
|
}
|
|
|
|
export class DuplicateEntryError extends Error {
|
|
constructor(name: string) {
|
|
super(`Entry "${name}" already exists`)
|
|
this.name = 'DuplicateEntryError'
|
|
}
|
|
}
|
|
|
|
export class KGStorage {
|
|
private static instance: KGStorage
|
|
private dbPromises: Map<string, Promise<IDBPDatabase>>
|
|
|
|
private constructor() {
|
|
this.dbPromises = new Map()
|
|
}
|
|
|
|
public static getInstance(): KGStorage {
|
|
if (!KGStorage.instance) {
|
|
KGStorage.instance = new KGStorage()
|
|
}
|
|
return KGStorage.instance
|
|
}
|
|
|
|
private getDB(dbName: string, _storeName: string, version: number = 1): Promise<IDBPDatabase> {
|
|
const key = `${dbName}_${version}`
|
|
|
|
if (!this.dbPromises.has(key)) {
|
|
const dbPromise = openDB(dbName, version, {
|
|
upgrade(db) {
|
|
// Create all required object stores for this database
|
|
const requiredStores = [
|
|
DB_CONSTANTS.PROJECTS_STORE_NAME,
|
|
DB_CONSTANTS.CONFIG_STORE_NAME
|
|
];
|
|
|
|
for (const store of requiredStores) {
|
|
if (!db.objectStoreNames.contains(store)) {
|
|
db.createObjectStore(store, { keyPath: 'name' })
|
|
console.log(`Created object store: ${store}`)
|
|
}
|
|
}
|
|
},
|
|
})
|
|
this.dbPromises.set(key, dbPromise)
|
|
}
|
|
|
|
return this.dbPromises.get(key)!
|
|
}
|
|
|
|
public async save<T>(
|
|
dbName: string,
|
|
storeName: string,
|
|
name: string,
|
|
data: T,
|
|
overwrite: boolean = false,
|
|
version: number = 1
|
|
): Promise<void> {
|
|
const db = await this.getDB(dbName, storeName, version)
|
|
const existing = await db.get(storeName, name)
|
|
if (existing && !overwrite) {
|
|
throw new DuplicateEntryError(name)
|
|
}
|
|
const entry: StorageEntry = {
|
|
name: name,
|
|
data: instanceToPlain(data) as Record<string, unknown>,
|
|
lastModified: Date.now(),
|
|
}
|
|
await db.put(storeName, entry)
|
|
}
|
|
|
|
public async load<T>(
|
|
dbName: string,
|
|
storeName: string,
|
|
name: string,
|
|
classType: new() => T,
|
|
version: number = 1
|
|
): Promise<T | null> {
|
|
try {
|
|
const db = await this.getDB(dbName, storeName, version)
|
|
const entry = await db.get(storeName, name)
|
|
|
|
if (!entry?.data) {
|
|
console.log(`No data found for entry "${name}" in store "${storeName}" of database "${dbName}"`)
|
|
return null
|
|
}
|
|
|
|
const instance = plainToInstance(classType, entry.data)
|
|
const loadedInstance = Array.isArray(instance) ? instance[0] || null : instance
|
|
|
|
if (loadedInstance && typeof (loadedInstance as { setName?: (projectName: string) => void }).setName === 'function') {
|
|
(loadedInstance as { setName: (projectName: string) => void }).setName(name)
|
|
}
|
|
|
|
return loadedInstance
|
|
} catch (error) {
|
|
console.log(`Error loading entry "${name}" from store "${storeName}" of database "${dbName}":`, error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
public async list(
|
|
dbName: string,
|
|
storeName: string,
|
|
version: number = 1
|
|
): Promise<string[]> {
|
|
const db = await this.getDB(dbName, storeName, version)
|
|
const all = await db.getAllKeys(storeName)
|
|
return all as string[]
|
|
}
|
|
|
|
public async delete(
|
|
dbName: string,
|
|
storeName: string,
|
|
name: string,
|
|
version: number = 1
|
|
): Promise<void> {
|
|
const db = await this.getDB(dbName, storeName, version)
|
|
await db.delete(storeName, name)
|
|
}
|
|
} |