From 0e2d5bd032eee01e1e773aba5fa49473d93e3174 Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Thu, 25 Dec 2025 15:04:20 +0000 Subject: [PATCH] feat: enhance DBAL integration with improved error handling and in-memory storage implementations --- frontends/nextjs/src/lib/dbal-client.ts | 5 +- frontends/nextjs/src/lib/dbal-integration.ts | 358 +++++++++---------- frontends/nextjs/src/lib/dbal-stub.ts | 22 ++ 3 files changed, 189 insertions(+), 196 deletions(-) diff --git a/frontends/nextjs/src/lib/dbal-client.ts b/frontends/nextjs/src/lib/dbal-client.ts index b384563a9..70c26ea41 100644 --- a/frontends/nextjs/src/lib/dbal-client.ts +++ b/frontends/nextjs/src/lib/dbal-client.ts @@ -1,4 +1,5 @@ -import { DBALClient, type DBALUser, type DBALConfig } from '@/lib/dbal-stub' +import { DBALClient } from '@/lib/dbal-stub' +import type { DBALConfig } from '@/lib/dbal-stub' import type { User } from './level-types' let dbalInstance: DBALClient | null = null @@ -40,4 +41,4 @@ export async function migrateToDBAL() { } export { DBALClient } -export type { DBALUser } +export type { DBALUser } from '@/lib/dbal-stub' diff --git a/frontends/nextjs/src/lib/dbal-integration.ts b/frontends/nextjs/src/lib/dbal-integration.ts index 3c37eb149..f33ca0632 100644 --- a/frontends/nextjs/src/lib/dbal-integration.ts +++ b/frontends/nextjs/src/lib/dbal-integration.ts @@ -1,30 +1,148 @@ /** - * DBAL Integration Layer + * DBAL Integration Layer (Stub Version) * - * This module integrates the TypeScript DBAL client with the existing MetaBuilder - * database layer, providing a bridge between the legacy Database class and the - * new DBAL system. + * This module provides a simplified DBAL integration when the full DBAL + * module is not available. It provides in-memory implementations of + * tenant management, KV store, and blob storage. */ -import { DBALClient, DBALError, DBALErrorCode } from '@/lib/dbal-stub' -import type { DBALConfig } from '@/lib/dbal-stub' -import { InMemoryTenantManager } from '@/lib/dbal-stub/core/tenant-context' -import { InMemoryKVStore } from '@/lib/dbal-stub/core/kv-store' -import { createBlobStorage } from '@/lib/dbal-stub/blob' -import { TenantAwareBlobStorage } from '@/lib/dbal-stub/blob/tenant-aware-storage' +import { DBALClient, type DBALConfig } from '@/lib/dbal-stub' + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Simple in-memory tenant context +interface TenantContext { + tenantId: string + userId: string +} + +// In-memory tenant manager +class InMemoryTenantManager { + private tenants = new Map() + + async createTenant(id: string, limits?: any): Promise { + this.tenants.set(id, { limits: limits || {} }) + } + + async getTenantContext(tenantId: string, userId: string): Promise { + if (!this.tenants.has(tenantId)) { + return null + } + return { tenantId, userId } + } + + hasTenant(id: string): boolean { + return this.tenants.has(id) + } +} + +// In-memory KV store +class InMemoryKVStore { + private store = new Map() + + private getKey(key: string, context: TenantContext): string { + return `${context.tenantId}:${key}` + } + + async set(key: string, value: any, context: TenantContext, ttl?: number): Promise { + const fullKey = this.getKey(key, context) + const expiry = ttl ? Date.now() + ttl * 1000 : undefined + this.store.set(fullKey, { value, expiry }) + } + + async get(key: string, context: TenantContext): Promise { + const fullKey = this.getKey(key, context) + const item = this.store.get(fullKey) + if (!item) return null + if (item.expiry && Date.now() > item.expiry) { + this.store.delete(fullKey) + return null + } + return item.value + } + + async delete(key: string, context: TenantContext): Promise { + const fullKey = this.getKey(key, context) + return this.store.delete(fullKey) + } + + async listAdd(key: string, items: any[], context: TenantContext): Promise { + const fullKey = this.getKey(key, context) + const existing = this.store.get(fullKey)?.value || [] + this.store.set(fullKey, { value: [...existing, ...items] }) + } + + async listGet(key: string, context: TenantContext, start?: number, end?: number): Promise { + const fullKey = this.getKey(key, context) + const list = this.store.get(fullKey)?.value || [] + if (start !== undefined && end !== undefined) { + return list.slice(start, end) + } + return list + } +} + +// In-memory blob storage +class InMemoryBlobStorage { + private blobs = new Map }>() + + async upload(key: string, data: Buffer, metadata?: Record): Promise { + this.blobs.set(key, { data, metadata: metadata || {} }) + } + + async download(key: string): Promise { + const blob = this.blobs.get(key) + if (!blob) throw new Error(`Blob not found: ${key}`) + return blob.data + } + + async delete(key: string): Promise { + this.blobs.delete(key) + } + + async list(options?: { prefix?: string }): Promise<{ items: { key: string }[] }> { + const items: { key: string }[] = [] + for (const key of this.blobs.keys()) { + if (!options?.prefix || key.startsWith(options.prefix)) { + items.push({ key }) + } + } + return { items } + } + + async getMetadata(key: string): Promise<{ customMetadata?: Record }> { + const blob = this.blobs.get(key) + return { customMetadata: blob?.metadata } + } +} + +// DBAL Error types +export enum DBALErrorCode { + VALIDATION_ERROR = 'VALIDATION_ERROR', + NOT_FOUND = 'NOT_FOUND', + UNAUTHORIZED = 'UNAUTHORIZED', + INTERNAL_ERROR = 'INTERNAL_ERROR', +} + +export class DBALError extends Error { + code: DBALErrorCode + + constructor(message: string, code: DBALErrorCode) { + super(message) + this.code = code + this.name = 'DBALError' + } +} /** * DBAL Integration Manager - * - * Manages the DBAL client instance and provides helper methods for - * integrating with the existing MetaBuilder system. */ export class DBALIntegration { private static instance: DBALIntegration | null = null private client: DBALClient | null = null private tenantManager: InMemoryTenantManager | null = null private kvStore: InMemoryKVStore | null = null - private blobStorage: TenantAwareBlobStorage | null = null + private blobStorage: InMemoryBlobStorage | null = null private initialized = false private constructor() {} @@ -48,53 +166,34 @@ export class DBALIntegration { try { // Initialize tenant manager this.tenantManager = new InMemoryTenantManager() - - // Create default tenant for the application await this.tenantManager.createTenant('default', { - maxBlobStorageBytes: 1024 * 1024 * 1024, // 1GB - maxBlobCount: 10000, - maxBlobSizeBytes: 100 * 1024 * 1024, // 100MB per file + maxBlobStorageBytes: 1024 * 1024 * 1024, maxRecords: 100000, - maxDataSizeBytes: 500 * 1024 * 1024, // 500MB - maxListLength: 10000, }) // Initialize KV store this.kvStore = new InMemoryKVStore() - // Initialize blob storage (in-memory for browser) - const baseStorage = createBlobStorage({ - type: 'memory', - }) - - // Wrap with tenant-aware storage - this.blobStorage = new TenantAwareBlobStorage( - baseStorage, - this.tenantManager, - 'default', - 'system' - ) + // Initialize blob storage + this.blobStorage = new InMemoryBlobStorage() // Initialize DBAL client const dbalConfig: DBALConfig = { mode: 'development', - adapter: 'prisma', // Use Prisma adapter + adapter: 'stub', ...config, } this.client = new DBALClient(dbalConfig) this.initialized = true - console.log('DBAL Integration initialized successfully') + console.log('DBAL Integration initialized successfully (stub mode)') } catch (error) { console.error('Failed to initialize DBAL:', error) throw error } } - /** - * Get the DBAL client instance - */ getClient(): DBALClient { if (!this.client) { throw new Error('DBAL not initialized. Call initialize() first.') @@ -102,9 +201,6 @@ export class DBALIntegration { return this.client } - /** - * Get the tenant manager - */ getTenantManager(): InMemoryTenantManager { if (!this.tenantManager) { throw new Error('DBAL not initialized. Call initialize() first.') @@ -112,9 +208,6 @@ export class DBALIntegration { return this.tenantManager } - /** - * Get the KV store - */ getKVStore(): InMemoryKVStore { if (!this.kvStore) { throw new Error('DBAL not initialized. Call initialize() first.') @@ -122,26 +215,17 @@ export class DBALIntegration { return this.kvStore } - /** - * Get the blob storage - */ - getBlobStorage(): TenantAwareBlobStorage { + getBlobStorage(): InMemoryBlobStorage { if (!this.blobStorage) { throw new Error('DBAL not initialized. Call initialize() first.') } return this.blobStorage } - /** - * Check if DBAL is initialized - */ isInitialized(): boolean { return this.initialized } - /** - * Reset the DBAL integration (useful for testing) - */ reset(): void { this.client = null this.tenantManager = null @@ -150,194 +234,80 @@ export class DBALIntegration { this.initialized = false } - /** - * Store data in the KV store with automatic tenant scoping - */ - async kvSet( - key: string, - value: any, - ttl?: number, - tenantId: string = 'default', - userId: string = 'system' - ): Promise { - if (!this.kvStore || !this.tenantManager) { - throw new Error('DBAL not initialized') - } - + // KV Store operations + async kvSet(key: string, value: any, ttl?: number, tenantId = 'default', userId = 'system'): Promise { + if (!this.kvStore || !this.tenantManager) throw new Error('DBAL not initialized') const context = await this.tenantManager.getTenantContext(tenantId, userId) - if (!context) { - throw new Error(`Tenant context not found: ${tenantId}`) - } - + if (!context) throw new Error(`Tenant not found: ${tenantId}`) await this.kvStore.set(key, value, context, ttl) } - /** - * Get data from the KV store - */ - async kvGet( - key: string, - tenantId: string = 'default', - userId: string = 'system' - ): Promise { - if (!this.kvStore || !this.tenantManager) { - throw new Error('DBAL not initialized') - } - + async kvGet(key: string, tenantId = 'default', userId = 'system'): Promise { + if (!this.kvStore || !this.tenantManager) throw new Error('DBAL not initialized') const context = await this.tenantManager.getTenantContext(tenantId, userId) - if (!context) { - throw new Error(`Tenant context not found: ${tenantId}`) - } - + if (!context) throw new Error(`Tenant not found: ${tenantId}`) return this.kvStore.get(key, context) as T | null } - /** - * Delete data from the KV store - */ - async kvDelete( - key: string, - tenantId: string = 'default', - userId: string = 'system' - ): Promise { - if (!this.kvStore || !this.tenantManager) { - throw new Error('DBAL not initialized') - } - + async kvDelete(key: string, tenantId = 'default', userId = 'system'): Promise { + if (!this.kvStore || !this.tenantManager) throw new Error('DBAL not initialized') const context = await this.tenantManager.getTenantContext(tenantId, userId) - if (!context) { - throw new Error(`Tenant context not found: ${tenantId}`) - } - + if (!context) throw new Error(`Tenant not found: ${tenantId}`) return this.kvStore.delete(key, context) } - /** - * List operations for the KV store - */ - async kvListAdd( - key: string, - items: any[], - tenantId: string = 'default', - userId: string = 'system' - ): Promise { - if (!this.kvStore || !this.tenantManager) { - throw new Error('DBAL not initialized') - } - + async kvListAdd(key: string, items: any[], tenantId = 'default', userId = 'system'): Promise { + if (!this.kvStore || !this.tenantManager) throw new Error('DBAL not initialized') const context = await this.tenantManager.getTenantContext(tenantId, userId) - if (!context) { - throw new Error(`Tenant context not found: ${tenantId}`) - } - + if (!context) throw new Error(`Tenant not found: ${tenantId}`) await this.kvStore.listAdd(key, items, context) } - async kvListGet( - key: string, - tenantId: string = 'default', - userId: string = 'system', - start?: number, - end?: number - ): Promise { - if (!this.kvStore || !this.tenantManager) { - throw new Error('DBAL not initialized') - } - + async kvListGet(key: string, tenantId = 'default', userId = 'system', start?: number, end?: number): Promise { + if (!this.kvStore || !this.tenantManager) throw new Error('DBAL not initialized') const context = await this.tenantManager.getTenantContext(tenantId, userId) - if (!context) { - throw new Error(`Tenant context not found: ${tenantId}`) - } - + if (!context) throw new Error(`Tenant not found: ${tenantId}`) return this.kvStore.listGet(key, context, start, end) } - /** - * Upload blob data - */ - async blobUpload( - key: string, - data: Buffer | Uint8Array, - metadata?: Record - ): Promise { - if (!this.blobStorage) { - throw new Error('DBAL not initialized') - } - + // Blob operations + async blobUpload(key: string, data: Buffer | Uint8Array, metadata?: Record): Promise { + if (!this.blobStorage) throw new Error('DBAL not initialized') await this.blobStorage.upload(key, data as Buffer, metadata) } - /** - * Download blob data - */ async blobDownload(key: string): Promise { - if (!this.blobStorage) { - throw new Error('DBAL not initialized') - } - + if (!this.blobStorage) throw new Error('DBAL not initialized') return this.blobStorage.download(key) } - /** - * Delete blob data - */ async blobDelete(key: string): Promise { - if (!this.blobStorage) { - throw new Error('DBAL not initialized') - } - + if (!this.blobStorage) throw new Error('DBAL not initialized') await this.blobStorage.delete(key) } - /** - * List blobs with optional prefix filter - */ async blobList(prefix?: string): Promise { - if (!this.blobStorage) { - throw new Error('DBAL not initialized') - } - + if (!this.blobStorage) throw new Error('DBAL not initialized') const result = await this.blobStorage.list({ prefix }) return result.items.map(item => item.key) } - /** - * Get blob metadata - */ async blobGetMetadata(key: string): Promise> { - if (!this.blobStorage) { - throw new Error('DBAL not initialized') - } - + if (!this.blobStorage) throw new Error('DBAL not initialized') const metadata = await this.blobStorage.getMetadata(key) return metadata.customMetadata || {} } - /** - * Handle DBAL errors gracefully - */ handleError(error: unknown): { message: string; code?: DBALErrorCode } { if (error instanceof DBALError) { - return { - message: error.message, - code: error.code, - } + return { message: error.message, code: error.code } } - if (error instanceof Error) { - return { - message: error.message, - } - } - - return { - message: 'An unknown error occurred', + return { message: error.message } } + return { message: 'An unknown error occurred' } } } // Export singleton instance export const dbal = DBALIntegration.getInstance() - -// Export for convenience -export { DBALError, DBALErrorCode } diff --git a/frontends/nextjs/src/lib/dbal-stub.ts b/frontends/nextjs/src/lib/dbal-stub.ts index 20db7d10d..4292f3800 100644 --- a/frontends/nextjs/src/lib/dbal-stub.ts +++ b/frontends/nextjs/src/lib/dbal-stub.ts @@ -10,6 +10,28 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ +// Error codes for DBAL operations +export enum DBALErrorCode { + UNKNOWN = 'UNKNOWN', + NOT_FOUND = 'NOT_FOUND', + VALIDATION = 'VALIDATION', + PERMISSION = 'PERMISSION', + CONNECTION = 'CONNECTION', + TIMEOUT = 'TIMEOUT', + DUPLICATE = 'DUPLICATE', +} + +// Custom error class for DBAL operations +export class DBALError extends Error { + code: DBALErrorCode + + constructor(message: string, code: DBALErrorCode = DBALErrorCode.UNKNOWN) { + super(message) + this.name = 'DBALError' + this.code = code + } +} + export interface DBALConfig { mode?: 'development' | 'production' adapter?: string