|
| 1 | +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +import type { IDataDriver } from '@objectstack/spec'; |
| 4 | +import type { DriverConfig } from '@objectstack/spec/cloud'; |
| 5 | + |
| 6 | +/** |
| 7 | + * Driver Factory Configuration |
| 8 | + */ |
| 9 | +export interface DriverFactoryConfig { |
| 10 | + /** |
| 11 | + * Available driver instances keyed by driver name |
| 12 | + * Example: { 'turso': tursoDriver, 'memory': memoryDriver } |
| 13 | + */ |
| 14 | + drivers?: Map<string, IDataDriver>; |
| 15 | + |
| 16 | + /** |
| 17 | + * Driver class constructors for dynamic instantiation |
| 18 | + * Example: { 'turso': TursoDriver, 'memory': InMemoryDriver } |
| 19 | + */ |
| 20 | + driverConstructors?: Map<string, new (config: any) => IDataDriver>; |
| 21 | +} |
| 22 | + |
| 23 | +/** |
| 24 | + * Driver Factory |
| 25 | + * |
| 26 | + * Manages driver instances for multi-tenant deployments. |
| 27 | + * Each organization's database uses a specific driver configuration, |
| 28 | + * and the factory creates/caches driver instances on demand. |
| 29 | + * |
| 30 | + * Features: |
| 31 | + * - Instance caching to avoid creating duplicate drivers |
| 32 | + * - Support for pre-configured drivers |
| 33 | + * - Dynamic driver instantiation |
| 34 | + * - Type-safe driver configuration |
| 35 | + */ |
| 36 | +export class DriverFactory { |
| 37 | + private driverCache = new Map<string, IDataDriver>(); |
| 38 | + private config: DriverFactoryConfig; |
| 39 | + |
| 40 | + constructor(config: DriverFactoryConfig = {}) { |
| 41 | + this.config = config; |
| 42 | + |
| 43 | + // Pre-populate cache with provided drivers |
| 44 | + if (config.drivers) { |
| 45 | + for (const [key, driver] of config.drivers) { |
| 46 | + this.driverCache.set(key, driver); |
| 47 | + } |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + /** |
| 52 | + * Create or retrieve a driver instance based on configuration |
| 53 | + * |
| 54 | + * @param driverConfig - Driver configuration from TenantDatabase |
| 55 | + * @returns Driver instance ready to use |
| 56 | + */ |
| 57 | + async create(driverConfig: DriverConfig): Promise<IDataDriver> { |
| 58 | + const cacheKey = this.getCacheKey(driverConfig); |
| 59 | + |
| 60 | + // Check cache first |
| 61 | + if (this.driverCache.has(cacheKey)) { |
| 62 | + return this.driverCache.get(cacheKey)!; |
| 63 | + } |
| 64 | + |
| 65 | + // Create new driver instance |
| 66 | + const driver = await this.instantiateDriver(driverConfig); |
| 67 | + |
| 68 | + // Cache for future use |
| 69 | + this.driverCache.set(cacheKey, driver); |
| 70 | + |
| 71 | + return driver; |
| 72 | + } |
| 73 | + |
| 74 | + /** |
| 75 | + * Instantiate a new driver based on configuration |
| 76 | + */ |
| 77 | + private async instantiateDriver(driverConfig: DriverConfig): Promise<IDataDriver> { |
| 78 | + switch (driverConfig.driver) { |
| 79 | + case 'turso': |
| 80 | + return this.createTursoDriver(driverConfig); |
| 81 | + |
| 82 | + case 'memory': |
| 83 | + return this.createMemoryDriver(driverConfig); |
| 84 | + |
| 85 | + case 'sql': |
| 86 | + return this.createSQLDriver(driverConfig); |
| 87 | + |
| 88 | + case 'sqlite': |
| 89 | + return this.createSQLiteDriver(driverConfig); |
| 90 | + |
| 91 | + case 'custom': |
| 92 | + return this.createCustomDriver(driverConfig); |
| 93 | + |
| 94 | + default: |
| 95 | + throw new Error(`Unsupported driver type: ${(driverConfig as any).driver}`); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + /** |
| 100 | + * Create Turso driver instance |
| 101 | + */ |
| 102 | + private async createTursoDriver(config: DriverConfig): Promise<IDataDriver> { |
| 103 | + if (config.driver !== 'turso') { |
| 104 | + throw new Error('Invalid driver config for Turso'); |
| 105 | + } |
| 106 | + |
| 107 | + // Check if constructor is available |
| 108 | + const TursoDriverConstructor = this.config.driverConstructors?.get('turso'); |
| 109 | + if (!TursoDriverConstructor) { |
| 110 | + throw new Error( |
| 111 | + 'Turso driver constructor not registered. Register it via DriverFactory config.', |
| 112 | + ); |
| 113 | + } |
| 114 | + |
| 115 | + return new TursoDriverConstructor({ |
| 116 | + url: config.databaseUrl, |
| 117 | + authToken: config.authToken, |
| 118 | + syncUrl: config.syncUrl, |
| 119 | + }); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Create Memory driver instance |
| 124 | + */ |
| 125 | + private async createMemoryDriver(config: DriverConfig): Promise<IDataDriver> { |
| 126 | + if (config.driver !== 'memory') { |
| 127 | + throw new Error('Invalid driver config for Memory'); |
| 128 | + } |
| 129 | + |
| 130 | + const MemoryDriverConstructor = this.config.driverConstructors?.get('memory'); |
| 131 | + if (!MemoryDriverConstructor) { |
| 132 | + throw new Error( |
| 133 | + 'Memory driver constructor not registered. Register it via DriverFactory config.', |
| 134 | + ); |
| 135 | + } |
| 136 | + |
| 137 | + return new MemoryDriverConstructor({ |
| 138 | + persistent: config.persistent, |
| 139 | + dataFile: config.dataFile, |
| 140 | + }); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Create SQL driver instance |
| 145 | + */ |
| 146 | + private async createSQLDriver(config: DriverConfig): Promise<IDataDriver> { |
| 147 | + if (config.driver !== 'sql') { |
| 148 | + throw new Error('Invalid driver config for SQL'); |
| 149 | + } |
| 150 | + |
| 151 | + const SQLDriverConstructor = this.config.driverConstructors?.get('sql'); |
| 152 | + if (!SQLDriverConstructor) { |
| 153 | + throw new Error( |
| 154 | + 'SQL driver constructor not registered. Register it via DriverFactory config.', |
| 155 | + ); |
| 156 | + } |
| 157 | + |
| 158 | + return new SQLDriverConstructor({ |
| 159 | + dialect: config.dialect, |
| 160 | + host: config.host, |
| 161 | + port: config.port, |
| 162 | + database: config.database, |
| 163 | + username: config.username, |
| 164 | + password: config.password, |
| 165 | + ssl: config.ssl, |
| 166 | + pool: config.pool, |
| 167 | + }); |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Create SQLite driver instance |
| 172 | + */ |
| 173 | + private async createSQLiteDriver(config: DriverConfig): Promise<IDataDriver> { |
| 174 | + if (config.driver !== 'sqlite') { |
| 175 | + throw new Error('Invalid driver config for SQLite'); |
| 176 | + } |
| 177 | + |
| 178 | + const SQLiteDriverConstructor = this.config.driverConstructors?.get('sqlite'); |
| 179 | + if (!SQLiteDriverConstructor) { |
| 180 | + throw new Error( |
| 181 | + 'SQLite driver constructor not registered. Register it via DriverFactory config.', |
| 182 | + ); |
| 183 | + } |
| 184 | + |
| 185 | + return new SQLiteDriverConstructor({ |
| 186 | + filename: config.filename, |
| 187 | + readonly: config.readonly, |
| 188 | + }); |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Create Custom driver instance |
| 193 | + */ |
| 194 | + private async createCustomDriver(config: DriverConfig): Promise<IDataDriver> { |
| 195 | + if (config.driver !== 'custom') { |
| 196 | + throw new Error('Invalid driver config for Custom'); |
| 197 | + } |
| 198 | + |
| 199 | + const CustomDriverConstructor = this.config.driverConstructors?.get(config.driverName); |
| 200 | + if (!CustomDriverConstructor) { |
| 201 | + throw new Error( |
| 202 | + `Custom driver '${config.driverName}' constructor not registered. Register it via DriverFactory config.`, |
| 203 | + ); |
| 204 | + } |
| 205 | + |
| 206 | + return new CustomDriverConstructor(config.config); |
| 207 | + } |
| 208 | + |
| 209 | + /** |
| 210 | + * Generate cache key from driver configuration |
| 211 | + */ |
| 212 | + private getCacheKey(driverConfig: DriverConfig): string { |
| 213 | + // Create a unique key based on driver type and critical connection params |
| 214 | + switch (driverConfig.driver) { |
| 215 | + case 'turso': |
| 216 | + return `turso:${driverConfig.databaseUrl}`; |
| 217 | + |
| 218 | + case 'memory': |
| 219 | + return `memory:${driverConfig.dataFile || 'ephemeral'}`; |
| 220 | + |
| 221 | + case 'sql': |
| 222 | + return `sql:${driverConfig.dialect}:${driverConfig.host}:${driverConfig.port}:${driverConfig.database}`; |
| 223 | + |
| 224 | + case 'sqlite': |
| 225 | + return `sqlite:${driverConfig.filename}`; |
| 226 | + |
| 227 | + case 'custom': |
| 228 | + return `custom:${driverConfig.driverName}:${JSON.stringify(driverConfig.config)}`; |
| 229 | + |
| 230 | + default: |
| 231 | + return `unknown:${JSON.stringify(driverConfig)}`; |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + /** |
| 236 | + * Clear driver cache (useful for testing) |
| 237 | + */ |
| 238 | + clearCache(): void { |
| 239 | + this.driverCache.clear(); |
| 240 | + } |
| 241 | + |
| 242 | + /** |
| 243 | + * Remove specific driver from cache |
| 244 | + */ |
| 245 | + invalidateDriver(cacheKey: string): void { |
| 246 | + this.driverCache.delete(cacheKey); |
| 247 | + } |
| 248 | + |
| 249 | + /** |
| 250 | + * Get number of cached drivers |
| 251 | + */ |
| 252 | + getCacheSize(): number { |
| 253 | + return this.driverCache.size; |
| 254 | + } |
| 255 | +} |
0 commit comments