metal-orm 1.1.21 → 1.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type { CompiledQuery } from '../abstract.js';
3
+
4
+ export interface CompiledProcedureCall extends CompiledQuery {
5
+ outParams: {
6
+ source: 'none' | 'firstResultSet' | 'lastResultSet';
7
+ names: string[];
8
+ };
9
+ }
10
+
11
+ /**
12
+ * Optional dialect capability for stored-procedure compilation.
13
+ *
14
+ * Dialects that do not support procedures simply do not implement this
15
+ * interface; unsupported behavior is resolved at the capability boundary
16
+ * rather than through mandatory methods that only throw.
17
+ */
18
+ export interface ProcedureCompiler {
19
+ compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall;
20
+ }
21
+
22
+ export const isProcedureCompiler = (value: unknown): value is ProcedureCompiler =>
23
+ typeof (value as { compileProcedureCall?: unknown } | null)?.compileProcedureCall === 'function';
24
+
25
+ export const requireProcedureCompiler = (value: unknown): ProcedureCompiler => {
26
+ if (!isProcedureCompiler(value)) {
27
+ throw new Error('Stored procedures are not supported by this dialect.');
28
+ }
29
+ return value;
30
+ };
@@ -1,7 +1,7 @@
1
1
  // Dialect factory for the SQL DSL.
2
2
  // Centralizes how we go from a symbolic name ("sqlite") to a concrete Dialect instance.
3
3
 
4
- import { Dialect } from './abstract.js';
4
+ import type { Dialect } from './abstract.js';
5
5
  import { PostgresDialect } from './postgres/index.js';
6
6
  import { MySqlDialect } from './mysql/index.js';
7
7
  import { SqliteDialect } from './sqlite/index.js';
@@ -42,9 +42,8 @@ export class DialectFactory {
42
42
  /**
43
43
  * Register (or override) a dialect factory for a key.
44
44
  *
45
- * Examples:
46
- * DialectFactory.register('sqlite', () => new SqliteDialect());
47
- * DialectFactory.register('my-tenant-dialect', () => new CustomDialect());
45
+ * Implementations are structural: extending DialectBase/SqlDialectBase is
46
+ * optional. A composed object satisfying Dialect is a valid registration.
48
47
  */
49
48
  public static register(key: DialectKey, factory: DialectFactoryFn): void {
50
49
  this.registry.set(key, factory);
@@ -1,4 +1,5 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
2
3
  import {
3
4
  SelectQueryNode,
4
5
  InsertQueryNode,
@@ -21,7 +22,7 @@ const toProcedureParamReference = (value: string): string =>
21
22
  /**
22
23
  * Microsoft SQL Server dialect implementation
23
24
  */
24
- export class SqlServerDialect extends SqlDialectBase {
25
+ export class SqlServerDialect extends SqlDialectBase implements ProcedureCompiler {
25
26
  protected readonly dialect = 'mssql';
26
27
  /**
27
28
  * Creates a new SqlServerDialect instance
@@ -1,4 +1,5 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
2
3
  import { JsonPathNode, IsDistinctExpressionNode } from '../../ast/expression.js';
3
4
  import { InsertQueryNode } from '../../ast/query.js';
4
5
  import { SqlDialectBase } from '../base/sql-dialect.js';
@@ -11,7 +12,7 @@ const sanitizeVariableSuffix = (value: string): string =>
11
12
  /**
12
13
  * MySQL dialect implementation
13
14
  */
14
- export class MySqlDialect extends SqlDialectBase {
15
+ export class MySqlDialect extends SqlDialectBase implements ProcedureCompiler {
15
16
  protected readonly dialect = 'mysql';
16
17
  /**
17
18
  * Creates a new MySqlDialect instance
@@ -1,4 +1,5 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
2
3
  import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
3
4
  import { InsertQueryNode, TableNode } from '../../ast/query.js';
4
5
  import { SqlDialectBase } from '../base/sql-dialect.js';
@@ -9,7 +10,7 @@ import { ProcedureCallNode } from '../../ast/procedure.js';
9
10
  /**
10
11
  * PostgreSQL dialect implementation
11
12
  */
12
- export class PostgresDialect extends SqlDialectBase {
13
+ export class PostgresDialect extends SqlDialectBase implements ProcedureCompiler {
13
14
  protected readonly dialect = 'postgres';
14
15
  /**
15
16
  * Creates a new PostgresDialect instance
@@ -1,9 +1,8 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
2
  import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
3
3
  import { InsertQueryNode, TableNode } from '../../ast/query.js';
4
4
  import { SqlDialectBase } from '../base/sql-dialect.js';
5
5
  import { SqliteFunctionStrategy } from './functions.js';
6
- import { ProcedureCallNode } from '../../ast/procedure.js';
7
6
 
8
7
  /**
9
8
  * SQLite dialect implementation
@@ -104,9 +103,4 @@ export class SqliteDialect extends SqlDialectBase {
104
103
  supportsDmlReturningClause(): boolean {
105
104
  return true;
106
105
  }
107
-
108
- compileProcedureCall(_ast: ProcedureCallNode): CompiledProcedureCall {
109
- void _ast;
110
- throw new Error('Stored procedures are not supported by the SQLite dialect.');
111
- }
112
106
  }
@@ -1,4 +1,3 @@
1
- import { TableHooks } from '../schema/table.js';
2
1
  import { RelationKinds } from '../schema/relation.js';
3
2
  import {
4
3
  addColumnMetadata,
@@ -15,7 +14,6 @@ import { syncTreeEntityMetadata } from '../tree/tree-decorator.js';
15
14
  */
16
15
  export interface EntityOptions {
17
16
  tableName?: string;
18
- hooks?: TableHooks;
19
17
  /** Entity type: 'table' (default) or 'view'. Views are read-only. */
20
18
  type?: 'table' | 'view';
21
19
  }
@@ -42,6 +40,7 @@ const deriveTableNameFromConstructor = (ctor: EntityConstructor<unknown>): strin
42
40
 
43
41
  /**
44
42
  * Class decorator to mark a class as an entity and configure its table mapping.
43
+ * Runtime lifecycle hooks are registered on OrmSession, not entity metadata.
45
44
  * @param options - Configuration options for the entity.
46
45
  * @returns A class decorator that registers the entity metadata.
47
46
  */
@@ -52,7 +51,7 @@ export function Entity(options: EntityOptions = {}) {
52
51
  ): T {
53
52
  const ctor = value;
54
53
  const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
55
- setEntityTableName(ctor, tableName, options.hooks, options.type);
54
+ setEntityTableName(ctor, tableName, options.type);
56
55
 
57
56
  const bag = context ? readMetadataBag(context) : readMetadataBagFromConstructor(ctor);
58
57
  if (bag) {
package/src/index.ts CHANGED
@@ -17,6 +17,9 @@ export * from './core/ast/expression.js';
17
17
  export * from './core/ast/procedure.js';
18
18
  export * from './core/ast/window-functions.js';
19
19
  export * from './core/hydration/types.js';
20
+ export * from './core/dialect/abstract.js';
21
+ export * from './core/dialect/dialect-factory.js';
22
+ export * from './core/dialect/capabilities/procedure-compiler.js';
20
23
  export * from './core/dialect/mysql/index.js';
21
24
  export * from './core/dialect/mssql/index.js';
22
25
  export * from './core/dialect/sqlite/index.js';
@@ -38,6 +41,7 @@ export * from './orm/hydration.js';
38
41
  export * from './codegen/typescript.js';
39
42
  export * from './orm/orm-session.js';
40
43
  export * from './orm/orm.js';
44
+ export * from './orm/lifecycle.js';
41
45
  export * from './orm/column-introspection.js';
42
46
  export * from './orm/entity.js';
43
47
  export * from './orm/lazy-batch.js';
@@ -1,5 +1,5 @@
1
1
  import { ColumnDef } from '../schema/column-types.js';
2
- import { defineTable, TableDef, TableHooks } from '../schema/table.js';
2
+ import { defineTable, TableDef } from '../schema/table.js';
3
3
  import { CascadeMode, RelationKinds } from '../schema/relation.js';
4
4
  import type { TransformerMetadata } from '../decorators/transformers/transformer-metadata.js';
5
5
 
@@ -153,7 +153,7 @@ export interface MorphManyRelationMetadata {
153
153
  }
154
154
 
155
155
  /**
156
- * Union type for all relation metadata.
156
+ * Union type for all relation metadata types.
157
157
  */
158
158
  export type RelationMetadata =
159
159
  | HasManyRelationMetadata
@@ -169,6 +169,7 @@ export type EntityType = 'table' | 'view';
169
169
 
170
170
  /**
171
171
  * Metadata for entities.
172
+ * Runtime lifecycle hooks intentionally live on OrmSession rather than here.
172
173
  * @template TColumns - The columns type
173
174
  */
174
175
  export interface EntityMetadata<TColumns extends Record<string, ColumnDefLike> = Record<string, ColumnDefLike>> {
@@ -184,8 +185,6 @@ export interface EntityMetadata<TColumns extends Record<string, ColumnDefLike> =
184
185
  relations: Record<string, RelationMetadata>;
185
186
  /** The transformers */
186
187
  transformers: Record<string, TransformerMetadata>;
187
- /** Optional hooks */
188
- hooks?: TableHooks;
189
188
  /** Optional table definition */
190
189
  table?: TableDef<MaterializeColumns<TColumns>>;
191
190
  }
@@ -295,25 +294,21 @@ export const addTransformerMetadata = (
295
294
  };
296
295
 
297
296
  /**
298
- * Sets the table name, hooks, and type for an entity.
297
+ * Sets the table name and type for an entity.
298
+ * Lifecycle policy is session-bound and is not persisted in metadata.
299
299
  * @param target - The entity constructor
300
300
  * @param tableName - The table name
301
- * @param hooks - Optional table hooks
302
301
  * @param type - Entity type: 'table' or 'view'
303
302
  */
304
303
  export const setEntityTableName = (
305
304
  target: EntityConstructor,
306
305
  tableName: string,
307
- hooks?: TableHooks,
308
306
  type?: EntityType
309
307
  ): void => {
310
308
  const meta = ensureEntityMetadata(target);
311
309
  if (tableName && tableName.length > 0) {
312
310
  meta.tableName = tableName;
313
311
  }
314
- if (hooks) {
315
- meta.hooks = hooks;
316
- }
317
312
  if (type) {
318
313
  meta.type = type;
319
314
  }
@@ -340,7 +335,7 @@ export const buildTableDef = <TColumns extends Record<string, ColumnDefLike>>(me
340
335
  } as ColumnDef;
341
336
  }
342
337
 
343
- const table = defineTable(meta.tableName, columns as MaterializeColumns<TColumns>, {}, meta.hooks);
338
+ const table = defineTable(meta.tableName, columns as MaterializeColumns<TColumns>);
344
339
  meta.table = table;
345
340
  return table;
346
341
  };
@@ -1,7 +1,8 @@
1
1
  import type { ProcedureCallNode } from '../core/ast/procedure.js';
2
2
  import type { QueryResult } from '../core/execution/db-executor.js';
3
3
  import { payloadResultSets } from '../core/execution/db-executor.js';
4
- import type { CompiledProcedureCall } from '../core/dialect/abstract.js';
4
+ import type { CompiledProcedureCall } from '../core/dialect/capabilities/procedure-compiler.js';
5
+ import { requireProcedureCompiler } from '../core/dialect/capabilities/procedure-compiler.js';
5
6
  import type { OrmSession } from './orm-session.js';
6
7
 
7
8
  export interface ProcedureExecutionResult {
@@ -63,7 +64,7 @@ export const executeProcedureAst = async (
63
64
  ast: ProcedureCallNode
64
65
  ): Promise<ProcedureExecutionResult> => {
65
66
  const execCtx = session.getExecutionContext();
66
- const compiled = execCtx.dialect.compileProcedureCall(ast);
67
+ const compiled = requireProcedureCompiler(execCtx.dialect).compileProcedureCall(ast);
67
68
  const payload = await execCtx.interceptors.run(
68
69
  { sql: compiled.sql, params: compiled.params },
69
70
  execCtx.executor
@@ -0,0 +1,20 @@
1
+ import type { TableDef } from '../schema/table.js';
2
+
3
+ /**
4
+ * Entity lifecycle hooks executed by the Unit of Work.
5
+ *
6
+ * Hooks are runtime/session configuration. They are intentionally not stored on
7
+ * TableDef so the same mapping can be used by independent sessions with
8
+ * different lifecycle policies.
9
+ */
10
+ export interface TableHooks<TEntity = unknown, TContext = unknown> {
11
+ beforeInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
12
+ afterInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
13
+ beforeUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
14
+ afterUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
15
+ beforeDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
16
+ afterDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
17
+ }
18
+
19
+ /** @internal */
20
+ export type TableHookResolver = (table: TableDef) => TableHooks | undefined;
@@ -13,6 +13,7 @@ import type { EntityConstructor } from './entity-metadata.js';
13
13
  import { Orm } from './orm.js';
14
14
  import { IdentityMap } from './identity-map.js';
15
15
  import { UnitOfWork } from './unit-of-work.js';
16
+ import type { TableHooks } from './lifecycle.js';
16
17
  import { DomainEventBus, DomainEventHandler, InitialHandlers } from './domain-event-bus.js';
17
18
  import { RelationChangeProcessor } from './relation-change-processor.js';
18
19
  import { createQueryLoggingExecutor, QueryLogger } from './query-logger.js';
@@ -38,6 +39,12 @@ const NESTED_TRANSACTIONS_REQUIRE_SAVEPOINTS =
38
39
  const ROLLBACK_ONLY_TRANSACTION =
39
40
  'Cannot commit transaction because an inner transaction failed';
40
41
 
42
+ type LifecycleHookTarget = TableDef | EntityConstructor<object>;
43
+ type LifecycleHookEntity<TTarget extends LifecycleHookTarget> =
44
+ TTarget extends TableDef ? EntityInstance<TTarget> :
45
+ TTarget extends EntityConstructor<object> ? InstanceType<TTarget> :
46
+ never;
47
+
41
48
  /**
42
49
  * Interface for ORM interceptors that allow hooking into the flush lifecycle.
43
50
  */
@@ -106,6 +113,7 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
106
113
  readonly tenantId?: string | number;
107
114
 
108
115
  private readonly interceptors: OrmInterceptor[];
116
+ private readonly tableHooks = new WeakMap<TableDef, TableHooks>();
109
117
  private saveGraphDefaults?: SaveGraphSessionOptions;
110
118
  private transactionDepth = 0;
111
119
  private savepointCounter = 0;
@@ -121,7 +129,13 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
121
129
  this.interceptors = [...(opts.interceptors ?? [])];
122
130
 
123
131
  this.identityMap = new IdentityMap();
124
- this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
132
+ this.unitOfWork = new UnitOfWork(
133
+ this.orm.dialect,
134
+ this.executor,
135
+ this.identityMap,
136
+ () => this,
137
+ table => this.tableHooks.get(table)
138
+ );
125
139
  this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
126
140
  this.domainEvents = new DomainEventBus<E, OrmSession<E>>(opts.domainEventHandlers);
127
141
  this.cacheManager = opts.cacheManager;
@@ -250,6 +264,24 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
250
264
  return this.unitOfWork.getEntitiesForTable(table);
251
265
  }
252
266
 
267
+ /**
268
+ * Registers INSERT/UPDATE/DELETE lifecycle hooks for this Session only.
269
+ * The target can be a TableDef or a decorated entity constructor.
270
+ * Registering again for the same table replaces the previous hook set.
271
+ */
272
+ registerTableHooks<TTarget extends LifecycleHookTarget>(
273
+ target: TTarget,
274
+ hooks: TableHooks<LifecycleHookEntity<TTarget>, OrmSession<E>>
275
+ ): void {
276
+ const table = typeof target === 'function'
277
+ ? getTableDefFromEntity(target as EntityConstructor<object>)
278
+ : target;
279
+ if (!table) {
280
+ throw new Error('Entity metadata has not been bootstrapped');
281
+ }
282
+ this.tableHooks.set(table as TableDef, hooks as unknown as TableHooks);
283
+ }
284
+
253
285
  /**
254
286
  * Registers an interceptor for flush lifecycle hooks.
255
287
  * @param interceptor - The interceptor to register
@@ -261,7 +293,7 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
261
293
  /**
262
294
  * Registers a domain event handler.
263
295
  * @param type - The event type
264
- * @param handler - The event handler
296
+ * @param handler - The domain event handler
265
297
  */
266
298
  registerDomainEventHandler<TType extends E['type']>(
267
299
  type: TType,
@@ -500,7 +532,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
500
532
  }
501
533
 
502
534
  /**
503
- * Flushes pending changes to the database without session hooks, relation processing, or domain events.
535
+ * Flushes pending changes to the database without session interceptors,
536
+ * relation processing, or domain events. Table lifecycle hooks still run
537
+ * because they are part of the Unit of Work.
504
538
  */
505
539
  async flush(): Promise<void> {
506
540
  await this.unitOfWork.flush();
@@ -5,13 +5,14 @@ import { InsertQueryBuilder } from '../query-builder/insert.js';
5
5
  import { UpdateQueryBuilder } from '../query-builder/update.js';
6
6
  import { DeleteQueryBuilder } from '../query-builder/delete.js';
7
7
  import { findPrimaryKey } from '../query-builder/hydration-planner.js';
8
- import type { TableDef, TableHooks } from '../schema/table.js';
8
+ import type { TableDef } from '../schema/table.js';
9
9
  import { payloadResultSets } from '../core/execution/db-executor.js';
10
10
  import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
11
11
  import { IdentityMap } from './identity-map.js';
12
12
  import { EntityStatus } from './runtime-types.js';
13
13
  import type { TrackedEntity } from './runtime-types.js';
14
14
  import type { PrimaryKey } from './entity-context.js';
15
+ import type { TableHookResolver, TableHooks } from './lifecycle.js';
15
16
 
16
17
  /**
17
18
  * Unit of Work pattern implementation for tracking entity changes.
@@ -25,12 +26,14 @@ export class UnitOfWork {
25
26
  * @param executor - The database executor
26
27
  * @param identityMap - The identity map
27
28
  * @param hookContext - Function to get the hook context
29
+ * @param resolveTableHooks - Session/runtime lifecycle hook resolver
28
30
  */
29
31
  constructor(
30
32
  private readonly dialect: Dialect,
31
33
  private readonly executor: DbExecutor,
32
34
  private readonly identityMap: IdentityMap,
33
- private readonly hookContext: () => unknown
35
+ private readonly hookContext: () => unknown,
36
+ private readonly resolveTableHooks: TableHookResolver = () => undefined
34
37
  ) { }
35
38
 
36
39
  /**
@@ -195,7 +198,8 @@ export class UnitOfWork {
195
198
  * @param tracked - The tracked entity to insert
196
199
  */
197
200
  private async flushInsert(tracked: TrackedEntity): Promise<void> {
198
- await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
201
+ const hooks = this.resolveTableHooks(tracked.table);
202
+ await this.runHook(hooks?.beforeInsert, tracked);
199
203
 
200
204
  const payload = this.extractColumns(tracked.table, tracked.entity as Record<string, unknown>);
201
205
  let builder = new InsertQueryBuilder(tracked.table).values(payload as Record<string, ValueOperandInput>);
@@ -212,7 +216,7 @@ export class UnitOfWork {
212
216
  tracked.pk = this.getPrimaryKeyValue(tracked);
213
217
  this.registerIdentity(tracked);
214
218
 
215
- await this.runHook(tracked.table.hooks?.afterInsert, tracked);
219
+ await this.runHook(hooks?.afterInsert, tracked);
216
220
  }
217
221
 
218
222
  /**
@@ -227,7 +231,8 @@ export class UnitOfWork {
227
231
  return;
228
232
  }
229
233
 
230
- await this.runHook(tracked.table.hooks?.beforeUpdate, tracked);
234
+ const hooks = this.resolveTableHooks(tracked.table);
235
+ await this.runHook(hooks?.beforeUpdate, tracked);
231
236
 
232
237
  const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
233
238
  if (!pkColumn) return;
@@ -248,7 +253,7 @@ export class UnitOfWork {
248
253
  tracked.original = this.createSnapshot(tracked.table, tracked.entity as Record<string, unknown>);
249
254
  this.registerIdentity(tracked);
250
255
 
251
- await this.runHook(tracked.table.hooks?.afterUpdate, tracked);
256
+ await this.runHook(hooks?.afterUpdate, tracked);
252
257
  }
253
258
 
254
259
  /**
@@ -257,7 +262,8 @@ export class UnitOfWork {
257
262
  */
258
263
  private async flushDelete(tracked: TrackedEntity): Promise<void> {
259
264
  if (tracked.pk == null) return;
260
- await this.runHook(tracked.table.hooks?.beforeDelete, tracked);
265
+ const hooks = this.resolveTableHooks(tracked.table);
266
+ await this.runHook(hooks?.beforeDelete, tracked);
261
267
 
262
268
  const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
263
269
  if (!pkColumn) return;
@@ -270,11 +276,11 @@ export class UnitOfWork {
270
276
  this.trackedEntities.delete(tracked.entity);
271
277
  this.identityMap.remove(tracked);
272
278
 
273
- await this.runHook(tracked.table.hooks?.afterDelete, tracked);
279
+ await this.runHook(hooks?.afterDelete, tracked);
274
280
  }
275
281
 
276
282
  /**
277
- * Runs a table hook if defined.
283
+ * Runs a lifecycle hook if defined.
278
284
  * @param hook - The hook function
279
285
  * @param tracked - The tracked entity
280
286
  */
@@ -1,5 +1,7 @@
1
1
  import type { ProcedureCallNode, ProcedureParamNode } from '../core/ast/procedure.js';
2
- import type { CompiledProcedureCall, Dialect } from '../core/dialect/abstract.js';
2
+ import type { Dialect } from '../core/dialect/abstract.js';
3
+ import type { CompiledProcedureCall } from '../core/dialect/capabilities/procedure-compiler.js';
4
+ import { requireProcedureCompiler } from '../core/dialect/capabilities/procedure-compiler.js';
3
5
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
4
6
  import { valueToOperand, ValueOperandInput } from '../core/ast/expression-builders.js';
5
7
  import type { OrmSession } from '../orm/orm-session.js';
@@ -82,8 +84,7 @@ export class ProcedureCallBuilder {
82
84
 
83
85
  compile(dialect: ProcedureDialectInput): CompiledProcedureCall {
84
86
  const resolved = resolveDialectInput(dialect);
85
- this.validateMssqlOutDbType(resolved);
86
- return resolved.compileProcedureCall(this.getAST());
87
+ return requireProcedureCompiler(resolved).compileProcedureCall(this.getAST());
87
88
  }
88
89
 
89
90
  toSql(dialect: ProcedureDialectInput): string {
@@ -99,23 +100,8 @@ export class ProcedureCallBuilder {
99
100
  }
100
101
 
101
102
  async execute(session: OrmSession): Promise<ProcedureExecutionResult> {
102
- this.validateMssqlOutDbType(session.getExecutionContext().dialect);
103
103
  return executeProcedureAst(session, this.getAST());
104
104
  }
105
-
106
- private validateMssqlOutDbType(dialect: Dialect): void {
107
- const isMssqlDialect = dialect.constructor.name === 'SqlServerDialect';
108
- if (!isMssqlDialect) return;
109
-
110
- for (const param of this.ast.params) {
111
- const needsDbType = param.direction === 'out' || param.direction === 'inout';
112
- if (needsDbType && !param.dbType) {
113
- throw new Error(
114
- `MSSQL requires "dbType" for procedure parameter "${param.name}" with direction "${param.direction}".`
115
- );
116
- }
117
- }
118
- }
119
105
  }
120
106
 
121
107
  export const callProcedure = (name: string, options?: CallProcedureOptions): ProcedureCallBuilder =>
@@ -36,23 +36,15 @@ export interface TableOptions {
36
36
  collation?: string;
37
37
  }
38
38
 
39
- export interface TableHooks<TEntity = unknown, TContext = unknown> {
40
- beforeInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
41
- afterInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
42
- beforeUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
43
- afterUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
44
- beforeDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
45
- afterDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
46
- }
47
-
48
39
  /**
49
- * Definition of a database table with its columns and relationships
40
+ * Definition of a database table with its columns and relationships.
41
+ *
42
+ * Runtime lifecycle policy intentionally does not live here. TableDef is
43
+ * mapping/schema metadata and can be shared by multiple OrmSession instances.
50
44
  * @typeParam T - Type of the columns record
51
45
  */
52
46
  export interface TableDef<
53
- T extends Record<string, ColumnDef> = Record<string, ColumnDef>,
54
- TEntity = unknown,
55
- TContext = unknown
47
+ T extends Record<string, ColumnDef> = Record<string, ColumnDef>
56
48
  > {
57
49
  /** Name of the table */
58
50
  name: string;
@@ -62,8 +54,6 @@ export interface TableDef<
62
54
  columns: T;
63
55
  /** Record of relationship definitions keyed by relation name */
64
56
  relations: Record<string, RelationDef>;
65
- /** Optional lifecycle hooks */
66
- hooks?: TableHooks<TEntity, TContext>;
67
57
  /** Composite primary key definition (falls back to column.primary flags) */
68
58
  primaryKey?: string[];
69
59
  /** Secondary indexes */
@@ -79,11 +69,12 @@ export interface TableDef<
79
69
  }
80
70
 
81
71
  /**
82
- * Creates a table definition with columns and relationships
72
+ * Creates a table definition with columns and relationships.
83
73
  * @typeParam T - Type of the columns record
84
74
  * @param name - Name of the table
85
- * @param columns - Record of column definitions
75
+ * @param columns - Record of column definitions keyed by property name
86
76
  * @param relations - Record of relationship definitions (optional)
77
+ * @param options - Schema/table options (optional)
87
78
  * @returns Complete table definition with runtime-filled column metadata
88
79
  *
89
80
  * @example
@@ -95,17 +86,12 @@ export interface TableDef<
95
86
  * });
96
87
  * ```
97
88
  */
98
- export const defineTable = <
99
- T extends Record<string, ColumnDef>,
100
- TEntity = unknown,
101
- TContext = unknown
102
- >(
89
+ export const defineTable = <T extends Record<string, ColumnDef>>(
103
90
  name: string,
104
91
  columns: T,
105
92
  relations: Record<string, RelationDef> = {},
106
- hooks?: TableHooks<TEntity, TContext>,
107
93
  options: TableOptions = {}
108
- ): TableDef<T, TEntity, TContext> => {
94
+ ): TableDef<T> => {
109
95
  // Runtime mutability to assign names to column definitions for convenience
110
96
  const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
111
97
  const colDef = { ...def, name: key, table: name };
@@ -118,7 +104,6 @@ export const defineTable = <
118
104
  schema: options.schema,
119
105
  columns: colsWithNames,
120
106
  relations,
121
- hooks,
122
107
  primaryKey: options.primaryKey,
123
108
  indexes: options.indexes,
124
109
  checks: options.checks,