Refactors repository layer and IndexedDB architecture
Eliminates redundant repository abstraction layer by directly using EntityService methods Implements key improvements: - Removes unnecessary repository wrappers - Introduces polymorphic DataSeeder for mock data loading - Renames IndexedDBService to IndexedDBContext - Fixes database injection timing with lazy access pattern - Simplifies EventManager to use services directly Reduces code complexity and improves separation of concerns
This commit is contained in:
parent
5648c7c304
commit
dcd76836bd
10 changed files with 1260 additions and 574 deletions
128
src/storage/IndexedDBContext.ts
Normal file
128
src/storage/IndexedDBContext.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { IStore } from './IStore';
|
||||
|
||||
/**
|
||||
* IndexedDBContext - Database connection manager and provider
|
||||
*
|
||||
* RESPONSIBILITY:
|
||||
* - Opens and manages IDBDatabase connection lifecycle
|
||||
* - Creates object stores via injected IStore implementations
|
||||
* - Provides shared IDBDatabase instance to all services
|
||||
*
|
||||
* SEPARATION OF CONCERNS:
|
||||
* - This class: Connection management ONLY
|
||||
* - OperationQueue: Queue and sync state operations
|
||||
* - Entity Services: CRUD operations for specific entities
|
||||
*
|
||||
* USAGE:
|
||||
* Services inject IndexedDBContext and call getDatabase() to access db.
|
||||
* This lazy access pattern ensures db is ready when requested.
|
||||
*/
|
||||
export class IndexedDBContext {
|
||||
private static readonly DB_NAME = 'CalendarDB';
|
||||
private static readonly DB_VERSION = 2;
|
||||
static readonly QUEUE_STORE = 'operationQueue';
|
||||
static readonly SYNC_STATE_STORE = 'syncState';
|
||||
|
||||
private db: IDBDatabase | null = null;
|
||||
private initialized: boolean = false;
|
||||
private stores: IStore[];
|
||||
|
||||
/**
|
||||
* @param stores - Array of IStore implementations injected via DI
|
||||
*/
|
||||
constructor(stores: IStore[]) {
|
||||
this.stores = stores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and open the database
|
||||
* Creates all entity stores, queue store, and sync state store
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(IndexedDBContext.DB_NAME, IndexedDBContext.DB_VERSION);
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to open IndexedDB: ${request.error}`));
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result;
|
||||
this.initialized = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
|
||||
// Create all entity stores via injected IStore implementations
|
||||
// Open/Closed Principle: Adding new entity only requires DI registration
|
||||
this.stores.forEach(store => {
|
||||
if (!db.objectStoreNames.contains(store.storeName)) {
|
||||
store.create(db);
|
||||
}
|
||||
});
|
||||
|
||||
// Create operation queue store (sync infrastructure)
|
||||
if (!db.objectStoreNames.contains(IndexedDBContext.QUEUE_STORE)) {
|
||||
const queueStore = db.createObjectStore(IndexedDBContext.QUEUE_STORE, { keyPath: 'id' });
|
||||
queueStore.createIndex('timestamp', 'timestamp', { unique: false });
|
||||
}
|
||||
|
||||
// Create sync state store (sync metadata)
|
||||
if (!db.objectStoreNames.contains(IndexedDBContext.SYNC_STATE_STORE)) {
|
||||
db.createObjectStore(IndexedDBContext.SYNC_STATE_STORE, { keyPath: 'key' });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if database is initialized
|
||||
*/
|
||||
public isInitialized(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IDBDatabase instance
|
||||
* Used by services to access the database
|
||||
*
|
||||
* @throws Error if database not initialized
|
||||
* @returns IDBDatabase instance
|
||||
*/
|
||||
public getDatabase(): IDBDatabase {
|
||||
if (!this.db) {
|
||||
throw new Error('IndexedDB not initialized. Call initialize() first.');
|
||||
}
|
||||
return this.db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
close(): void {
|
||||
if (this.db) {
|
||||
this.db.close();
|
||||
this.db = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entire database (for testing/reset)
|
||||
*/
|
||||
static async deleteDatabase(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(IndexedDBContext.DB_NAME);
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to delete database: ${request.error}`));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue