Plugin System
SlintORM's plugin system lets you hook into the ORM lifecycle — before and after every query, insert, update, delete, and migration. Plugins are registered with orm.use() and receive an event context they can inspect or mutate.
Plugin interface
A plugin is an object with a name, an install() method, an optional on() handler, and an optional priority.
// Plugin interface
interface SlintORMPlugin {
name: string;
install(orm: OrmInstance): void | Promise<void>;
on?(event: string, ctx: EventContext): void | Promise<void>;
priority?: number; // lower runs first, default 100
}
// Event types:
// beforeQuery afterQuery
// beforeInsert afterInsert
// beforeUpdate afterUpdate
// beforeDelete afterDelete
// beforeMigrate afterMigrate
// EventContext:
interface EventContext {
model?: ModelDefinition;
table?: string;
data?: Record<string, unknown>;
filter?: Record<string, unknown>;
query?: string;
params?: unknown[];
duration?: number; // populated for after* events
}Registering plugins
Call orm.use(plugin) to register a plugin. Plugins with lower priority values run first.
import { Orm } from 'slintorm';
const orm = new Orm({ /* config */ });
// Register a plugin
orm.use({
name: 'audit-log',
priority: 10,
on(event, ctx) {
if (event.startsWith('after')) {
console.log(`[${event}] ${ctx.table}`, ctx.data ?? ctx.filter);
}
},
});
// Plugins run in priority order (lower number first)
// orm.use() can be called before or after defining modelsExample: audit logging
Log every insert, update, and delete to an audit table.
// Example — audit logging plugin
const auditPlugin = {
name: 'audit-log',
priority: 20,
install(orm) {
// One-time setup — create audit table, register listeners, etc.
console.log('Audit plugin installed');
},
async on(event, ctx) {
// Only log mutating events
const mutating = ['afterInsert', 'afterUpdate', 'afterDelete'];
if (!mutating.includes(event)) return;
await AuditLog.insert({
table: ctx.table,
event,
modelName: ctx.model?.name ?? 'unknown',
data: JSON.stringify(ctx.data ?? ctx.filter),
timestamp: new Date().toISOString(),
});
},
};
orm.use(auditPlugin);Example: query timing
Measure query duration and emit metrics to your monitoring system.
// Example — query timing plugin
const timingPlugin = {
name: 'query-timing',
priority: 5, // run early for before*, late for after*
on(event, ctx) {
if (event === 'beforeQuery') {
// Store start time on context
ctx.__start = Date.now();
}
if (event === 'afterQuery') {
const elapsed = Date.now() - (ctx.__start ?? Date.now());
console.log(`[TIMING] ${ctx.table} — ${elapsed}ms`);
// Emit metric to monitoring system
metrics.timing('slintorm.query', elapsed, {
table: ctx.table,
operation: ctx.query?.split(' ')[0], // SELECT, INSERT, etc.
});
}
},
};
orm.use(timingPlugin);Example: data transformation
Automatically set tenant IDs, trim strings, or enforce field conventions before writes and queries.
// Example — data transformation plugin
const transformPlugin = {
name: 'data-transform',
priority: 15,
on(event, ctx) {
if (event === 'beforeInsert' || event === 'beforeUpdate') {
// Auto-set tenant ID on every mutation
if (ctx.data && !ctx.data.tenantId) {
ctx.data.tenantId = getCurrentTenant();
}
// Trim string fields
if (ctx.data) {
for (const [key, val] of Object.entries(ctx.data)) {
if (typeof val === 'string') {
ctx.data[key] = val.trim();
}
}
}
}
if (event === 'beforeQuery') {
// Auto-filter by tenant for multi-tenant apps
if (ctx.filter && !ctx.filter.tenantId) {
ctx.filter.tenantId = getCurrentTenant();
}
}
},
};
orm.use(transformPlugin);Lifecycle and cleanup
install() runs once at registration. on() runs for every matching event. Before-events can mutate context to affect the operation; after-events are read-only.
// Plugin lifecycle
// install() — called once when orm.use(plugin) is registered
// Use for: creating tables, setting up connections, validation
// on(event, ctx) — called for every matching event
// before* events: mutate ctx.data / ctx.filter to affect the operation
// after* events: read-only observation (ctx.duration available)
// Cleanup — remove a plugin (not yet implemented, track via issue)
// Future: orm.unuse('plugin-name') or plugin.uninstall()
// Plugins are called synchronously in priority order.
// If on() returns a Promise, the ORM awaits it before proceeding.
// A rejected promise in a before* event will abort the operation.