@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
727 lines (580 loc) • 20.2 kB
Markdown
# Multi-Source ORM
A Prisma-like DSL → IR → Type-safe TypeScript client for stitching data from multiple sources (PostgreSQL, MS SQL Server, HTTP APIs).
## ✨ Prisma-Like Workflow
This ORM follows Prisma's architecture: **Schema → Generate → Type-Safe Client**
```typescript
const orm = new ORMClient(schema); // 1. Parse schema
const client = orm.generate(); // 2. Generate client (like `prisma generate`)
const users = await client.User.findMany({ ... }); // 3. Type-safe queries
```
## Features
- **🎯 Type-safe client**: Prisma-like API with full IntelliSense support
- **💾 Multi-datasource**: Query across PostgreSQL, MS SQL Server, HTTP APIs
- **🔐 Windows Authentication**: First-class support for SQL Server Windows Auth (ODBC)
- **🔗 Client-side joins**: Stitch data across different datasources
- **⚡ Smart query planning**: Automatic bulk fetching and concurrency control
- **🔒 Strict/non-strict modes**: Control error handling behavior
- **🔑 OAuth support**: Built-in OAuth with refresh token handling
- **⚙️ Computed fields**: Sync and async field resolvers with timeout control
- **💰 ACID transactions**: Full transaction support with isolation levels
- **🏊 Connection pooling**: Efficient connection management for PostgreSQL and MS SQL Server
- **⚡ Query caching**: In-memory cache with TTL and LRU eviction (10-100x speedup)
- **📊 Configurable limits**: Prevent over-fetching and fan-out explosions
- **🚀 Production-ready**: Handles 200+ concurrent users with 226 req/sec
## Quick Start
### 1. Install Dependencies
```powershell
npm install
```
### 2. Create a Schema
Create a `.qts` schema file (see `orm.qts`):
```typescript
config {
maxIncludeDepth = 5
maxFanOut = 100
strictMode = false
}
datasource main_db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
model User @datasource(main_db) {
id Int @id @default(autoincrement())
email String @unique
name String?
role String
tenant_id Int
created_at DateTime?
}
```
### 3. Generate Types
```bash
npm run generate
```
### 4. Use Type-Safe Client
```typescript
import { ORMClient } from '@aradox/multi-orm';
import * as fs from 'fs';
// Load schema
const schema = fs.readFileSync('./orm.qts', 'utf-8');
// Create ORM and generate client
const orm = new ORMClient(schema, { schemaPath: './orm.qts' });
const db = orm.generate();
// Type-safe queries with IntelliSense
const users = await db.User.findMany({
where: {
role: { eq: 'admin' },
is_active: { eq: true }
},
orderBy: { created_at: 'desc' },
take: 10
});
console.log(users);
// Clean up
await db.$disconnect();
```
### 5. Add Row-Level Security (Optional)
```typescript
import { tenantIsolationMiddleware, rbacMiddleware } from '@aradox/multi-orm/dist/src/middleware/examples';
// Register middleware
db.use(tenantIsolationMiddleware(['User', 'Order', 'Customer']));
db.use(rbacMiddleware({ 'User': ['admin'], 'Order': ['admin', 'user'] }));
// Set context per request
db.setContext({
user: {
id: session.user.id,
tenantId: session.user.tenantId,
role: session.user.role
}
});
// All queries automatically filtered by tenant
const customers = await db.Customer.findMany({});
// SQL: SELECT * FROM customers WHERE tenant_id = {session.user.tenantId}
```
### 6. Use Transactions (Optional)
```typescript
// ACID transactions with automatic commit/rollback
await db.$transaction(async (tx) => {
const customer = await tx.Customer.create({
data: { name: 'John', email: 'john@example.com' }
});
const order = await tx.Orders.create({
data: {
customerId: customer.Id,
total: 100.00,
status: 'pending'
}
});
// Automatic commit if successful
// Automatic rollback on any error
});
```
### 7. Enable Query Caching (Optional)
```typescript
// Configure in-memory cache with TTL and LRU eviction
const orm = new ORMClient(schema, {
schemaPath: './orm.qts',
cache: {
enabled: true,
ttl: 60000, // 60 seconds
maxSize: 1000 // Max 1000 cached queries
}
});
const db = orm.generate();
// First query: Cache miss (45ms)
const users1 = await db.User.findMany({ where: { active: true } });
// Second identical query: Cache hit (0.4ms) - 100x faster!
const users2 = await db.User.findMany({ where: { active: true } });
// Cache automatically invalidated on mutations
await db.User.create({ data: { name: 'Jane' } });
```
## Documentation
📚 **[Complete User Guide](./docs/USER_GUIDE.md)** - Comprehensive guide for using the ORM
### Quick Links
- **[User Guide](./docs/USER_GUIDE.md)** - Complete documentation for developers
- **[Architecture & Performance Guide](./docs/ARCHITECTURE_PERFORMANCE_GUIDE.md)** - Performance benchmarks and best practices
- **[Query Caching Guide](./docs/QUERY_CACHING_GUIDE.md)** - Detailed caching configuration
- **[Nested Queries Explained](./docs/NESTED_QUERIES_EXPLAINED.md)** - How cross-datasource stitching works
- **[Hooks & Middleware Guide](./docs/HOOKS_MIDDLEWARE_GUIDE.md)** - Row-level security and middleware
- **[SQL Logging Guide](./docs/SQL_LOGGING_GUIDE.md)** - Debug SQL queries
- **[CRUD Guide](./docs/CRUD_GUIDE.md)** - CRUD operations reference
- **[Enum Guide](./docs/ENUM_GUIDE.md)** - Type-safe enums reference
- **[AI Coding Guide](./docs/AI_CODING_GUIDE.md)** - Build multi-tenant applications
- **[Quick Reference](./docs/QUICK_REFERENCE.md)** - One-page cheat sheet
### Type-Safe API
All queries are type-safe with full IntelliSense support:
```typescript
// Query methods
await db.User.findMany({ where: { ... } })
await db.User.findUnique({ where: { id: 1 } })
await db.User.findFirst({ where: { ... } })
await db.User.count({ where: { ... } })
// Mutation methods
await db.User.create({ data: { ... } })
await db.User.createMany({ data: [...] })
await db.User.update({ where: { ... }, data: { ... } })
await db.User.updateMany({ where: { ... }, data: { ... } })
await db.User.delete({ where: { ... } })
await db.User.deleteMany({ where: { ... } })
await db.User.upsert({ where: { ... }, create: { ... }, update: { ... } })
// Transaction methods
await db.$transaction(async (tx) => { ... })
await db.$transaction(async (tx) => { ... }, { isolationLevel: 'SERIALIZABLE' })
// Utility methods
await db.$disconnect()
```
## DSL Reference
### Config Block
```
config {
strict = false
limits = {
maxIncludeDepth = 2
maxFanOut = 2000
maxConcurrentRequests = 10
requestTimeoutMs = 10000
postFilterRowLimit = 10000
}
}
```
### Datasources
#### PostgreSQL
```
datasource pg_main {
provider = "postgres"
url = env("DATABASE_URL")
}
```
**Connection String Format:**
```
postgresql://user:password@localhost:5432/database
```
#### MySQL
```
datasource mysql_main {
provider = "mysql"
url = env("MYSQL_CONNECTION_STRING")
}
```
**Connection String Format:**
```
mysql://user:password@localhost:3306/database
```
**Key Features:**
- Supports all MySQL operators and filters
- Uses `mysql2/promise` with connection pooling
- Snake_case convention for table and column names
- Full ACID transaction support with isolation levels
- Configurable pool size (default: 10 connections)
#### MongoDB
```
datasource mongo_main {
provider = "mongodb"
url = env("MONGODB_CONNECTION_STRING")
}
```
**Connection String Format:**
```
mongodb://user:password@localhost:27017/database
mongodb+srv://user:password@cluster.mongodb.net/database
```
**Key Features:**
- NoSQL document database support
- Automatic `_id` to `id` field conversion
- Supports MongoDB query operators ($eq, $in, $regex, etc.)
- Full ACID transaction support (requires replica set)
- Lowercase collection names (e.g., "users", "orderitems")
- Connection pooling with configurable pool size
#### MS SQL Server / T-SQL
```
datasource sqlserver_main {
provider = "mssql"
url = env("MSSQL_CONNECTION_STRING")
}
```
**Connection String Formats:**
*SQL Server Authentication:*
```
Server=localhost;Database=mydb;User Id=sa;Password=yourPassword;Encrypt=true
```
*Windows Authentication:*
```
Server=ServerName;Database=mydb;Integrated Security=true;Domain=DOMAIN;TrustServerCertificate=true
```
**Key Features:**
- Supports all T-SQL operators and filters
- Windows Authentication (Integrated Security) and SQL Server Authentication
- Uses `OUTPUT INSERTED.*` for returning created/updated records
- Handles SQL Server's 2100 parameter limit for IN queries
- Supports `TOP`, `OFFSET`/`FETCH NEXT` pagination
- PascalCase convention for table and column names
**See:** [WINDOWS_AUTH.md](WINDOWS_AUTH.md) for detailed Windows Authentication setup
#### HTTP API
```
datasource crm_api {
provider = "http"
baseUrl = "https://api.example.com"
oauth = {
script: "./auth/crm_oauth.ts",
cacheTtl: "55m",
optional: false
}
}
```
### Enums
Define enums for type-safe string values:
```
enum UserRole {
SUPER_ADMIN
ADMIN
MODERATOR
USER
GUEST
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
```
**Usage in models:**
```
model User @datasource(pg_main) {
id Int @id @default(autoincrement())
email String @unique
role UserRole // Enum field
status UserStatus? // Optional enum field
}
model Order @datasource(pg_main) {
id Int @id
status OrderStatus // Enum field
priority OrderPriority[] // Enum array field (if supported by database)
}
```
**Key Features:**
- Enums are emitted as TypeScript string union types
- Full IntelliSense support in queries and mutations
- Values should be ALL_CAPS (recommended convention)
- Enum names should be PascalCase
- Can be used with optional (`?`) and array (`[]`) modifiers
- Integrated with StringFilter for querying
**Type Generation:**
```typescript
// Generated TypeScript types:
export type UserRole = 'SUPER_ADMIN' | 'ADMIN' | 'MODERATOR' | 'USER' | 'GUEST';
export type OrderStatus = 'PENDING' | 'PROCESSING' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED';
// Used in model types:
export type User = {
id: number;
email: string;
role: UserRole;
status?: UserStatus | null;
};
```
**Query Examples:**
```typescript
// Type-safe enum filtering
const admins = await db.User.findMany({
where: { role: { eq: 'ADMIN' } } // IntelliSense shows available enum values
});
// Enum array filtering
const orders = await db.Order.findMany({
where: {
status: { in: ['PENDING', 'PROCESSING'] } // Type-safe array of enum values
}
});
// Create with enum values
const user = await db.User.create({
data: {
email: 'admin@example.com',
role: 'SUPER_ADMIN' // IntelliSense autocomplete
}
});
```
### Models
```
model User @datasource(pg_main) {
id Int @id @default(autoincrement())
email String @unique
name String?
role UserRole // Enum field
createdAt DateTime @default(now())
displayName String @computed(resolver: "./resolvers/user.displayName.ts", async: false)
lead Lead? @relation(fields: [email], references: [email], strategy: "lookup")
}
```
### HTTP Endpoints
```
model Lead @datasource(crm_api) {
id String @id
email String
status String
@endpoint(findMany: {
method: "GET",
path: "/leads",
query: { status: "$where.status.eq?", offset: "$skip", limit: "$take" },
response: { items: "$.data.items", total: "$.data.total" }
})
@endpoint(findManyBulkById: {
method: "POST",
path: "/leads/bulk",
body: { ids: "$where.id.in" },
response: { items: "$.data" }
})
}
```
## Query Examples
### Basic Query
```typescript
const users = await orm.findMany('User', {
where: { email: { contains: '@acme.com' } },
take: 100
});
```
### Cross-Datasource Join
```typescript
const users = await orm.findMany('User', {
where: { email: { contains: '@acme.com' } },
include: {
lead: {
where: { status: 'new' },
select: { id: true, status: true }
}
}
});
```
### Strict Mode
```typescript
const users = await orm.findMany('User', {
include: { lead: true },
$options: {
strict: true,
limits: { maxFanOut: 1000 }
}
});
```
## OAuth Hooks
Create an OAuth hook script (see `auth/crm_oauth.ts`):
```typescript
import type { OAuthContext, OAuthResult } from '../src/types/adapter';
export default async function auth(ctx: OAuthContext): Promise<OAuthResult> {
const accessToken = await getAccessToken();
return {
headers: { Authorization: `Bearer ${accessToken}` },
expiresAt: Date.now() + 3600000,
refresh: async (reason) => {
const newToken = await refreshToken();
return {
headers: { Authorization: `Bearer ${newToken}` },
expiresAt: Date.now() + 3600000
};
}
};
}
```
## Computed Fields
Create a resolver (see `resolvers/user.displayName.ts`):
```typescript
import type { ComputedContext } from '../src/types/adapter';
// Synchronous computed field
export default function displayName(ctx: ComputedContext): string {
const { row } = ctx;
return row.name || row.email.split('@')[0];
}
```
**Async computed fields with I/O:**
```typescript
// In your schema:
model User {
id Int @id
email String
enrichedData Json @computed(
resolver: "./resolvers/user.enrichedData.ts",
async: true,
io: true,
timeout: 5000,
cache: true
)
}
// In resolvers/user.enrichedData.ts:
export default async function enrichedData(ctx: ComputedContext): Promise<any> {
const response = await fetch(`https://api.example.com/enrich?email=${ctx.row.email}`);
return response.json();
}
```
## VS Code Extension
**🎨 Syntax highlighting, IntelliSense, and validation for `.qts` files!**
### Features
- **Syntax Highlighting**: Full color coding for keywords, types, attributes
- **IntelliSense**: Smart autocomplete for models, fields, datasources, enums
- **Code Snippets**: 20+ snippets for rapid schema development
- **Real-time Validation**: Instant error detection and warnings
- **Navigation**: Go to definition, hover docs, document outline
### Installation
```powershell
cd vscode-extension
npm install
npm run compile
```
Then press `F5` in VS Code to launch the Extension Development Host.
**See:** [vscode-extension/QUICKSTART.md](./vscode-extension/QUICKSTART.md) for full documentation
## Build & Test
```powershell
# Build
npm run build
# Run tests
npm test
# Watch mode
npm run dev
```
## Adapters
### PostgreSQL Adapter
- Uses `pg` library with connection pooling
- Snake_case naming convention
- Supports all PostgreSQL operators
- Max IN clause: 65,000 parameters
- Full ACID transaction support with isolation levels
- Configurable pool size (default: max 10, min 2)
### MySQL Adapter
- Uses `mysql2` library with connection pooling
- Snake_case naming convention
- Supports all MySQL operators
- Max IN clause: 65,000 parameters
- Full ACID transaction support with isolation levels (READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE)
- Configurable pool size (default: limit 10)
- Connection via connection strings or config object
### MongoDB Adapter
- Uses `mongodb` native driver with connection pooling
- Lowercase collection names (e.g., "users", "orderitems")
- Supports MongoDB query operators
- Max array size: 100,000+ elements
- Full ACID transaction support (requires replica set)
- Configurable pool size (default: max 10, min 0)
- Automatic `_id` to `id` conversion for consistency with SQL adapters
### MS SQL Server Adapter
- Uses `odbc` library (MSSQL Native) with connection pooling
- PascalCase naming convention
- Supports T-SQL operators and filters
- Max IN clause: 2,100 parameters (SQL Server limit)
- Uses `OUTPUT INSERTED.*` / `OUTPUT DELETED.*`
- Full ACID transaction support with isolation levels
- Windows Authentication and SQL Server Authentication
- Configurable pool size (default: max 10, min 2)
- Connection via standard connection strings or ODBC DSN
### HTTP API Adapter
- Uses `axios` for requests
- Configurable endpoint mappings
- OAuth 2.0 with refresh token support
- Automatic retry on 401
- Bulk endpoint detection
- Rate limiting and backoff
## Architecture
1. **DSL Parser** (`src/parser/`) - Parses `.schema` files into IR
2. **IR** (`src/types/ir.ts`) - Intermediate representation (JSON)
3. **Adapters** (`src/adapters/`) - Database/API specific implementations
- PostgreSQL (`postgres.ts`)
- MS SQL Server (`mssql.ts`)
- HTTP APIs (`http.ts`)
4. **Stitcher** (`src/runtime/stitcher.ts`) - Cross-datasource join engine
5. **Type Generator** (planned) - Generate TypeScript client types
## Performance Benchmarks
**Test Environment:** Intel Core i7, 16GB RAM, SQL Server 2022 (localhost)
| Test | Performance | Notes |
|------|-------------|-------|
| **Query (no cache)** | 439 queries/sec | 2.27ms avg |
| **Query (with cache)** | 4,545 queries/sec | 0.21ms avg, **10.8x speedup** |
| **50 Concurrent Queries** | 213 queries/sec | Connection pooling |
| **Cross-Datasource** | 32 queries/sec | Includes HTTP API calls |
| **Nested Includes (3 levels)** | 1,667 queries/sec | Smart bulk fetching |
| **Write Operations** | 472 creates/sec | With automatic cache invalidation |
| **Transactions** | 188 tx/sec | 5.23ms avg with ACID guarantees |
| **Count Operations** | 962 counts/sec | Optimized COUNT queries |
| **200 Concurrent Users** | 226 req/sec | **100% success rate** |
| **Memory Usage** | 7.98 MB | For 500 cached queries |
**Key Findings:**
- ✅ Cache provides **10.8x speedup** with 99% hit rate
- ✅ Handles **200 concurrent users** with zero failures
- ✅ **472 writes/second** throughput
- ✅ **188 transactions/second** with full ACID compliance
- ✅ Memory-efficient: only **7.98 MB** for 500 queries
**See:** [ARCHITECTURE_PERFORMANCE_GUIDE.md](./docs/ARCHITECTURE_PERFORMANCE_GUIDE.md) for detailed benchmarks
## Roadmap
### Completed ✅
- [x] DSL parser with error reporting
- [x] IR validation
- [x] PostgreSQL adapter with connection pooling
- [x] MySQL adapter with connection pooling
- [x] MongoDB adapter with connection pooling
- [x] MS SQL Server adapter (Windows Auth + SQL Auth)
- [x] HTTP API adapter with OAuth 2.0
- [x] OAuth hook runner with refresh tokens
- [x] Stitcher with fan-out control
- [x] Type generator with IntelliSense support
- [x] Full CRUD operations (create, read, update, delete)
- [x] Row-level security middleware
- [x] RBAC (Role-Based Access Control)
- [x] Audit logging middleware
- [x] Multi-datasource support (SQL + NoSQL + HTTP)
- [x] Cross-datasource joins
- [x] SQL query logging
- [x] Comprehensive documentation
- [x] Database-level RLS setup scripts
- [x] **Computed fields executor** (sync/async with timeout and caching)
- [x] **Transaction support** (ACID with isolation levels for PostgreSQL, MySQL, MongoDB, MSSQL)
- [x] **Connection pooling** (PostgreSQL, MySQL, MongoDB, MS SQL Server)
- [x] **Query result caching** (in-memory with TTL/LRU eviction)
- [x] **Performance benchmarks** (200+ concurrent users, 226 req/sec)
- [x] **Enum support** (type-safe string unions with IntelliSense)
### In Progress 🚧
- [ ] Redis-based distributed caching
- [ ] Deadlock retry with exponential backoff
### Planned 📋
- [ ] GraphQL adapter
- [ ] CLI tools (migrate, seed, studio)
- [ ] Schema migrations
- [ ] Visual studio extension
- [ ] Performance profiling tools
## License
MIT