cassandraorm-js
Version:
The most advanced ORM for Apache Cassandra and ScyllaDB with native TypeScript support, AI/ML integration, and enterprise-grade features
389 lines (330 loc) • 11.5 kB
text/typescript
import type { QueryBuilder } from "./query-builder.js";
import type { ModelSchema } from "../core/types.js";
export type ScopeFunction = (...args: any[]) => Record<string, any> | ((query: QueryBuilder) => QueryBuilder);
export type FilterFunction = (query: QueryBuilder, ...args: any[]) => QueryBuilder;
export interface ModelScopes {
[scopeName: string]: ScopeFunction;
}
export interface ModelFilters {
[filterName: string]: FilterFunction;
}
export interface ScopeContext {
modelName: string;
tableName: string;
schema: ModelSchema;
}
export class ScopesManager {
private globalScopes = new Map<string, ScopeFunction>();
private modelScopes = new Map<string, ModelScopes>();
private globalFilters = new Map<string, FilterFunction>();
private modelFilters = new Map<string, ModelFilters>();
// Register global scopes
registerGlobalScope(name: string, scope: ScopeFunction): void {
this.globalScopes.set(name, scope);
}
// Register model-specific scopes
registerModelScopes(modelName: string, scopes: ModelScopes): void {
this.modelScopes.set(modelName, { ...this.modelScopes.get(modelName), ...scopes });
}
// Register global filters
registerGlobalFilter(name: string, filter: FilterFunction): void {
this.globalFilters.set(name, filter);
}
// Register model-specific filters
registerModelFilters(modelName: string, filters: ModelFilters): void {
this.modelFilters.set(modelName, { ...this.modelFilters.get(modelName), ...filters });
}
// Apply scope to query builder
applyScope(
query: QueryBuilder,
scopeName: string,
modelName: string,
args: any[] = []
): QueryBuilder {
// Try model-specific scope first
const modelScopes = this.modelScopes.get(modelName);
if (modelScopes && modelScopes[scopeName]) {
return this.executeScope(query, modelScopes[scopeName], args);
}
// Try global scope
const globalScope = this.globalScopes.get(scopeName);
if (globalScope) {
return this.executeScope(query, globalScope, args);
}
throw new Error(`Scope '${scopeName}' not found for model '${modelName}'`);
}
// Apply filter to query builder
applyFilter(
query: QueryBuilder,
filterName: string,
modelName: string,
args: any[] = []
): QueryBuilder {
// Try model-specific filter first
const modelFilters = this.modelFilters.get(modelName);
if (modelFilters && modelFilters[filterName]) {
return modelFilters[filterName](query, ...args);
}
// Try global filter
const globalFilter = this.globalFilters.get(filterName);
if (globalFilter) {
return globalFilter(query, ...args);
}
throw new Error(`Filter '${filterName}' not found for model '${modelName}'`);
}
private executeScope(query: QueryBuilder, scope: ScopeFunction, args: any[]): QueryBuilder {
const result = scope(...args);
if (typeof result === 'function') {
// Scope returns a function that modifies the query
return result(query);
} else if (typeof result === 'object') {
// Scope returns conditions to apply
for (const [field, value] of Object.entries(result)) {
if (Array.isArray(value)) {
query.whereIn(field, value);
} else if (typeof value === 'object' && value !== null) {
// Handle operators like { $gt: 10 }
for (const [operator, operatorValue] of Object.entries(value)) {
const cassandraOp = this.mapOperator(operator);
query.where(field, cassandraOp, operatorValue);
}
} else {
query.where(field, value);
}
}
return query;
}
return query;
}
private mapOperator(operator: string): string {
const operatorMap: Record<string, string> = {
'$gt': 'gt',
'$gte': 'gte',
'$lt': 'lt',
'$lte': 'lte',
'$ne': 'ne',
'$in': 'in',
'$like': 'like',
'$contains': 'contains'
};
return operatorMap[operator] || 'eq';
}
// Get available scopes for a model
getAvailableScopes(modelName: string): string[] {
const modelScopes = Object.keys(this.modelScopes.get(modelName) || {});
const globalScopes = Array.from(this.globalScopes.keys());
return [...new Set([...modelScopes, ...globalScopes])];
}
// Get available filters for a model
getAvailableFilters(modelName: string): string[] {
const modelFilters = Object.keys(this.modelFilters.get(modelName) || {});
const globalFilters = Array.from(this.globalFilters.keys());
return [...new Set([...modelFilters, ...globalFilters])];
}
// Check if scope exists
hasScope(scopeName: string, modelName: string): boolean {
const modelScopes = this.modelScopes.get(modelName);
return !!(modelScopes?.[scopeName] || this.globalScopes.has(scopeName));
}
// Check if filter exists
hasFilter(filterName: string, modelName: string): boolean {
const modelFilters = this.modelFilters.get(modelName);
return !!(modelFilters?.[filterName] || this.globalFilters.has(filterName));
}
}
// Enhanced Query Builder with scopes and filters
export class ScopedQueryBuilder {
private client: any;
private tableName: string;
private keyspace: string;
private modelName: string;
private scopesManager: ScopesManager;
private schema?: ModelSchema;
protected conditions: any[] = [];
protected joins: any[] = [];
protected options: any = {};
constructor(
client: any,
tableName: string,
keyspace: string,
modelName: string,
scopesManager: ScopesManager,
schema?: ModelSchema
) {
this.client = client;
this.tableName = tableName;
this.keyspace = keyspace;
this.modelName = modelName;
this.scopesManager = scopesManager;
this.schema = schema;
}
// Apply scope
scope(scopeName: string, ...args: any[]): this {
this.scopesManager.applyScope(this as any, scopeName, this.modelName, args);
return this;
}
// Apply filter
filter(filterName: string, ...args: any[]): this {
this.scopesManager.applyFilter(this as any, filterName, this.modelName, args);
return this;
}
// Apply multiple scopes
scopes(scopes: string[] | Record<string, any[]>): this {
if (Array.isArray(scopes)) {
scopes.forEach(scope => this.scope(scope));
} else {
Object.entries(scopes).forEach(([scope, args]) => this.scope(scope, ...args));
}
return this;
}
// Apply multiple filters
filters(filters: string[] | Record<string, any[]>): this {
if (Array.isArray(filters)) {
filters.forEach(filter => this.filter(filter));
} else {
Object.entries(filters).forEach(([filter, args]) => this.filter(filter, ...args));
}
return this;
}
// Conditional scope application
when(condition: boolean | (() => boolean), scopeName: string, ...args: any[]): this {
const shouldApply = typeof condition === 'function' ? condition() : condition;
if (shouldApply) {
this.scope(scopeName, ...args);
}
return this;
}
// Conditional filter application
filterWhen(condition: boolean | (() => boolean), filterName: string, ...args: any[]): this {
const shouldApply = typeof condition === 'function' ? condition() : condition;
if (shouldApply) {
this.filter(filterName, ...args);
}
return this;
}
// Basic query methods
where(field: string, operator?: any, value?: any): this {
// Implementation would be similar to QueryBuilder
return this;
}
async get(): Promise<any[]> {
// Implementation would be similar to QueryBuilder
return [];
}
// Clone with scopes support
clone(): ScopedQueryBuilder {
const cloned = new ScopedQueryBuilder(
this.client,
this.tableName,
this.keyspace,
this.modelName,
this.scopesManager,
this.schema
);
// Copy properties
cloned.conditions = [...this.conditions];
cloned.joins = [...this.joins];
cloned.options = { ...this.options };
return cloned;
}
}
// Common scopes and filters
export const CommonScopes = {
// Time-based scopes
recent: (days: number = 30) => ({
created_at: { $gte: new Date(Date.now() - days * 24 * 60 * 60 * 1000) }
}),
today: () => {
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
return {
created_at: { $gte: today, $lt: tomorrow }
};
},
thisWeek: () => {
const now = new Date();
const startOfWeek = new Date(now.setDate(now.getDate() - now.getDay()));
startOfWeek.setHours(0, 0, 0, 0);
return {
created_at: { $gte: startOfWeek }
};
},
thisMonth: () => {
const now = new Date();
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
return {
created_at: { $gte: startOfMonth }
};
},
// Status-based scopes
active: () => ({ status: 'active' }),
inactive: () => ({ status: 'inactive' }),
published: () => ({ published: true }),
draft: () => ({ published: false }),
// Ordering scopes
latest: () => (query: QueryBuilder) => query.orderBy('created_at', 'DESC'),
oldest: () => (query: QueryBuilder) => query.orderBy('created_at', 'ASC'),
alphabetical: () => (query: QueryBuilder) => query.orderBy('name', 'ASC'),
// Pagination scopes
limit: (count: number) => (query: QueryBuilder) => query.limit(count),
page: (page: number, perPage: number = 15) => (query: QueryBuilder) =>
query.limit(perPage).offset((page - 1) * perPage),
// Search scopes
search: (term: string, fields: string[] = ['name']) => {
const conditions: Record<string, any> = {};
fields.forEach(field => {
conditions[field] = { $like: `%${term}%` };
});
return conditions;
},
// Range scopes
between: (field: string, min: any, max: any) => ({
[field]: { $gte: min, $lte: max }
}),
// Null/Not null scopes
whereNull: (field: string) => ({ [field]: null }),
whereNotNull: (field: string) => ({ [field]: { $ne: null } })
};
export const CommonFilters = {
// Search filter
search: (query: QueryBuilder, term: string, fields: string[] = ['name']) => {
fields.forEach((field, index) => {
if (index === 0) {
query.whereLike(field, `%${term}%`);
} else {
query.orWhere(field, 'like', `%${term}%`);
}
});
return query;
},
// Date range filter
dateRange: (query: QueryBuilder, field: string, start: Date, end: Date) => {
return query.whereBetween(field, start, end);
},
// Status filter
status: (query: QueryBuilder, status: string | string[]) => {
if (Array.isArray(status)) {
return query.whereIn('status', status);
}
return query.where('status', status);
},
// Sorting filter
sort: (query: QueryBuilder, field: string, direction: 'ASC' | 'DESC' = 'ASC') => {
return query.orderBy(field, direction);
},
// Pagination filter
paginate: (query: QueryBuilder, page: number, perPage: number = 15) => {
return query.paginate(page, perPage);
}
};
// Default scopes manager instance
export const scopesManager = new ScopesManager();
// Register common scopes and filters
Object.entries(CommonScopes).forEach(([name, scope]) => {
scopesManager.registerGlobalScope(name, scope);
});
Object.entries(CommonFilters).forEach(([name, filter]) => {
scopesManager.registerGlobalFilter(name, filter);
});