Hooks & Lifecycle Events
Use hooks and events to run custom logic at key points in the data lifecycle
Available Hooks
Stabilize supports these lifecycle hooks:
hooks/types.ts
// Available hook types:// - beforeCreate - Before a record is created// - afterCreate - After a record is created// - beforeUpdate - Before a record is updated// - afterUpdate - After a record is updated// - beforeDelete - Before a record is deleted// - afterDelete - After a record is deleted// - beforeSave - Before create OR update// - afterSave - After create OR updateEvent Listeners
Listen to ORM events for logging and monitoring:
examples/events.ts
import { Stabilize, StabilizeEmitter } from "stabilize-orm";
// connection:open fires from the constructor, before a handler registered on// orm.events afterwards could exist. Build the emitter, subscribe, then pass it// as the fifth argument.const events = new StabilizeEmitter();
events.on("connection:open", (dbType) => { console.log(`Connected to ${dbType}`);});
const orm = new Stabilize( dbConfig, { enabled: false, ttl: 60 }, {}, undefined, // no shared client events,);
orm.events.on("connection:close", () => { console.log("Database connection closed");});
// Every other event can be subscribed to normally, in any order.orm.events.on("query", ({ dbType, query, params, executionTime }) => { console.log(`${dbType} ${query} in ${executionTime}ms`, params);});
// "phase" says which of the three sources raised it: "query", "transaction"// or "migration".orm.events.on("error", ({ phase, error }) => { console.error(`Database error during ${phase}:`, error);});
orm.events.on("transaction:start", ({ dbType }) => { console.log(`Transaction started on ${dbType}`);});
orm.events.on("transaction:complete", ({ dbType }) => { console.log(`Transaction committed on ${dbType}`);});
orm.events.on("transaction:error", ({ dbType, error }) => { console.error(`Transaction rolled back on ${dbType}:`, error);});
orm.events.on("migration:start", ({ name, index, total }) => { console.log(`Migration ${index}/${total} started: ${name}`);});
orm.events.on("migration:complete", ({ name, index, total }) => { console.log(`Migration ${index}/${total} complete: ${name}`);});Practical Hook Patterns
examples/hook-patterns.ts
// Logging pattern - log all create operationsconst userRepo = orm.getRepository(User);const originalCreate = userRepo.create.bind(userRepo);
userRepo.create = async (entity, options) => { console.log("Creating user:", entity.email); const result = await originalCreate(entity, options); console.log("Created user with ID:", result.id); return result;};
// Validation pattern - extra validation before saveconst orderRepo = orm.getRepository(Order);const originalUpdate = orderRepo.update.bind(orderRepo);
orderRepo.update = async (id, entity) => { if (entity.status === "shipped" && !entity.trackingNumber) { throw new Error("Tracking number required for shipped orders"); } return originalUpdate(id, entity);};
// Audit pattern - track who made changesconst postRepo = orm.getRepository(Post);orm.events.on("query", (entry) => { if (entry.query.includes("UPDATE") || entry.query.includes("INSERT")) { console.log("Audit:", { query: entry.query, timestamp: new Date().toISOString(), duration: entry.durationMs, }); }});Middleware Pattern
examples/middleware.ts
// Create a middleware wrapper for repositoriesfunction withLogging<T>(repo: any, tableName: string) { const wrap = (method: string) => { const original = repo[method].bind(repo); repo[method] = async (...args: any[]) => { const start = performance.now(); const result = await original(...args); const ms = (performance.now() - start).toFixed(1); console.log(`[${tableName}] ${method} completed in ${ms}ms`); return result; }; };
["create", "update", "delete", "findOne"].forEach(wrap); return repo;}
// Usageconst userRepo = withLogging(orm.getRepository(User), "users");const postRepo = withLogging(orm.getRepository(Post), "posts");
// Now all operations are automatically loggedawait userRepo.create({ id: "1", name: "Test" });// [users] create completed in 2.3ms