import { createServiceLogger } from '../../utils/logger'; import { ErrorCode, StoreType, StoreOptions, CreateResult, UpdateResult, PaginatedResult, QueryOptions, ListOptions, } from '../types'; import { DBError } from '../core/error'; import { BaseStore, openStore } from './baseStore'; import * as events from '../events/eventService'; const logger = createServiceLogger('COUNTER_STORE'); /** * CounterStore implementation * Uses OrbitDB's counter store for simple numeric counters */ export class CounterStore implements BaseStore { /** * Create or set counter value */ async create>( collection: string, id: string, data: Omit, options?: StoreOptions, ): Promise { try { const db = await openStore(collection, StoreType.COUNTER, options); // Extract value from data, default to 0 const value = typeof data === 'object' && data !== null && 'value' in data ? Number(data.value) : 0; // Set the counter value const hash = await db.set(value); // Construct document representation const document = { id, value, createdAt: Date.now(), updatedAt: Date.now(), }; // Emit change event events.emit('document:created', { collection, id, document }); logger.info(`Set counter in ${collection} to ${value}`); return { id, hash }; } catch (error: unknown) { if (error instanceof DBError) { throw error; } logger.error(`Error setting counter in ${collection}:`, error); throw new DBError( ErrorCode.OPERATION_FAILED, `Failed to set counter in ${collection}`, error, ); } } /** * Get counter value */ async get>( collection: string, id: string, options?: StoreOptions & { skipCache?: boolean }, ): Promise { try { // Note: for counters, id is not used in the underlying store (there's only one counter per db) // but we use it for consistency with the API const db = await openStore(collection, StoreType.COUNTER, options); // Get the counter value const value = await db.value(); // Construct document representation const document = { id, value, updatedAt: Date.now(), } as unknown as T; return document; } catch (error: unknown) { if (error instanceof DBError) { throw error; } logger.error(`Error getting counter from ${collection}:`, error); throw new DBError( ErrorCode.OPERATION_FAILED, `Failed to get counter from ${collection}`, error, ); } } /** * Update counter (increment/decrement) */ async update>( collection: string, id: string, data: Partial>, options?: StoreOptions & { upsert?: boolean }, ): Promise { try { const db = await openStore(collection, StoreType.COUNTER, options); // Get current value before update const currentValue = await db.value(); // Extract value from data let value: number; let operation: 'increment' | 'decrement' | 'set' = 'set'; // Check what kind of operation we're doing if (typeof data === 'object' && data !== null) { if ('increment' in data) { value = Number(data.increment); operation = 'increment'; } else if ('decrement' in data) { value = Number(data.decrement); operation = 'decrement'; } else if ('value' in data) { value = Number(data.value); operation = 'set'; } else { value = 0; operation = 'set'; } } else { value = 0; operation = 'set'; } // Update the counter let hash; let newValue; switch (operation) { case 'increment': hash = await db.inc(value); newValue = currentValue + value; break; case 'decrement': hash = await db.inc(-value); // Counter store uses inc with negative value newValue = currentValue - value; break; case 'set': hash = await db.set(value); newValue = value; break; } // Construct document representation const document = { id, value: newValue, updatedAt: Date.now(), }; // Emit change event events.emit('document:updated', { collection, id, document, previous: { id, value: currentValue }, }); logger.info(`Updated counter in ${collection} from ${currentValue} to ${newValue}`); return { id, hash }; } catch (error: unknown) { if (error instanceof DBError) { throw error; } logger.error(`Error updating counter in ${collection}:`, error); throw new DBError( ErrorCode.OPERATION_FAILED, `Failed to update counter in ${collection}`, error, ); } } /** * Delete/reset counter */ async remove(collection: string, id: string, options?: StoreOptions): Promise { try { const db = await openStore(collection, StoreType.COUNTER, options); // Get the current value for the event const currentValue = await db.value(); // Reset the counter to 0 (counters can't be truly deleted) await db.set(0); // Emit change event events.emit('document:deleted', { collection, id, document: { id, value: currentValue }, }); logger.info(`Reset counter in ${collection} from ${currentValue} to 0`); return true; } catch (error: unknown) { if (error instanceof DBError) { throw error; } logger.error(`Error resetting counter in ${collection}:`, error); throw new DBError( ErrorCode.OPERATION_FAILED, `Failed to reset counter in ${collection}`, error, ); } } /** * List all counters (for counter stores, there's only one counter per db) */ async list>( collection: string, options?: ListOptions, ): Promise> { try { const db = await openStore(collection, StoreType.COUNTER, options); const value = await db.value(); // For counter stores, we just return one document with the counter value const document = { id: '0', // Default ID since counters don't have IDs value, updatedAt: Date.now(), } as unknown as T; return { documents: [document], total: 1, hasMore: false, }; } catch (error: unknown) { if (error instanceof DBError) { throw error; } logger.error(`Error listing counter in ${collection}:`, error); throw new DBError( ErrorCode.OPERATION_FAILED, `Failed to list counter in ${collection}`, error, ); } } /** * Query is not applicable for counter stores, but we implement for API consistency */ async query>( collection: string, filter: (doc: T) => boolean, options?: QueryOptions, ): Promise> { try { const db = await openStore(collection, StoreType.COUNTER, options); const value = await db.value(); // Create document const document = { id: '0', // Default ID since counters don't have IDs value, updatedAt: Date.now(), } as unknown as T; // Apply filter const documents = filter(document) ? [document] : []; return { documents, total: documents.length, hasMore: false, }; } catch (error: unknown) { if (error instanceof DBError) { throw error; } logger.error(`Error querying counter in ${collection}:`, error); throw new DBError( ErrorCode.OPERATION_FAILED, `Failed to query counter in ${collection}`, error, ); } } /** * Create an index - not applicable for counter stores */ async createIndex(collection: string, _field: string, _options?: StoreOptions): Promise { logger.warn( `Index creation not supported for counter collections, ignoring request for ${collection}`, ); return false; } }