UNPKG

@aradox/multi-orm

Version:

Type-safe ORM with multi-datasource support, row-level security, and Prisma-like API for PostgreSQL, SQL Server, and HTTP APIs

365 lines 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GeneratedClient = exports.ModelDelegate = void 0; /** * Model delegate provides type-safe methods for a specific model */ class ModelDelegate { modelName; stitcher; adapter; ir; middlewareChain; getContext; constructor(modelName, stitcher, adapter, ir, middlewareChain, getContext) { this.modelName = modelName; this.stitcher = stitcher; this.adapter = adapter; this.ir = ir; this.middlewareChain = middlewareChain; this.getContext = getContext; } /** * Execute query with middleware hooks */ async executeWithMiddleware(operation, args, executor) { const context = this.getContext(); try { // Execute beforeQuery hooks const modifiedArgs = await this.middlewareChain.executeBeforeQuery(this.modelName, operation, args || {}, context); // Execute the actual query const result = await executor(modifiedArgs); // Execute afterQuery hooks const modifiedResult = await this.middlewareChain.executeAfterQuery(this.modelName, operation, modifiedArgs, result, context); return modifiedResult; } catch (error) { // Execute error hooks await this.middlewareChain.executeOnError(this.modelName, operation, args || {}, error, context); // Re-throw the error throw error; } } async findMany(args) { return this.executeWithMiddleware('findMany', args, async (modifiedArgs) => { const result = await this.stitcher.findMany(this.modelName, modifiedArgs || {}); return result.data; }); } async findUnique(args) { return this.executeWithMiddleware('findUnique', args, async (modifiedArgs) => { const result = await this.stitcher.findUnique(this.modelName, modifiedArgs); return result.data; }); } async findFirst(args) { return this.executeWithMiddleware('findMany', args, async (modifiedArgs) => { const result = await this.stitcher.findMany(this.modelName, { ...modifiedArgs, take: 1 }); return result.data[0] || null; }); } async count(args) { return this.executeWithMiddleware('count', args, async (modifiedArgs) => { return this.adapter.count(this.modelName, { where: modifiedArgs?.where }); }); } async create(args) { return this.executeWithMiddleware('create', args, async (modifiedArgs) => { const result = await this.adapter.create(this.modelName, { data: modifiedArgs.data }); // Invalidate cache for this model this.stitcher.invalidateCache(this.modelName); return result; }); } async createMany(args) { return this.executeWithMiddleware('createMany', args, async (modifiedArgs) => { let count = 0; for (const item of modifiedArgs.data) { try { await this.adapter.create(this.modelName, { data: item }); count++; } catch (error) { if (!modifiedArgs.skipDuplicates) throw error; } } // Invalidate cache for this model this.stitcher.invalidateCache(this.modelName); return { count }; }); } async update(args) { return this.executeWithMiddleware('update', args, async (modifiedArgs) => { const result = await this.adapter.update(this.modelName, { where: modifiedArgs.where, data: modifiedArgs.data }); // Invalidate cache for this model this.stitcher.invalidateCache(this.modelName); return result; }); } async updateMany(args) { return this.executeWithMiddleware('updateMany', args, async (modifiedArgs) => { // For now, simplified implementation // TODO: Add batch update support to adapters await this.adapter.update(this.modelName, { where: modifiedArgs.where || {}, data: modifiedArgs.data }); // Invalidate cache for this model this.stitcher.invalidateCache(this.modelName); return { count: 1 }; // Placeholder }); } async delete(args) { return this.executeWithMiddleware('delete', args, async (modifiedArgs) => { const result = await this.adapter.delete(this.modelName, { where: modifiedArgs.where }); // Invalidate cache for this model this.stitcher.invalidateCache(this.modelName); return result; }); } async deleteMany(args) { return this.executeWithMiddleware('deleteMany', args, async (modifiedArgs) => { // For now, simplified implementation // TODO: Add batch delete support to adapters await this.adapter.delete(this.modelName, { where: modifiedArgs?.where || {} }); // Invalidate cache for this model this.stitcher.invalidateCache(this.modelName); return { count: 1 }; // Placeholder }); } async upsert(args) { return this.executeWithMiddleware('upsert', args, async (modifiedArgs) => { // Try to find existing record const existing = await this.findUnique({ where: modifiedArgs.where }); if (existing) { // Update existing return this.adapter.update(this.modelName, { where: modifiedArgs.where, data: modifiedArgs.update }); } else { // Create new return this.adapter.create(this.modelName, { data: modifiedArgs.create }); } }); } } exports.ModelDelegate = ModelDelegate; /** * Generated client with model delegates */ class GeneratedClient { ir; stitcher; adapters; middlewareChain; queryContext; delegates = new Map(); datasourceQueryDelegates = new Map(); constructor(ir, stitcher, adapters, middlewareChain, queryContext) { this.ir = ir; this.stitcher = stitcher; this.adapters = adapters; this.middlewareChain = middlewareChain; this.queryContext = queryContext; this.generateDelegates(); this.generateDatasourceQueryDelegates(); } generateDelegates() { for (const [modelName, model] of Object.entries(this.ir.models)) { const adapter = this.adapters.get(model.datasource); if (!adapter) { throw new Error(`No adapter found for datasource: ${model.datasource}`); } const delegate = new ModelDelegate(modelName, this.stitcher, adapter, this.ir, this.middlewareChain, () => this.queryContext); this.delegates.set(modelName, delegate); // Create property accessor for model delegate Object.defineProperty(this, modelName, { get: () => delegate, enumerable: true, configurable: false }); } } /** * Generate datasource query delegates for raw SQL queries * Creates properties like client.main_db.query(sql) */ generateDatasourceQueryDelegates() { for (const [datasourceName, adapter] of this.adapters.entries()) { // Only create query delegate if adapter supports raw queries if ('query' in adapter && typeof adapter.query === 'function') { const queryDelegate = { query: async (sql, params) => { return adapter.query(sql, params); } }; this.datasourceQueryDelegates.set(datasourceName, queryDelegate); // Create property accessor for datasource query delegate Object.defineProperty(this, datasourceName, { get: () => queryDelegate, enumerable: true, configurable: false }); } } } /** * Register middleware for query interception * * Example: * ```typescript * client.use({ * name: 'tenant-isolation', * beforeQuery: ({ model, args, context }) => { * if (context.user?.tenantId) { * args.where = { ...args.where, tenant_id: context.user.tenantId }; * } * return args; * } * }); * ``` */ use(middleware) { this.middlewareChain.use(middleware); } /** * Set query context (user, tenantId, etc.) * This context is available to all middleware hooks * * Example: * ```typescript * client.setContext({ * user: { id: 123, tenantId: 456, role: 'admin' } * }); * ``` */ setContext(context) { this.queryContext = context; } /** * Get current query context */ getContext() { return this.queryContext; } /** * Get a model delegate by name */ model(modelName) { const delegate = this.delegates.get(modelName); if (!delegate) { throw new Error(`Model '${modelName}' not found in schema`); } return delegate; } /** * Close all adapter connections */ async $disconnect() { for (const adapter of this.adapters.values()) { if ('close' in adapter && typeof adapter.close === 'function') { await adapter.close(); } } } /** * Raw query execution (datasource-specific) */ async $queryRaw(datasourceName, query, params) { const adapter = this.adapters.get(datasourceName); if (!adapter) { throw new Error(`Datasource '${datasourceName}' not found`); } // Check if adapter supports raw queries if ('query' in adapter && typeof adapter.query === 'function') { return adapter.query(query, params); } throw new Error(`Adapter for '${datasourceName}' does not support raw queries`); } /** * Transaction support - Execute multiple operations in a transaction * * Example: * ```typescript * await client.$transaction(async (tx) => { * const user = await tx.User.create({ data: { email: 'test@example.com' } }); * const order = await tx.Orders.create({ data: { userId: user.id } }); * return { user, order }; * }); * ``` * * With options: * ```typescript * await client.$transaction(async (tx) => { * // ...operations * }, { * isolationLevel: 'SERIALIZABLE', * timeout: 10000 * }); * ``` */ async $transaction(callback, options) { // Find a SQL adapter that supports transactions let transactionAdapter = null; let datasourceName = null; for (const [name, adapter] of this.adapters.entries()) { if (adapter.capabilities.transactions && 'beginTransaction' in adapter) { transactionAdapter = adapter; datasourceName = name; break; } } if (!transactionAdapter || !datasourceName) { throw new Error('No adapter with transaction support found. Transactions require PostgreSQL or MSSQL adapters.'); } // Begin transaction const tx = await transactionAdapter.beginTransaction(options); // Create a transaction-scoped client const txAdapters = new Map(this.adapters); txAdapters.set(datasourceName, tx); // Replace adapter with transaction const txClient = new GeneratedClient(this.ir, this.stitcher, txAdapters, this.middlewareChain, this.queryContext); try { const result = await callback(txClient); await tx.commit(); return result; } catch (error) { await tx.rollback(); throw error; } } /** * Execute a raw SQL query on the first available SQL datasource * * Example: * ```typescript * const result = await client.generalQuery('SELECT * FROM users WHERE id = $1', [123]); * console.log(result); * ``` * * Note: For MSSQL, use @p1, @p2, etc. instead of $1, $2: * ```typescript * const result = await client.generalQuery('SELECT * FROM users WHERE id = @p1', [123]); * ``` * * @param sql - The SQL query string * @param params - Optional query parameters * @returns Query result rows */ async generalQuery(sql, params) { // Find the first SQL adapter that supports raw queries for (const [datasourceName, adapter] of this.adapters.entries()) { if ('query' in adapter && typeof adapter.query === 'function') { return adapter.query(sql, params); } } throw new Error('No SQL datasource with query support found. Raw queries require PostgreSQL or MSSQL adapters.'); } } exports.GeneratedClient = GeneratedClient; //# sourceMappingURL=generated-client.js.map