cassandraorm-js
Version:
The most advanced ORM for Apache Cassandra and ScyllaDB with native TypeScript support, AI/ML integration, and enterprise-grade features
376 lines (304 loc) • 10.4 kB
text/typescript
export type HookFunction = (data: any, context?: HookContext) => Promise<any> | any;
export type MiddlewareFunction = (data: any, next: () => Promise<any>) => Promise<any>;
export interface HookContext {
operation: 'create' | 'update' | 'delete' | 'find' | 'save';
tableName: string;
modelName: string;
originalData?: any;
conditions?: any;
instance?: any;
isNew?: boolean;
}
export interface ModelHooks {
// Lifecycle hooks
beforeSave?: HookFunction;
afterSave?: HookFunction;
beforeCreate?: HookFunction;
afterCreate?: HookFunction;
beforeUpdate?: HookFunction;
afterUpdate?: HookFunction;
beforeDelete?: HookFunction;
afterDelete?: HookFunction;
beforeFind?: HookFunction;
afterFind?: HookFunction;
// Validation hooks
beforeValidation?: HookFunction;
afterValidation?: HookFunction;
// Custom hooks
[key: string]: HookFunction | undefined;
}
export class HooksManager {
private globalHooks = new Map<string, HookFunction[]>();
private modelHooks = new Map<string, ModelHooks>();
private middleware = new Map<string, MiddlewareFunction[]>();
// Register global hooks
on(event: string, hook: HookFunction): void {
if (!this.globalHooks.has(event)) {
this.globalHooks.set(event, []);
}
this.globalHooks.get(event)!.push(hook);
}
// Remove global hooks
off(event: string, hook?: HookFunction): void {
if (!hook) {
this.globalHooks.delete(event);
return;
}
const eventHooks = this.globalHooks.get(event);
if (eventHooks) {
const index = eventHooks.indexOf(hook);
if (index > -1) {
eventHooks.splice(index, 1);
}
}
}
// Register model-specific hooks
registerModelHooks(modelName: string, hooks: ModelHooks): void {
this.modelHooks.set(modelName, { ...this.modelHooks.get(modelName), ...hooks });
}
// Register middleware
use(event: string, middleware: MiddlewareFunction): void {
if (!this.middleware.has(event)) {
this.middleware.set(event, []);
}
this.middleware.get(event)!.push(middleware);
}
// Execute hooks for an event
async execute(event: string, data: any, context?: HookContext): Promise<any> {
let result = data;
// Execute middleware first
result = await this.executeMiddleware(event, result);
// Execute global hooks
result = await this.executeGlobalHooks(event, result, context);
// Execute model-specific hooks
if (context?.modelName) {
result = await this.executeModelHooks(context.modelName, event, result, context);
}
return result;
}
private async executeMiddleware(event: string, data: any): Promise<any> {
const middlewares = this.middleware.get(event);
if (!middlewares || middlewares.length === 0) {
return data;
}
let result = data;
let index = 0;
const next = async (): Promise<any> => {
if (index >= middlewares.length) {
return result;
}
const middleware = middlewares[index++];
return middleware(result, next);
};
return next();
}
private async executeGlobalHooks(event: string, data: any, context?: HookContext): Promise<any> {
const eventHooks = this.globalHooks.get(event);
if (!eventHooks || eventHooks.length === 0) {
return data;
}
let result = data;
for (const hook of eventHooks) {
try {
const hookResult = await hook(result, context);
if (hookResult !== undefined) {
result = hookResult;
}
} catch (error) {
console.error(`Error in global hook for event ${event}:`, error);
throw error;
}
}
return result;
}
private async executeModelHooks(modelName: string, event: string, data: any, context?: HookContext): Promise<any> {
const modelHooks = this.modelHooks.get(modelName);
if (!modelHooks) {
return data;
}
const hook = modelHooks[event];
if (!hook) {
return data;
}
try {
const hookResult = await hook(data, context);
return hookResult !== undefined ? hookResult : data;
} catch (error) {
console.error(`Error in model hook ${event} for ${modelName}:`, error);
throw error;
}
}
// Lifecycle hook helpers
async beforeSave(modelName: string, instance: any, context: HookContext): Promise<any> {
context.operation = 'save';
context.instance = instance;
return this.execute('beforeSave', instance, context);
}
async afterSave(modelName: string, instance: any, context: HookContext): Promise<any> {
context.operation = 'save';
context.instance = instance;
return this.execute('afterSave', instance, context);
}
async beforeCreate(modelName: string, data: any, context: HookContext): Promise<any> {
context.operation = 'create';
context.isNew = true;
return this.execute('beforeCreate', data, context);
}
async afterCreate(modelName: string, instance: any, context: HookContext): Promise<any> {
context.operation = 'create';
context.instance = instance;
return this.execute('afterCreate', instance, context);
}
async beforeUpdate(modelName: string, data: any, context: HookContext): Promise<any> {
context.operation = 'update';
context.isNew = false;
return this.execute('beforeUpdate', data, context);
}
async afterUpdate(modelName: string, instance: any, context: HookContext): Promise<any> {
context.operation = 'update';
context.instance = instance;
return this.execute('afterUpdate', instance, context);
}
async beforeDelete(modelName: string, conditions: any, context: HookContext): Promise<any> {
context.operation = 'delete';
context.conditions = conditions;
return this.execute('beforeDelete', conditions, context);
}
async afterDelete(modelName: string, result: any, context: HookContext): Promise<any> {
context.operation = 'delete';
return this.execute('afterDelete', result, context);
}
async beforeFind(modelName: string, query: any, context: HookContext): Promise<any> {
context.operation = 'find';
return this.execute('beforeFind', query, context);
}
async afterFind(modelName: string, results: any, context: HookContext): Promise<any> {
context.operation = 'find';
return this.execute('afterFind', results, context);
}
// Validation hooks
async beforeValidation(modelName: string, data: any, context: HookContext): Promise<any> {
return this.execute('beforeValidation', data, context);
}
async afterValidation(modelName: string, data: any, context: HookContext): Promise<any> {
return this.execute('afterValidation', data, context);
}
// Custom event execution
async trigger(event: string, data: any, context?: HookContext): Promise<any> {
return this.execute(event, data, context);
}
// Get registered hooks for debugging
getGlobalHooks(): Map<string, HookFunction[]> {
return new Map(this.globalHooks);
}
getModelHooks(modelName: string): ModelHooks | undefined {
return this.modelHooks.get(modelName);
}
// Clear hooks
clearGlobalHooks(): void {
this.globalHooks.clear();
}
clearModelHooks(modelName?: string): void {
if (modelName) {
this.modelHooks.delete(modelName);
} else {
this.modelHooks.clear();
}
}
clearMiddleware(): void {
this.middleware.clear();
}
// Hook composition helpers
compose(...hooks: HookFunction[]): HookFunction {
return async (data: any, context?: HookContext) => {
let result = data;
for (const hook of hooks) {
result = await hook(result, context);
}
return result;
};
}
// Conditional hooks
when(condition: (data: any, context?: HookContext) => boolean, hook: HookFunction): HookFunction {
return async (data: any, context?: HookContext) => {
if (condition(data, context)) {
return hook(data, context);
}
return data;
};
}
// Async hook helpers
parallel(...hooks: HookFunction[]): HookFunction {
return async (data: any, context?: HookContext) => {
const results = await Promise.all(hooks.map(hook => hook(data, context)));
return results[results.length - 1] || data;
};
}
sequence(...hooks: HookFunction[]): HookFunction {
return this.compose(...hooks);
}
}
// Default hooks manager instance
export const hooksManager = new HooksManager();
// Decorator for adding hooks to models
export function Hook(event: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const context: HookContext = {
operation: event as any,
tableName: this.constructor.tableName || this.constructor.name.toLowerCase(),
modelName: this.constructor.name
};
// Execute before hook
await hooksManager.execute(`before${event.charAt(0).toUpperCase() + event.slice(1)}`, args[0], context);
// Execute original method
const result = await originalMethod.apply(this, args);
// Execute after hook
await hooksManager.execute(`after${event.charAt(0).toUpperCase() + event.slice(1)}`, result, context);
return result;
};
};
}
// Common hook utilities
export const HookUtils = {
// Timestamp hooks
timestamps: {
beforeSave: (data: any) => {
const now = new Date();
if (!data.created_at) data.created_at = now;
data.updated_at = now;
return data;
}
},
// Soft delete hooks
softDelete: {
beforeDelete: (data: any) => {
return { ...data, deleted_at: new Date() };
},
beforeFind: (query: any) => {
if (!query.deleted_at) {
query.deleted_at = null;
}
return query;
}
},
// Validation hooks
validation: {
beforeSave: (data: any, context?: HookContext) => {
// Add validation logic here
return data;
}
},
// Logging hooks
logging: {
afterSave: (instance: any, context?: HookContext) => {
console.log(`${context?.modelName} saved:`, instance.id);
return instance;
},
afterDelete: (result: any, context?: HookContext) => {
console.log(`${context?.modelName} deleted`);
return result;
}
}
};