fix: remove DBAL stub, use real implementation only

- Removed all stub files (dbal-stub, database-lib/dbal-stub)
- Updated all imports to use real DBAL from @/dbal
- Updated tsconfig.json to add @/dbal path (without wildcard)
- Updated next.config.ts webpack alias to dbal/development/src
- Updated dbal-state, get-dbal, and dbal-client to use real DBALClient
- Frontend now exclusively uses real DBAL (development or production mode)
- No fallback to stub - proper error handling only

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-27 13:48:16 +00:00
parent efe56340f7
commit 68e2dfd950
19 changed files with 28 additions and 546 deletions

View File

@@ -85,7 +85,7 @@ const nextConfig: NextConfig = {
webpack(config) {
config.resolve.alias = {
...config.resolve.alias,
'@/dbal': path.resolve(__dirname, '..', 'dbal', 'ts', 'src'),
'@/dbal': path.resolve(__dirname, '../../dbal/development/src'),
}
return config
},

View File

@@ -1 +0,0 @@
export * from '../dbal/dbal-stub/index'

View File

@@ -2,5 +2,5 @@ export { createDBALClient } from './dbal-client/create-dbal-client'
export { getDBALClient } from './dbal-client/get-dbal-client'
export { migrateToDBAL } from './dbal-client/migrate-to-dbal'
export { DBALClient } from '@/lib/dbal-stub'
export type { DBALUser } from '@/lib/dbal-stub'
export { DBALClient } from '@/dbal'
export type { DBALUser } from '@/dbal/core/entities/operations/core/user-operations'

View File

@@ -1,12 +1,12 @@
/**
* DBAL Integration Layer (Stub Version)
* DBAL Integration Layer
*
* 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.
* This module provides DBAL integration using the real DBAL client
* from dbal/development for development mode and WebSocket connection
* for production mode.
*/
import { DBALClient, type DBALConfig } from '@/lib/dbal-stub'
import { DBALClient, type DBALConfig } from '@/dbal'
/* eslint-disable @typescript-eslint/no-explicit-any */

View File

@@ -1,5 +1,5 @@
// DBAL (Database Abstraction Layer) exports
export { DBALClient, createDBALClient } from './dbal-client'
export { DBALIntegration, dbal } from './dbal-integration'
export { default as DBALStub } from './dbal-stub'
export type { DBALConfig, DBALUser, ListResult } from './dbal-stub'
export { DBALClient as DBALRealClient } from '@/dbal'
export type { DBALConfig } from '@/dbal/runtime/config'

View File

@@ -1,48 +0,0 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { DBALClient, resetDBALStubState } from '@/lib/dbal-stub'
describe('dbal-stub users', () => {
beforeEach(() => {
resetDBALStubState()
})
it('lists seeded users', async () => {
const client = new DBALClient({ adapter: 'stub' })
const result = await client.users.list()
expect(result.data.length).toBeGreaterThanOrEqual(2)
const emails = result.data.map((user) => user.email)
expect(emails).toContain('admin@example.com')
expect(emails).toContain('user@example.com')
})
it('creates, updates, and deletes a user', async () => {
const client = new DBALClient({ adapter: 'stub' })
const created = await client.users.create({
username: 'newuser',
email: 'newuser@example.com',
role: 'admin',
bio: 'hello',
})
expect(created.id).toBeDefined()
expect(created.bio).toBe('hello')
const fetched = await client.users.get(created.id)
expect(fetched?.email).toBe('newuser@example.com')
const updated = await client.users.update(created.id, {
email: 'updated@example.com',
profilePicture: 'https://example.com/avatar.png',
})
expect(updated.email).toBe('updated@example.com')
expect(updated.profilePicture).toBe('https://example.com/avatar.png')
const deleted = await client.users.delete(created.id)
expect(deleted).toBe(true)
const afterDelete = await client.users.get(created.id)
expect(afterDelete).toBeNull()
})
})

View File

@@ -1,160 +0,0 @@
/**
* DBAL Stub Implementation
*
* Provides a mock implementation of DBAL when the full DBAL module is not available.
* This allows the frontend to build and function in development without the full DBAL.
*
* In production, replace this with the actual DBAL module connection.
*/
// 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
database?: {
url?: string
}
security?: {
sandbox?: 'strict' | 'permissive'
enableAuditLog?: boolean
}
}
export interface DBALUser {
id: string
email: string
username?: string
name?: string
role?: string
level?: number
tenantId?: string
profilePicture?: string
bio?: string
isInstanceOwner?: boolean
createdAt?: number | Date
updatedAt?: number | Date
}
export interface ListResult<T> {
data: T[]
total?: number
}
// In-memory store for development
const defaultUsers: DBALUser[] = [
{ id: '1', email: 'admin@example.com', username: 'admin', role: 'admin', level: 4 },
{ id: '2', email: 'user@example.com', username: 'user', role: 'user', level: 1 },
]
const userStore = new Map<string, DBALUser>(
defaultUsers.map((user) => [user.id, { ...user }])
)
let nextId = defaultUsers.length + 1
export function resetDBALStubState(): void {
userStore.clear()
defaultUsers.forEach((user) => userStore.set(user.id, { ...user }))
nextId = defaultUsers.length + 1
}
export class DBALClient {
private config: DBALConfig
constructor(config: DBALConfig) {
this.config = config
}
users = {
async list(): Promise<ListResult<DBALUser>> {
return {
data: Array.from(userStore.values()),
total: userStore.size,
}
},
async create(data: Partial<DBALUser>): Promise<DBALUser> {
const id = String(nextId++)
const user: DBALUser = {
id,
email: data.email || '',
username: data.username,
name: data.name,
role: data.role || 'user',
level: data.level || 1,
tenantId: data.tenantId,
profilePicture: data.profilePicture,
bio: data.bio,
isInstanceOwner: data.isInstanceOwner,
createdAt: Date.now(),
updatedAt: Date.now(),
}
userStore.set(id, user)
return user
},
async update(id: string, data: Partial<DBALUser>): Promise<DBALUser> {
const existing = userStore.get(id)
if (!existing) {
throw new Error(`User not found: ${id}`)
}
const updated: DBALUser = {
...existing,
...data,
id,
updatedAt: Date.now(),
}
userStore.set(id, updated)
return updated
},
async delete(id: string): Promise<boolean> {
return userStore.delete(id)
},
async get(id: string): Promise<DBALUser | null> {
return userStore.get(id) || null
},
}
async connect(): Promise<void> {
console.log('[DBAL Stub] Connected (mock mode)')
}
async disconnect(): Promise<void> {
console.log('[DBAL Stub] Disconnected (mock mode)')
}
async capabilities(): Promise<{ adapter: string; features: string[] }> {
return {
adapter: this.config.adapter || 'stub',
features: ['users', 'crud', 'mock'],
}
}
}
export default DBALClient

View File

@@ -1,8 +1,7 @@
import type { DBALClient as StubDBALClient } from '@/lib/dbal-stub'
import type { DBALClient as RealDBALClient } from '@/dbal/development/src'
import type { DBALClient } from '@/dbal'
export const dbalState: {
client: StubDBALClient | RealDBALClient | null
client: DBALClient | null
initialized: boolean
} = {
client: null,

View File

@@ -1,6 +1,6 @@
import 'server-only'
import type { DBALClient } from '@/lib/dbal-stub'
import type { DBALClient } from '@/dbal'
import { dbalState } from './dbal-state.server'
import { initializeDBAL } from './initialize-dbal.server'

View File

@@ -1,13 +1,12 @@
import 'server-only'
import { DBALClient as StubDBALClient } from '@/lib/dbal/dbal-stub'
import type { DBALConfig as StubDBALConfig } from '@/lib/dbal/dbal-stub'
import { DBALClient as RealDBALClient } from '@/dbal'
import type { DBALConfig as RealDBALConfig } from '@/dbal/runtime/config'
import { DBALClient } from '@/dbal'
import type { DBALConfig } from '@/dbal/runtime/config'
import { dbalState } from './dbal-state.server'
/**
* Initialize DBAL client for database operations
* Supports both development (TypeScript) and production (C++ daemon) modes
*/
export async function initializeDBAL(): Promise<void> {
if (dbalState.initialized) {
@@ -20,7 +19,7 @@ export async function initializeDBAL(): Promise<void> {
const engine = process.env.DBAL_SQL_ENGINE?.toLowerCase() === 'mysql' ? 'mysql' : 'postgres'
const auth = process.env.DBAL_API_KEY ? { apiKey: process.env.DBAL_API_KEY } : undefined
const config: RealDBALConfig = {
const config: DBALConfig = {
mode,
adapter: endpoint ? 'prisma' : engine,
endpoint: endpoint ?? undefined,
@@ -34,25 +33,7 @@ export async function initializeDBAL(): Promise<void> {
},
}
try {
dbalState.client = new RealDBALClient(config)
dbalState.initialized = true
console.log('Real DBAL client initialized successfully')
} catch (error) {
console.warn('Falling back to stub DBAL client:', error)
const stubConfig: StubDBALConfig = {
mode,
adapter: engine,
auth,
database: {
url: databaseUrl,
},
security: {
sandbox: 'permissive',
enableAuditLog: false,
},
}
dbalState.client = new StubDBALClient(stubConfig)
dbalState.initialized = true
}
dbalState.client = new DBALClient(config)
dbalState.initialized = true
console.log(`DBAL client initialized in ${mode} mode${endpoint ? ' with WebSocket endpoint' : ''}`)
}

View File

@@ -1,5 +1,5 @@
import { DBALClient } from '@/lib/dbal-stub'
import type { DBALConfig } from '@/lib/dbal-stub'
import { DBALClient } from '@/dbal'
import type { DBALConfig } from '@/dbal'
import type { User } from '../../types/level-types'
import { dbalClientState } from './dbal-client-state'

View File

@@ -1,4 +1,4 @@
import type { DBALClient } from '@/lib/dbal-stub'
import type { DBALClient } from '@/dbal'
export const dbalClientState: { instance: DBALClient | null } = {
instance: null,

View File

@@ -1,82 +0,0 @@
/**
* DBAL Blob Storage Stub
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface BlobStorageConfig {
type: 'filesystem' | 'memory' | 's3'
basePath?: string
}
export interface BlobMetadata {
contentType?: string
size?: number
lastModified?: Date
[key: string]: any
}
export interface BlobListItem {
key: string
[key: string]: any
}
export interface BlobListResult {
items: BlobListItem[]
}
export interface BlobStorage {
upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise<string>
download(key: string): Promise<Buffer>
delete(key: string): Promise<void>
exists(key: string): Promise<boolean>
list(options?: { prefix?: string }): Promise<BlobListResult>
getMetadata(key: string): Promise<BlobMetadata | null>
}
class InMemoryBlobStorage implements BlobStorage {
private store = new Map<string, { data: Buffer; metadata: BlobMetadata }>()
async upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise<string> {
const buffer = typeof data === 'string' ? Buffer.from(data) : data
this.store.set(key, {
data: buffer,
metadata: { ...metadata, size: buffer.length, lastModified: new Date() },
})
return key
}
async download(key: string): Promise<Buffer> {
const item = this.store.get(key)
if (!item) throw new Error(`Blob not found: ${key}`)
return item.data
}
async delete(key: string): Promise<void> {
this.store.delete(key)
}
async exists(key: string): Promise<boolean> {
return this.store.has(key)
}
async list(options?: { prefix?: string }): Promise<BlobListResult> {
const items: BlobListItem[] = []
for (const key of this.store.keys()) {
if (!options?.prefix || key.startsWith(options.prefix)) {
items.push({ key })
}
}
return { items }
}
async getMetadata(key: string): Promise<BlobMetadata | null> {
const item = this.store.get(key)
return item?.metadata ?? null
}
}
export function createBlobStorage(_config: BlobStorageConfig): BlobStorage {
return new InMemoryBlobStorage()
}

View File

@@ -1,40 +0,0 @@
/**
* DBAL Tenant-Aware Blob Storage Stub
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { BlobStorage, BlobMetadata, BlobListResult } from './index'
export class TenantAwareBlobStorage implements BlobStorage {
constructor(
private storage: BlobStorage,
_tenantManager: any,
..._args: any[]
) {}
async upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise<string> {
return this.storage.upload(key, data, metadata)
}
async download(key: string): Promise<Buffer> {
return this.storage.download(key)
}
async delete(key: string): Promise<void> {
return this.storage.delete(key)
}
async exists(key: string): Promise<boolean> {
return this.storage.exists(key)
}
async list(options?: { prefix?: string }): Promise<BlobListResult> {
return this.storage.list(options)
}
async getMetadata(key: string): Promise<BlobMetadata | null> {
return this.storage.getMetadata(key)
}
}

View File

@@ -1,34 +0,0 @@
/**
* DBAL KV Store Stub
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TenantContext } from './tenant-context'
export class InMemoryKVStore {
private store = new Map<string, any>()
async get<T>(key: string, _context?: TenantContext): Promise<T | null> {
return this.store.get(key) ?? null
}
async set<T>(key: string, value: T, _context?: TenantContext, _ttl?: number): Promise<void> {
this.store.set(key, value)
}
async delete(key: string, _context?: TenantContext): Promise<boolean> {
return this.store.delete(key)
}
async listAdd(key: string, items: any[], _context?: TenantContext): Promise<void> {
const existing = this.store.get(key) || []
this.store.set(key, [...existing, ...items])
}
async listGet(key: string, _context?: TenantContext, start = 0, end = -1): Promise<any[]> {
const list = this.store.get(key) || []
return end === -1 ? list.slice(start) : list.slice(start, end)
}
}

View File

@@ -1,31 +0,0 @@
/**
* DBAL Tenant Context Stub
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface TenantContext {
tenantId: string
userId?: string
}
export class InMemoryTenantManager {
private currentTenant: string | null = null
setCurrentTenant(tenantId: string): void {
this.currentTenant = tenantId
}
getCurrentTenant(): string | null {
return this.currentTenant
}
async createTenant(_id: string, _metadata: Record<string, any>, ..._args: any[]): Promise<void> {
// Stub implementation
}
async getTenantContext(tenantId: string, userId?: string): Promise<TenantContext> {
return { tenantId, userId }
}
}

View File

@@ -1,11 +0,0 @@
/**
* DBAL Core Types Stub
*/
export interface User {
id: string
email: string
name?: string
level: number
tenantId: string
}

View File

@@ -1,94 +0,0 @@
/**
* DBAL Stub Module
* This provides runtime stubs for the DBAL when the full module is not available.
* Used in the frontend when DBAL is not fully configured.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface DBALConfig {
mode?: 'development' | 'production'
adapter?: string
auth?: any
database?: {
url?: string
}
security?: {
sandbox?: 'strict' | 'permissive' | 'disabled'
enableAuditLog?: boolean
}
[key: string]: any
}
export interface DBALUser {
id: string
email: string
name?: string
username?: string
level?: number
role?: string
tenantId?: string
createdAt?: number | string | Date
[key: string]: any
}
export interface ListResult<T> {
data: T[]
total?: number
}
export interface UsersAPI {
list(): Promise<ListResult<DBALUser>>
create(data: Partial<DBALUser>): Promise<DBALUser>
update(id: string, data: Partial<DBALUser>): Promise<DBALUser>
delete(id: string): Promise<boolean>
}
export class DBALClient {
users: UsersAPI
constructor(_config: DBALConfig) {
// Stub users API
this.users = {
list: async () => ({ data: [], total: 0 }),
create: async (data) => ({ id: 'stub', email: data.email || '', ...data }),
update: async (id, data) => ({ id, email: '', ...data }),
delete: async () => true,
}
}
async query<T>(_sql: string, _params?: unknown[]): Promise<T[]> {
console.warn('DBAL stub: query not implemented')
return []
}
async execute(_sql: string, _params?: unknown[]): Promise<void> {
console.warn('DBAL stub: execute not implemented')
}
async capabilities(): Promise<Record<string, boolean>> {
return {
users: true,
tenants: false,
kv: false,
blob: false,
}
}
}
export class DBALError extends Error {
code: DBALErrorCode
constructor(message: string, code: DBALErrorCode) {
super(message)
this.code = code
this.name = 'DBALError'
}
}
export enum DBALErrorCode {
UNKNOWN = 'UNKNOWN',
CONNECTION_ERROR = 'CONNECTION_ERROR',
QUERY_ERROR = 'QUERY_ERROR',
VALIDATION_ERROR = 'VALIDATION_ERROR',
}

View File

@@ -29,11 +29,14 @@
"@/*": [
"./src/*"
],
"@/dbal": [
"../../dbal/development/src"
],
"@/dbal/*": [
"../dbal/development/src/*"
"../../dbal/development/src/*"
],
"@dbal-ui/*": [
"../dbal/src/*"
"../../dbal/src/*"
]
},
"plugins": [