Refactor offline-first architecture with IndexedDB

Improves dependency injection and service initialization for IndexedDB-based calendar application

Implements lazy initialization for IndexedDB
Fixes race conditions in async event handling
Adds proper dependency injection with registerType
Enhances sync manager and repository pattern

Key improvements:
- Lazy database initialization
- Proper service lifecycle management
- Improved network awareness for sync operations
- Cleaned up initialization logic in index.ts
This commit is contained in:
Janus C. H. Knudsen 2025-11-05 20:35:21 +01:00
parent e7011526e3
commit a1bee99d8e
6 changed files with 226 additions and 34 deletions

View file

@ -80,22 +80,6 @@ async function initializeCalendar(): Promise<void> {
// Load configuration from JSON
const config = await ConfigManager.load();
// ========================================
// Initialize IndexedDB and seed if needed
// ========================================
const indexedDB = new IndexedDBService();
await indexedDB.initialize();
await indexedDB.seedIfEmpty();
// Create operation queue
const queue = new OperationQueue(indexedDB);
// Create API repository (placeholder for now)
const apiRepository = new ApiEventRepository(config.apiEndpoint || '/api');
// Create IndexedDB repository
const repository = new IndexedDBEventRepository(indexedDB, queue);
// Create NovaDI container
const container = new Container();
const builder = container.builder();
@ -109,13 +93,14 @@ async function initializeCalendar(): Promise<void> {
// Register configuration instance
builder.registerInstance(config).as<Configuration>();
// Register IndexedDB and storage instances
builder.registerInstance(indexedDB).as<IndexedDBService>();
builder.registerInstance(queue).as<OperationQueue>();
builder.registerInstance(apiRepository).as<ApiEventRepository>();
// Register storage and repository services
builder.registerType(IndexedDBService).as<IndexedDBService>();
builder.registerType(OperationQueue).as<OperationQueue>();
builder.registerType(ApiEventRepository).as<ApiEventRepository>();
builder.registerType(IndexedDBEventRepository).as<IEventRepository>();
// Register repository
builder.registerInstance(repository).as<IEventRepository>();
// Register workers
builder.registerType(SyncManager).as<SyncManager>();
// Register renderers
builder.registerType(DateHeaderRenderer).as<IHeaderRenderer>();
@ -171,12 +156,8 @@ async function initializeCalendar(): Promise<void> {
await calendarManager.initialize?.();
await resizeHandleManager.initialize?.();
// ========================================
// Initialize and start SyncManager
// ========================================
const syncManager = new SyncManager(eventBus, queue, indexedDB, apiRepository);
syncManager.startSync();
console.log('SyncManager initialized and started');
// Resolve SyncManager (starts automatically in constructor)
const syncManager = app.resolveType<SyncManager>();
// Handle deep linking after managers are initialized
await handleDeepLinking(eventManager, urlManager);
@ -189,8 +170,6 @@ async function initializeCalendar(): Promise<void> {
calendarManager: typeof calendarManager;
eventManager: typeof eventManager;
syncManager: typeof syncManager;
indexedDB: typeof indexedDB;
queue: typeof queue;
};
}).calendarDebug = {
eventBus,
@ -198,8 +177,6 @@ async function initializeCalendar(): Promise<void> {
calendarManager,
eventManager,
syncManager,
indexedDB,
queue,
};
} catch (error) {

View file

@ -1,4 +1,5 @@
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configurations/CalendarConfig';
/**
* ApiEventRepository
@ -15,8 +16,8 @@ import { ICalendarEvent } from '../types/CalendarTypes';
export class ApiEventRepository {
private apiEndpoint: string;
constructor(apiEndpoint: string) {
this.apiEndpoint = apiEndpoint;
constructor(config: Configuration) {
this.apiEndpoint = config.apiEndpoint || '/api';
}
/**

View file

@ -23,8 +23,15 @@ export class IndexedDBEventRepository implements IEventRepository {
/**
* Load all events from IndexedDB
* Ensures IndexedDB is initialized and seeded on first call
*/
async loadEvents(): Promise<ICalendarEvent[]> {
// Lazy initialization on first data load
if (!this.indexedDB.isInitialized()) {
await this.indexedDB.initialize();
await this.indexedDB.seedIfEmpty();
}
return await this.indexedDB.getAllEvents();
}

View file

@ -24,6 +24,7 @@ export class IndexedDBService {
private static readonly SYNC_STATE_STORE = 'syncState';
private db: IDBDatabase | null = null;
private initialized: boolean = false;
/**
* Initialize and open the database
@ -38,6 +39,7 @@ export class IndexedDBService {
request.onsuccess = () => {
this.db = request.result;
this.initialized = true;
resolve();
};
@ -66,6 +68,13 @@ export class IndexedDBService {
});
}
/**
* Check if database is initialized
*/
public isInitialized(): boolean {
return this.initialized;
}
/**
* Ensure database is initialized
*/

View file

@ -40,6 +40,8 @@ export class SyncManager {
this.apiRepository = apiRepository;
this.setupNetworkListeners();
this.startSync();
console.log('SyncManager initialized and started');
}
/**