@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
420 lines • 19.3 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTypes = exports.TypeGenerator = exports.ModelDelegate = exports.GeneratedClient = exports.Stitcher = exports.MSSQLNativeAdapter = exports.MSSQLAdapter = exports.HttpApiAdapter = exports.PostgresAdapter = exports.DSLParser = exports.ORMClient = void 0;
const stitcher_1 = require("./runtime/stitcher");
const parser_1 = require("./parser");
const postgres_1 = require("./adapters/postgres");
const http_1 = require("./adapters/http");
const mssql_1 = require("./adapters/mssql");
const mongodb_1 = require("./adapters/mongodb");
const mysql_1 = require("./adapters/mysql");
const generated_client_1 = require("./client/generated-client");
const logger_1 = require("./utils/logger");
const middleware_1 = require("./types/middleware");
/**
* ORM Client Factory - Used to generate type-safe clients from schema
*
* Usage:
* ```typescript
* import { ORMClient } from '@your-org/orm';
* import * as fs from 'fs';
*
* const schema = fs.readFileSync('./schema.prisma', 'utf-8');
* const orm = new ORMClient(schema);
* const client = orm.generate();
*
* // Now use type-safe methods
* const users = await client.User.findMany({ where: { active: true } });
* ```
*/
class ORMClient {
ir;
stitcher;
adapters;
schemaPath;
middlewareChain;
queryContext;
constructor(schema, options) {
const parser = new parser_1.DSLParser();
this.ir = parser.parse(schema);
this.adapters = this.initializeAdapters();
this.schemaPath = options?.schemaPath;
this.stitcher = new stitcher_1.Stitcher(this.ir, this.adapters, this.schemaPath, options?.cache);
this.middlewareChain = new middleware_1.MiddlewareChain();
this.queryContext = {}; // Empty context by default
}
/**
* 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 middleware chain (for GeneratedClient to use)
*/
getMiddlewareChain() {
return this.middlewareChain;
}
/**
* Generate a type-safe client with model delegates
* This is similar to Prisma's generated client pattern
*
* Checks if TypeScript types have been generated and suggests running
* the generator if they don't exist or are out of date.
*/
generate() {
// Check for generated types
const typesChecked = this.checkGeneratedTypes();
if (!typesChecked.exists) {
console.warn('\n⚠️ Generated TypeScript types not found!');
console.warn(' For full IntelliSense support, run:');
if (this.schemaPath) {
console.warn(` npx orm generate ${this.schemaPath}`);
}
else {
console.warn(' npx orm generate ./schema.prisma');
}
console.warn(' Or: npm run generate\n');
}
else if (typesChecked.outOfDate) {
console.warn('\n⚠️ Schema may have changed since types were generated.');
console.warn(' Consider regenerating types:');
if (this.schemaPath) {
console.warn(` npx orm generate ${this.schemaPath}`);
}
else {
console.warn(' npx orm generate ./schema.prisma');
}
console.warn(' Or: npm run generate\n');
}
else {
console.log('✅ Using generated types from:', typesChecked.path);
}
return new generated_client_1.GeneratedClient(this.ir, this.stitcher, this.adapters, this.middlewareChain, this.queryContext);
}
/**
* Check if generated types exist and if they're up to date
*/
checkGeneratedTypes() {
const fs = require('fs');
const path = require('path');
// Common paths to check for generated types
const possiblePaths = [
'./.qts/types.d.ts', // For .qts files
'./generated/types.d.ts',
'./Example/generated/types.d.ts',
'./Example/.qts/types.d.ts', // For .qts files in Example
'./src/generated/types.d.ts',
path.join(process.cwd(), '.qts', 'types.d.ts'),
path.join(process.cwd(), 'generated', 'types.d.ts'),
path.join(process.cwd(), 'Example', 'generated', 'types.d.ts'),
path.join(process.cwd(), 'Example', '.qts', 'types.d.ts'),
];
for (const typesPath of possiblePaths) {
try {
if (fs.existsSync(typesPath)) {
// Check if types file mentions all our models
const typesContent = fs.readFileSync(typesPath, 'utf-8');
const modelNames = Object.keys(this.ir.models);
const allModelsPresent = modelNames.every(name => typesContent.includes(`export type ${name} =`) ||
typesContent.includes(`export interface ${name}Delegate`));
if (!allModelsPresent) {
return { exists: true, outOfDate: true, path: typesPath };
}
// Check timestamp if schema path is available
if (this.schemaPath && fs.existsSync(this.schemaPath)) {
const schemaStats = fs.statSync(this.schemaPath);
const typesStats = fs.statSync(typesPath);
if (schemaStats.mtime > typesStats.mtime) {
return { exists: true, outOfDate: true, path: typesPath };
}
}
return { exists: true, outOfDate: false, path: typesPath };
}
}
catch (error) {
// Continue to next path
}
}
return { exists: false, outOfDate: false };
}
initializeAdapters() {
const adapters = new Map();
logger_1.logger.debug('runtime', 'Initializing adapters for datasources:', Object.keys(this.ir.datasources));
for (const [name, datasource] of Object.entries(this.ir.datasources)) {
logger_1.logger.debug('runtime', `Creating adapter for datasource '${name}' with provider '${datasource.provider}'`);
if (datasource.provider === 'postgres' || datasource.provider === 'postgresql') {
const url = this.resolveEnvVar(datasource.url);
const adapter = new postgres_1.PostgresAdapter(url);
// Set model metadata for @map support
if (adapter.setModelMetadata) {
adapter.setModelMetadata(this.ir.models);
}
adapters.set(name, adapter);
}
else if (datasource.provider === 'mongodb' || datasource.provider === 'mongo') {
const url = this.resolveEnvVar(datasource.url);
const adapter = new mongodb_1.MongoDBAdapter(url);
// Set model metadata
if (adapter.setModelMetadata) {
adapter.setModelMetadata(this.ir.models);
}
adapters.set(name, adapter);
}
else if (datasource.provider === 'mysql') {
const url = this.resolveEnvVar(datasource.url);
const adapter = new mysql_1.MySQLAdapter(url);
// Set model metadata for @map support
if (adapter.setModelMetadata) {
adapter.setModelMetadata(this.ir.models);
}
adapters.set(name, adapter);
}
else if (datasource.provider === 'mssql' || datasource.provider === 'sqlserver') {
const url = this.resolveEnvVar(datasource.url);
const config = this.parseMSSQLConnectionString(url);
logger_1.logger.debug('runtime', 'MSSQL config:', JSON.stringify(config, null, 2));
// Use ODBC adapter (MSSQLAdapter) for all SQL Server connections
logger_1.logger.debug('runtime', 'Using MSSQLAdapter (ODBC) for SQL Server');
const adapter = new mssql_1.MSSQLAdapter(config);
// Set model metadata for @map support
if (adapter.setModelMetadata) {
adapter.setModelMetadata(this.ir.models);
}
adapters.set(name, adapter);
}
else if (datasource.provider === 'http') {
const baseUrl = datasource.baseUrl;
// For HTTP adapters, collect all models for this datasource
const modelsForDatasource = Object.values(this.ir.models).filter(m => m.datasource === name);
if (modelsForDatasource.length > 0) {
logger_1.logger.debug('runtime', `Creating HTTP adapter for datasource '${name}' with ${modelsForDatasource.length} model(s)`);
for (const model of modelsForDatasource) {
logger_1.logger.debug('runtime', `HTTP Model '${model.name}' endpoints:`, JSON.stringify(model.endpoints, null, 2));
}
// Create one adapter with all models for this datasource
// TODO: Load OAuth hook if configured
const adapter = new http_1.HttpApiAdapter(baseUrl, modelsForDatasource);
adapters.set(name, adapter);
}
}
}
return adapters;
}
parseMSSQLConnectionString(connectionString) {
// Parse connection string like: Server=localhost;Database=mydb;User Id=sa;Password=pass;
// Or for Windows Auth: Server=localhost;Database=mydb;Integrated Security=true;Domain=DOMAIN
logger_1.logger.debug('runtime', 'Parsing connection string:', connectionString);
const parts = connectionString.split(';').reduce((acc, part) => {
const [key, value] = part.split('=');
if (key && value) {
const k = key.trim().toLowerCase();
const v = value.trim();
logger_1.logger.debug('runtime', `Connection string part: ${k} = ${v}`);
acc[k] = v;
}
return acc;
}, {});
logger_1.logger.debug('runtime', 'Parsed connection string parts:', JSON.stringify(parts, null, 2));
const useWindowsAuth = parts['integrated security'] === 'true' ||
parts['integrated security'] === 'sspi' ||
parts['trusted_connection'] === 'true' ||
parts['trusted_connection'] === 'yes';
logger_1.logger.debug('runtime', 'Use Windows Auth?', useWindowsAuth);
const config = {
server: parts['server'] || parts['data source'] || 'localhost',
database: parts['database'] || parts['initial catalog'] || 'master',
options: {
trustedConnection: useWindowsAuth,
trustServerCertificate: parts['trustservercertificate'] === 'true' || true,
encrypt: parts['encrypt'] === 'true' || parts['encrypt'] === '1',
port: parseInt(parts['port'] || '1433')
}
};
if (useWindowsAuth) {
// Windows Authentication - config for native adapter
config.domain = parts['domain'] || '';
// Also keep tedious-style config for backward compatibility
config.authentication = {
type: 'ntlm',
options: {
domain: config.domain,
userName: process.env.USERNAME || '',
password: ''
}
};
}
else {
// SQL Server Authentication
config.authentication = {
type: 'default',
options: {
userName: parts['user id'] || parts['uid'] || 'sa',
password: parts['password'] || parts['pwd'] || ''
}
};
}
return config;
}
resolveEnvVar(value) {
const match = value.match(/env\(["']?(.+?)["']?\)/);
if (match) {
const envVar = process.env[match[1]];
if (!envVar) {
throw new Error(`Environment variable ${match[1]} is not set`);
}
return envVar;
}
return value;
}
/**
* @deprecated Use generate() to create a type-safe client instead
* @example
* const client = orm.generate();
* await client.User.findMany({ ... });
*/
async findMany(model, args = {}) {
console.warn('[DEPRECATED] Direct orm.findMany() is deprecated. Use orm.generate() to create a type-safe client.');
return this.stitcher.findMany(model, args);
}
/**
* @deprecated Use generate() to create a type-safe client instead
*/
async findUnique(model, args) {
console.warn('[DEPRECATED] Direct orm.findUnique() is deprecated. Use orm.generate() to create a type-safe client.');
return this.stitcher.findUnique(model, args);
}
/**
* @deprecated Use generate() to create a type-safe client instead
*/
async count(model, args = {}) {
console.warn('[DEPRECATED] Direct orm.count() is deprecated. Use orm.generate() to create a type-safe client.');
const modelDef = this.ir.models[model];
if (!modelDef)
throw new Error(`Model ${model} not found`);
const adapter = this.adapters.get(modelDef.datasource);
if (!adapter)
throw new Error(`No adapter for datasource ${modelDef.datasource}`);
return adapter.count(model, { where: args.where });
}
/**
* @deprecated Use generate() to create a type-safe client instead
*/
async create(model, args) {
console.warn('[DEPRECATED] Direct orm.create() is deprecated. Use orm.generate() to create a type-safe client.');
const modelDef = this.ir.models[model];
if (!modelDef)
throw new Error(`Model ${model} not found`);
const adapter = this.adapters.get(modelDef.datasource);
if (!adapter)
throw new Error(`No adapter for datasource ${modelDef.datasource}`);
return adapter.create(model, { data: args.data });
}
/**
* @deprecated Use generate() to create a type-safe client instead
*/
async update(model, args) {
console.warn('[DEPRECATED] Direct orm.update() is deprecated. Use orm.generate() to create a type-safe client.');
const modelDef = this.ir.models[model];
if (!modelDef)
throw new Error(`Model ${model} not found`);
const adapter = this.adapters.get(modelDef.datasource);
if (!adapter)
throw new Error(`No adapter for datasource ${modelDef.datasource}`);
return adapter.update(model, { where: args.where, data: args.data });
}
/**
* @deprecated Use generate() to create a type-safe client instead
*/
async delete(model, args) {
console.warn('[DEPRECATED] Direct orm.delete() is deprecated. Use orm.generate() to create a type-safe client.');
const modelDef = this.ir.models[model];
if (!modelDef)
throw new Error(`Model ${model} not found`);
const adapter = this.adapters.get(modelDef.datasource);
if (!adapter)
throw new Error(`No adapter for datasource ${modelDef.datasource}`);
return adapter.delete(model, { where: args.where });
}
/**
* @deprecated Use client.$disconnect() instead
*/
async close() {
console.warn('[DEPRECATED] orm.close() is deprecated. Use client.$disconnect() instead.');
for (const adapter of this.adapters.values()) {
if ('close' in adapter && typeof adapter.close === 'function') {
await adapter.close();
}
}
}
}
exports.ORMClient = ORMClient;
// Export types and utilities
__exportStar(require("./types/ir"), exports);
__exportStar(require("./types/adapter"), exports);
var parser_2 = require("./parser");
Object.defineProperty(exports, "DSLParser", { enumerable: true, get: function () { return parser_2.DSLParser; } });
var postgres_2 = require("./adapters/postgres");
Object.defineProperty(exports, "PostgresAdapter", { enumerable: true, get: function () { return postgres_2.PostgresAdapter; } });
var http_2 = require("./adapters/http");
Object.defineProperty(exports, "HttpApiAdapter", { enumerable: true, get: function () { return http_2.HttpApiAdapter; } });
var mssql_2 = require("./adapters/mssql");
Object.defineProperty(exports, "MSSQLAdapter", { enumerable: true, get: function () { return mssql_2.MSSQLAdapter; } });
var mssql_3 = require("./adapters/mssql");
Object.defineProperty(exports, "MSSQLNativeAdapter", { enumerable: true, get: function () { return mssql_3.MSSQLNativeAdapter; } });
var stitcher_2 = require("./runtime/stitcher");
Object.defineProperty(exports, "Stitcher", { enumerable: true, get: function () { return stitcher_2.Stitcher; } });
var generated_client_2 = require("./client/generated-client");
Object.defineProperty(exports, "GeneratedClient", { enumerable: true, get: function () { return generated_client_2.GeneratedClient; } });
Object.defineProperty(exports, "ModelDelegate", { enumerable: true, get: function () { return generated_client_2.ModelDelegate; } });
var typegen_1 = require("./generator/typegen");
Object.defineProperty(exports, "TypeGenerator", { enumerable: true, get: function () { return typegen_1.TypeGenerator; } });
Object.defineProperty(exports, "generateTypes", { enumerable: true, get: function () { return typegen_1.generateTypes; } });
//# sourceMappingURL=index.js.map