This repository has been archived on 2025-08-03. You can view files and clone it, but cannot push or open issues or pull requests.
network-orbit/src/db/stores/baseStore.ts

157 lines
4.3 KiB
TypeScript

import { createServiceLogger } from '../../utils/logger';
import { openDB } from '../../orbit/orbitDBService';
import { validateDocument } from '../schema/validator';
import {
ErrorCode,
StoreType,
StoreOptions,
CreateResult,
UpdateResult,
PaginatedResult,
QueryOptions,
ListOptions,
} from '../types';
import { DBError } from '../core/error';
const logger = createServiceLogger('DB_STORE');
/**
* Base Store interface that all store implementations should extend
*/
export interface BaseStore {
create<T extends Record<string, any>>(
collection: string,
id: string,
data: Omit<T, 'createdAt' | 'updatedAt'>,
options?: StoreOptions,
): Promise<CreateResult>;
get<T extends Record<string, any>>(
collection: string,
id: string,
options?: StoreOptions & { skipCache?: boolean },
): Promise<T | null>;
update<T extends Record<string, any>>(
collection: string,
id: string,
data: Partial<Omit<T, 'createdAt' | 'updatedAt'>>,
options?: StoreOptions & { upsert?: boolean },
): Promise<UpdateResult>;
remove(collection: string, id: string, options?: StoreOptions): Promise<boolean>;
list<T extends Record<string, any>>(
collection: string,
options?: ListOptions,
): Promise<PaginatedResult<T>>;
query<T extends Record<string, any>>(
collection: string,
filter: (doc: T) => boolean,
options?: QueryOptions,
): Promise<PaginatedResult<T>>;
createIndex(collection: string, field: string, options?: StoreOptions): Promise<boolean>;
}
/**
* Open a store of the specified type
*/
export async function openStore(
collection: string,
storeType: StoreType,
options?: StoreOptions,
): Promise<any> {
try {
// Log minimal connection info to avoid leaking sensitive data
logger.info(
`Opening ${storeType} store for collection: ${collection} (connection ID: ${options?.connectionId || 'default'})`,
);
return await openDB(collection, storeType).catch((err) => {
throw new Error(`OrbitDB openDB failed: ${err.message}`);
});
} catch (error) {
logger.error(`Error opening ${storeType} store for collection ${collection}:`, error);
// Add more context to the error for improved debugging
const errorMessage = error instanceof Error ? error.message : String(error);
throw new DBError(
ErrorCode.OPERATION_FAILED,
`Failed to open ${storeType} store for collection ${collection}: ${errorMessage}`,
error,
);
}
}
/**
* Recursively sanitize an object by removing undefined values
* This is necessary because IPLD doesn't support undefined values
*/
function deepSanitizeUndefined(obj: any): any {
if (obj === null || obj === undefined) {
return null;
}
if (Array.isArray(obj)) {
return obj.map(deepSanitizeUndefined).filter((item) => item !== undefined);
}
if (typeof obj === 'object' && obj.constructor === Object) {
const sanitized: any = {};
for (const [key, value] of Object.entries(obj)) {
const sanitizedValue = deepSanitizeUndefined(value);
// Only include the property if it's not undefined
if (sanitizedValue !== undefined) {
sanitized[key] = sanitizedValue;
}
}
return sanitized;
}
return obj;
}
/**
* Helper function to prepare a document for storage
*/
export function prepareDocument<T extends Record<string, any>>(
collection: string,
data: Omit<T, 'createdAt' | 'updatedAt'>,
existingDoc?: T | null,
): T {
const timestamp = Date.now();
// Deep sanitize the input data by removing undefined values
const sanitizedData = deepSanitizeUndefined(data) as Omit<T, 'createdAt' | 'updatedAt'>;
// Prepare document for validation
let docToValidate: T;
// If it's an update to an existing document
if (existingDoc) {
docToValidate = {
...existingDoc,
...sanitizedData,
updatedAt: timestamp,
} as T;
} else {
// Otherwise it's a new document
docToValidate = {
...sanitizedData,
createdAt: timestamp,
updatedAt: timestamp,
} as unknown as T;
}
// Deep sanitize the final document to ensure no undefined values remain
const finalDocument = deepSanitizeUndefined(docToValidate) as T;
// Validate the document BEFORE processing
validateDocument(collection, finalDocument);
// Return the validated document
return finalDocument;
}