/** * DbService - Singleton Database Connection Manager * * @description Manages database connections for the entire application using a singleton pattern. * This service wraps @plyaz/db and provides a centralized way to initialize and access * the database across all domains (feature flags, users, campaigns, backoffice, etc.). * * **Architecture:** * - Uses `createDatabaseService()` from @plyaz/db which builds an adapter chain * - Supports Drizzle (direct PostgreSQL), Supabase (REST API), or raw SQL adapters * - Extension layers: Encryption → SoftDelete → Caching → Audit → ReadReplica * * **Adapter Configuration:** * - **Drizzle**: Requires `connectionString` (PostgreSQL URL) * - **Supabase**: Requires `supabaseUrl`, `supabaseServiceKey`, `supabaseAnonKey` * - **SQL**: Requires `connectionString` * * **Required Environment Variables (Drizzle with Supabase):** * - DATABASE_URL: PostgreSQL connection string (from Supabase Dashboard > Database > URI) * * **Required Environment Variables (Supabase REST API):** * - SUPABASE_URL: Your Supabase project URL * - SUPABASE_SERVICE_ROLE_KEY: Service role key for admin operations * - SUPABASE_ANON_PUBLIC_KEY: Anonymous key for public operations * * @example Using with Core.initialize() (Recommended) * ```typescript * import { Core } from '@plyaz/core'; * * // Core.initialize() handles loading env and passing to DbService * await Core.initialize({ * envPath: '.env', * db: { * adapter: 'sql', * cache: { enabled: true, ttl: 60 }, * }, * }); * * // Access via Core.db * const db = Core.db.getDatabase(); * const result = await db.list('users', { pagination: { limit: 10, offset: 0 } }); * ``` * * @example Direct Usage (requires explicit config) * ```typescript * // Direct usage requires explicit connectionString * await DbService.initialize({ * adapter: 'drizzle', * drizzle: { * connectionString: 'postgresql://user:pass@localhost:5432/db', * poolSize: 20, * }, * }); * ``` * * @example With Extensions * ```typescript * await Core.initialize({ * envPath: '.env', * db: { * adapter: 'drizzle', * softDelete: { * enabled: true, * field: 'deleted_at', * excludeTables: ['audit_logs', 'feature_flag_evaluations'], * }, * cache: { * enabled: true, * provider: 'memory', * ttl: 300, * }, * audit: { * enabled: true, * retentionDays: 90, * }, * }, * }); * ``` * * @example Using with BaseRepository * ```typescript * import { BaseRepository } from '@plyaz/db'; * import { DbService } from '@plyaz/core'; * * class UserRepository extends BaseRepository { * constructor() { * super(DbService.getInstance().getDatabase(), 'users'); * } * } * ``` * * @module services */ import { type DatabaseServiceInterface, type Transaction } from '@plyaz/types/db'; /** * Default fields to encrypt per table * Contains sensitive PII and financial data that should be encrypted at rest * * @see docs/db-schemas/dbdiagram_schema.dbml for field definitions */ export declare const DEFAULT_ENCRYPTION_FIELDS: Record; export type { CoreDbServiceConfig } from '@plyaz/types/core'; import type { CoreDbServiceConfig as DbServiceConfig, CoreDbServiceInstance } from '@plyaz/types/core'; /** * Complete table registry from database migrations * Maps logical table names to their ID columns * * @see docs/db-schemas/migrations/ for schema definitions * @see docs/db-schemas/dbdiagram_schema.dbml for full schema */ /** * TABLE_REGISTRY - Custom ID Column and Schema Registry * * ONLY register tables with: * 1. Custom ID columns (anything other than 'id') * 2. Custom database schemas (anything other than 'public') * * Tables with standard 'id' column in 'public' schema do NOT need registration. * You can also override ID column and schema per-query using OperationConfig. * * Schema can be defined in the table key or in the config object (or both): * - 'schema.table': { idColumn: 'key' } ← Schema in key only * - 'schema.table': { idColumn: 'key', schema: 'schema' } ← Redundant but allowed * - 'schema.table': {} ← Empty config, schema in key * - 'table': { idColumn: 'key', schema: 'schema' } ← Schema in config only * * @example * ```typescript * // Using registry * await db.get('feature_flags', 'my-flag-key'); // Uses 'key' column from registry * * // Per-query override * await db.get('custom_table', 'some-key', { idColumn: 'custom_id' }); * * // Schema override * await db.get('logs', '123', { schema: 'logging' }); * ``` */ export declare const TABLE_REGISTRY: Record; export declare class DbService implements CoreDbServiceInstance { private databaseService; private namedAdapters; private config; private initialized; private constructor(); /** * Emits a database error event via CoreEventManager. * Called when database operations fail to integrate with global error handling. * * @param error - The error that occurred * @param operation - The operation that failed (e.g., 'transaction', 'query', 'healthCheck') * @param table - Optional table name involved in the operation * @param query - Optional query string that failed * @param recoverable - Whether the error is recoverable (default: false) */ private emitDatabaseError; /** * Creates merged event handlers that wrap user-provided handlers. * Adds Core-level logging and forwards to user handlers. * * Note: Unlike StorageService/NotificationService, DB events don't emit * to CoreEventManager by default (too verbose). User handlers can emit * if needed. * * @param userHandlers - User-provided event handlers from config * @returns Merged handlers with Core logging + user handlers */ private static createMergedEventHandlers; /** * Gets the singleton instance of DbService * * @returns {DbService} The singleton instance */ static getInstance(): DbService; /** * Checks if the database service has been initialized * * @returns {boolean} True if initialized */ static isInitialized(): boolean; /** * Resets the database service by closing connections and clearing the singleton instance * * @description Properly closes the database connection and clears the singleton. * Useful for testing or when you need to reinitialize with different configuration. * * @example * ```typescript * await DbService.reset(); * await DbService.initialize({ adapter: 'sql', ... }); * ``` */ static reset(): Promise; /** * Initializes the database connection * * @description Sets up the database connection using the provided configuration * or environment variables. This method is idempotent - calling it multiple * times won't create additional connections. * * **Environment Variables:** * - `DATABASE_URL` - PostgreSQL connection string (for sql/drizzle adapters) * - `ENCRYPTION_KEY` - 32-byte encryption key for field encryption (optional) * - `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_ANON_PUBLIC_KEY` - For supabase adapter * * **Encryption:** If `ENCRYPTION_KEY` env is set, encryption is auto-enabled * using `DEFAULT_ENCRYPTION_FIELDS`. Override via config.encryption. * * @param {DbServiceConfig} [config] - Optional configuration * @returns {Promise} The initialized DbService instance * @throws {DatabasePackageError} When configuration is invalid or connection fails * * @example * ```typescript * // Minimal - uses DATABASE_URL env, auto-enables encryption if ENCRYPTION_KEY set * await DbService.initialize(); * * // With explicit encryption * await DbService.initialize({ * adapter: 'sql', * encryption: { * enabled: true, * key: 'your-32-byte-encryption-key-here!!', * fields: DEFAULT_ENCRYPTION_FIELDS, * }, * }); * * // Or via Core.initialize() with env: * await Core.initialize({ * env: { ENCRYPTION_KEY: '...' }, * db: { adapter: 'sql' }, * }); * ``` */ /** Build encryption config from user config */ private static buildEncryptionConfig; /** Merge user config with defaults */ private static mergeConfig; /** Initialize named adapters */ private initializeNamedAdapters; static initialize(config?: DbServiceConfig): Promise; /** * Builds the DatabaseServiceConfig based on the adapter type * @private */ /** Builds adapter-specific config based on adapter type */ private buildAdapterConfig; /** Builds soft delete extension config */ private buildSoftDeleteExtension; /** Builds cache extension config */ private buildCacheExtension; /** Builds audit extension config */ private buildAuditExtension; /** Builds encryption extension config */ private buildEncryptionExtension; private buildDatabaseConfig; /** * Builds Drizzle adapter configuration * @private */ private buildDrizzleConfig; /** * Builds Supabase adapter configuration * @private */ private buildSupabaseConfig; /** * Builds SQL adapter configuration * @private */ private buildSqlConfig; /** * Builds table ID column mappings from TABLE_REGISTRY * @private * @returns Record of table names to ID column names */ private buildTableIdColumns; /** * Gets the initialized database service instance * * @param {string} [adapterName] - Optional named adapter to use instead of default * @returns {DatabaseServiceInterface} The database service instance * @throws {DatabasePackageError} When database is not initialized or named adapter not found */ getDatabase(adapterName?: string): DatabaseServiceInterface; /** * Gets a named adapter by name * * @param {string} name - The name of the adapter * @returns {DatabaseServiceInterface} The named adapter instance * @throws {DatabasePackageError} When adapter not found */ getAdapter(name: string): DatabaseServiceInterface; /** * Lists all available named adapters * * @returns {string[]} Array of adapter names */ getAvailableAdapters(): string[]; /** * Executes a database transaction with automatic rollback on failure * * @template T The return type of the transaction callback * @param {Function} callback - Function that receives transaction object * @returns {Promise} The result of the transaction callback * @throws {DatabasePackageError} When transaction fails */ transaction(callback: (tx: Transaction) => Promise): Promise; /** * Sets audit context for subsequent operations * * @param context - Audit context (userId, requestId, etc.) */ setAuditContext(context: { userId?: string; requestId?: string; ipAddress?: string; userAgent?: string; }): Promise; /** * Performs a health check on the database connection * * @returns Health check result */ healthCheck(): Promise<{ isHealthy: boolean; responseTime?: number; error?: string; }>; /** * Gets the table registry with all known tables and their ID columns * * @returns The complete table registry */ static getTableRegistry(): typeof TABLE_REGISTRY; /** * Gets ID column for a specific table * * @param tableName - Name of the table * @returns ID column name or 'id' as default */ static getTableIdColumn(tableName: string): string; /** * Reinitializes the database connection with new config * * @param {DbServiceConfig} [config] - New configuration * @returns {Promise} The reinitialized DbService instance */ static reinitialize(config?: DbServiceConfig): Promise; /** * Closes the database connection and cleans up resources */ close(): Promise; /** * Gets the current configuration * * @returns {DbServiceConfig | null} Current config or null if not initialized */ getConfig(): DbServiceConfig | null; /** * Gets the current adapter type * * @returns The adapter type or null if not initialized */ getAdapterType(): 'drizzle' | 'supabase' | 'sql' | null; /** * Creates a dedicated database service instance (NOT the singleton) * * Use this when you need an isolated database connection with its own configuration * that doesn't affect or get affected by the shared singleton instance. * * @param config - Database service configuration * @returns Promise that resolves to a new dedicated DbService instance * * @example * ```typescript * // Create a dedicated instance for analytics database * const analyticsDb = await DbService.createInstance({ * adapter: 'sql', * sql: { connectionString: process.env.ANALYTICS_DB_URL }, * cache: { enabled: false }, // No caching for analytics * }); * * // This instance is independent from DbService.getInstance() * const data = await analyticsDb.getDatabase().list('events'); * * // Clean up when done * await analyticsDb.close(); * ``` */ static createInstance(config: DbServiceConfig): Promise; } /** Type alias for DbService instance (use for type-only imports to avoid bundling) */ export type DbServiceInstance = DbService; //# sourceMappingURL=DbService.d.ts.map