metal-orm 1.0.39 → 1.0.41

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.
Files changed (52) hide show
  1. package/dist/index.cjs +1466 -189
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +723 -51
  4. package/dist/index.d.ts +723 -51
  5. package/dist/index.js +1457 -189
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/codegen/typescript.ts +66 -5
  9. package/src/core/ast/aggregate-functions.ts +15 -15
  10. package/src/core/ast/expression-builders.ts +378 -316
  11. package/src/core/ast/expression-nodes.ts +210 -186
  12. package/src/core/ast/expression-visitor.ts +40 -30
  13. package/src/core/ast/query.ts +164 -132
  14. package/src/core/ast/window-functions.ts +86 -86
  15. package/src/core/dialect/abstract.ts +509 -479
  16. package/src/core/dialect/base/groupby-compiler.ts +6 -6
  17. package/src/core/dialect/base/join-compiler.ts +9 -12
  18. package/src/core/dialect/base/orderby-compiler.ts +20 -6
  19. package/src/core/dialect/base/sql-dialect.ts +237 -138
  20. package/src/core/dialect/mssql/index.ts +164 -185
  21. package/src/core/dialect/sqlite/index.ts +39 -34
  22. package/src/core/execution/db-executor.ts +46 -6
  23. package/src/core/execution/executors/mssql-executor.ts +39 -22
  24. package/src/core/execution/executors/mysql-executor.ts +23 -6
  25. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  26. package/src/core/execution/pooling/pool-types.ts +30 -0
  27. package/src/core/execution/pooling/pool.ts +268 -0
  28. package/src/core/functions/standard-strategy.ts +46 -37
  29. package/src/decorators/bootstrap.ts +7 -7
  30. package/src/index.ts +6 -0
  31. package/src/orm/domain-event-bus.ts +49 -0
  32. package/src/orm/entity-metadata.ts +9 -9
  33. package/src/orm/entity.ts +58 -0
  34. package/src/orm/orm-session.ts +465 -270
  35. package/src/orm/orm.ts +61 -11
  36. package/src/orm/pooled-executor-factory.ts +131 -0
  37. package/src/orm/query-logger.ts +6 -12
  38. package/src/orm/relation-change-processor.ts +75 -0
  39. package/src/orm/relations/many-to-many.ts +4 -2
  40. package/src/orm/save-graph.ts +303 -0
  41. package/src/orm/transaction-runner.ts +3 -3
  42. package/src/orm/unit-of-work.ts +128 -0
  43. package/src/query-builder/delete-query-state.ts +67 -38
  44. package/src/query-builder/delete.ts +37 -1
  45. package/src/query-builder/hydration-manager.ts +93 -79
  46. package/src/query-builder/insert-query-state.ts +131 -61
  47. package/src/query-builder/insert.ts +27 -1
  48. package/src/query-builder/query-ast-service.ts +207 -170
  49. package/src/query-builder/select-query-state.ts +169 -162
  50. package/src/query-builder/select.ts +15 -23
  51. package/src/query-builder/update-query-state.ts +114 -77
  52. package/src/query-builder/update.ts +38 -1
@@ -1,213 +1,192 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode, DerivedTableNode } from '../../ast/query.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import {
3
+ SelectQueryNode,
4
+ DeleteQueryNode,
5
+ TableSourceNode,
6
+ DerivedTableNode,
7
+ OrderByNode
8
+ } from '../../ast/query.js';
3
9
  import { JsonPathNode } from '../../ast/expression.js';
4
10
  import { MssqlFunctionStrategy } from './functions.js';
5
- import { FunctionTableFormatter } from '../base/function-table-formatter.js';
6
-
7
- /**
8
- * Microsoft SQL Server dialect implementation
9
- */
10
- export class SqlServerDialect extends Dialect {
11
- protected readonly dialect = 'mssql';
12
- /**
13
- * Creates a new SqlServerDialect instance
14
- */
15
- public constructor() {
16
- super(new MssqlFunctionStrategy());
17
- }
18
-
19
- /**
20
- * Quotes an identifier using SQL Server bracket syntax
21
- * @param id - Identifier to quote
22
- * @returns Quoted identifier
23
- */
24
- quoteIdentifier(id: string): string {
25
- return `[${id}]`;
26
- }
27
-
28
- /**
29
- * Compiles JSON path expression using SQL Server syntax
30
- * @param node - JSON path node
31
- * @returns SQL Server JSON path expression
32
- */
33
- protected compileJsonPath(node: JsonPathNode): string {
34
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
35
- // SQL Server uses JSON_VALUE(col, '$.path')
36
- return `JSON_VALUE(${col}, '${node.path}')`;
37
- }
38
-
39
- /**
40
- * Formats parameter placeholders using SQL Server named parameter syntax
41
- * @param index - Parameter index
42
- * @returns Named parameter placeholder
43
- */
44
- protected formatPlaceholder(index: number): string {
45
- return `@p${index}`;
46
- }
47
-
48
- /**
49
- * Compiles SELECT query AST to SQL Server SQL
50
- * @param ast - Query AST
51
- * @param ctx - Compiler context
52
- * @returns SQL Server SQL string
53
- */
54
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
55
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
56
- const ctes = this.compileCtes(ast, ctx);
57
-
58
- const baseAst: SelectQueryNode = hasSetOps
59
- ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
60
- : ast;
61
-
62
- const baseSelect = this.compileSelectCore(baseAst, ctx);
63
-
64
- if (!hasSetOps) {
65
- return `${ctes}${baseSelect}`;
66
- }
67
-
68
- const compound = ast.setOps!
69
- .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
70
- .join(' ');
71
-
72
- const orderBy = this.compileOrderBy(ast);
73
- const pagination = this.compilePagination(ast, orderBy);
74
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
75
- const tail = pagination || orderBy;
76
- return `${ctes}${combined}${tail}`;
77
- }
78
-
79
- protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
80
- const table = this.quoteIdentifier(ast.into.name);
81
- const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
82
- const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
83
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
84
- }
85
-
86
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
87
- const table = this.quoteIdentifier(ast.table.name);
88
- const assignments = ast.set.map(assignment => {
89
- const col = assignment.column;
90
- const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
91
- const value = this.compileOperand(assignment.value, ctx);
92
- return `${target} = ${value}`;
93
- }).join(', ');
94
- const whereClause = this.compileWhere(ast.where, ctx);
95
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
96
- }
97
-
11
+ import { OrderByCompiler } from '../base/orderby-compiler.js';
12
+ import { JoinCompiler } from '../base/join-compiler.js';
13
+ import { SqlDialectBase } from '../base/sql-dialect.js';
14
+
15
+ /**
16
+ * Microsoft SQL Server dialect implementation
17
+ */
18
+ export class SqlServerDialect extends SqlDialectBase {
19
+ protected readonly dialect = 'mssql';
20
+ /**
21
+ * Creates a new SqlServerDialect instance
22
+ */
23
+ public constructor() {
24
+ super(new MssqlFunctionStrategy());
25
+ }
26
+
27
+ /**
28
+ * Quotes an identifier using SQL Server bracket syntax
29
+ * @param id - Identifier to quote
30
+ * @returns Quoted identifier
31
+ */
32
+ quoteIdentifier(id: string): string {
33
+ return `[${id}]`;
34
+ }
35
+
36
+ /**
37
+ * Compiles JSON path expression using SQL Server syntax
38
+ * @param node - JSON path node
39
+ * @returns SQL Server JSON path expression
40
+ */
41
+ protected compileJsonPath(node: JsonPathNode): string {
42
+ const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
43
+ // SQL Server uses JSON_VALUE(col, '$.path')
44
+ return `JSON_VALUE(${col}, '${node.path}')`;
45
+ }
46
+
47
+ /**
48
+ * Formats parameter placeholders using SQL Server named parameter syntax
49
+ * @param index - Parameter index
50
+ * @returns Named parameter placeholder
51
+ */
52
+ protected formatPlaceholder(index: number): string {
53
+ return `@p${index}`;
54
+ }
55
+
56
+ /**
57
+ * Compiles SELECT query AST to SQL Server SQL
58
+ * @param ast - Query AST
59
+ * @param ctx - Compiler context
60
+ * @returns SQL Server SQL string
61
+ */
62
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
63
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
64
+ const ctes = this.compileCtes(ast, ctx);
65
+
66
+ const baseAst: SelectQueryNode = hasSetOps
67
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
68
+ : ast;
69
+
70
+ const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
71
+
72
+ if (!hasSetOps) {
73
+ return `${ctes}${baseSelect}`;
74
+ }
75
+
76
+ const compound = ast.setOps!
77
+ .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
78
+ .join(' ');
79
+
80
+ const orderBy = this.compileOrderBy(ast, ctx);
81
+ const pagination = this.compilePagination(ast, orderBy);
82
+ const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
83
+ const tail = pagination || orderBy;
84
+ return `${ctes}${combined}${tail}`;
85
+ }
86
+
98
87
  protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
88
+ if (ast.using) {
89
+ throw new Error('DELETE ... USING is not supported in the MSSQL dialect; use join() instead.');
90
+ }
91
+
99
92
  if (ast.from.type !== 'Table') {
100
93
  throw new Error('DELETE only supports base tables in the MSSQL dialect.');
101
94
  }
102
- const table = this.quoteIdentifier(ast.from.name);
95
+
96
+ const alias = ast.from.alias ?? ast.from.name;
97
+ const target = this.compileTableReference(ast.from);
98
+ const joins = JoinCompiler.compileJoins(
99
+ ast.joins,
100
+ ctx,
101
+ this.compileFrom.bind(this),
102
+ this.compileExpression.bind(this)
103
+ );
103
104
  const whereClause = this.compileWhere(ast.where, ctx);
104
- return `DELETE FROM ${table}${whereClause};`;
105
+ const returning = this.compileReturning(ast.returning, ctx);
106
+ return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
105
107
  }
106
108
 
107
- private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
108
- const columns = ast.columns.map(c => {
109
- let expr = '';
110
- if (c.type === 'Function') {
111
- expr = this.compileOperand(c, ctx);
112
- } else if (c.type === 'Column') {
113
- expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
114
- } else if (c.type === 'ScalarSubquery') {
115
- expr = this.compileOperand(c, ctx);
116
- } else if (c.type === 'WindowFunction') {
117
- expr = this.compileOperand(c, ctx);
118
- }
119
-
120
- if (c.alias) {
121
- if (c.alias.includes('(')) return c.alias;
122
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
123
- }
124
- return expr;
125
- }).join(', ');
109
+ private compileSelectCoreForMssql(ast: SelectQueryNode, ctx: CompilerContext): string {
110
+ const columns = ast.columns.map(c => {
111
+ let expr = '';
112
+ if (c.type === 'Function') {
113
+ expr = this.compileOperand(c, ctx);
114
+ } else if (c.type === 'Column') {
115
+ expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
116
+ } else if (c.type === 'ScalarSubquery') {
117
+ expr = this.compileOperand(c, ctx);
118
+ } else if (c.type === 'WindowFunction') {
119
+ expr = this.compileOperand(c, ctx);
120
+ }
121
+
122
+ if (c.alias) {
123
+ if (c.alias.includes('(')) return c.alias;
124
+ return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
125
+ }
126
+ return expr;
127
+ }).join(', ');
126
128
 
127
129
  const distinct = ast.distinct ? 'DISTINCT ' : '';
128
- const from = this.compileTableSource(ast.from, ctx);
130
+ const from = this.compileTableSource(ast.from);
129
131
 
130
132
  const joins = ast.joins.map(j => {
131
- const table = this.compileTableSource(j.table, ctx);
133
+ const table = this.compileTableSource(j.table);
132
134
  const cond = this.compileExpression(j.condition, ctx);
133
135
  return `${j.kind} JOIN ${table} ON ${cond}`;
134
136
  }).join(' ');
135
- const whereClause = this.compileWhere(ast.where, ctx);
136
-
137
- const groupBy = ast.groupBy && ast.groupBy.length > 0
138
- ? ' GROUP BY ' + ast.groupBy.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(', ')
139
- : '';
140
-
141
- const having = ast.having
142
- ? ` HAVING ${this.compileExpression(ast.having, ctx)}`
143
- : '';
144
-
145
- const orderBy = this.compileOrderBy(ast);
146
- const pagination = this.compilePagination(ast, orderBy);
147
-
148
- if (pagination) {
149
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
150
- }
151
-
152
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
153
- }
154
-
155
- private compileOrderBy(ast: SelectQueryNode): string {
156
- if (!ast.orderBy || ast.orderBy.length === 0) return '';
157
- return ' ORDER BY ' + ast.orderBy
158
- .map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`)
159
- .join(', ');
160
- }
161
-
137
+ const whereClause = this.compileWhere(ast.where, ctx);
138
+
139
+ const groupBy = ast.groupBy && ast.groupBy.length > 0
140
+ ? ' GROUP BY ' + ast.groupBy.map(term => this.compileOrderingTerm(term, ctx)).join(', ')
141
+ : '';
142
+
143
+ const having = ast.having
144
+ ? ` HAVING ${this.compileExpression(ast.having, ctx)}`
145
+ : '';
146
+
147
+ const orderBy = this.compileOrderBy(ast, ctx);
148
+ const pagination = this.compilePagination(ast, orderBy);
149
+
150
+ if (pagination) {
151
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
152
+ }
153
+
154
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
155
+ }
156
+
157
+ private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
158
+ return OrderByCompiler.compileOrderBy(
159
+ ast,
160
+ term => this.compileOrderingTerm(term, ctx),
161
+ this.renderOrderByNulls.bind(this),
162
+ this.renderOrderByCollation.bind(this)
163
+ );
164
+ }
165
+
162
166
  private compilePagination(ast: SelectQueryNode, orderBy: string): string {
163
167
  const hasLimit = ast.limit !== undefined;
164
168
  const hasOffset = ast.offset !== undefined;
165
169
  if (!hasLimit && !hasOffset) return '';
166
-
167
- const off = ast.offset ?? 0;
168
- const orderClause = orderBy || ' ORDER BY (SELECT NULL)';
169
- let pagination = `${orderClause} OFFSET ${off} ROWS`;
170
+
171
+ const off = ast.offset ?? 0;
172
+ const orderClause = orderBy || ' ORDER BY (SELECT NULL)';
173
+ let pagination = `${orderClause} OFFSET ${off} ROWS`;
170
174
  if (hasLimit) {
171
175
  pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
172
176
  }
173
177
  return pagination;
174
178
  }
175
179
 
176
- private compileTableSource(table: TableSourceNode, ctx: CompilerContext): string {
177
- if (table.type === 'FunctionTable') {
178
- return FunctionTableFormatter.format(table, ctx, this as any);
179
- }
180
- if (table.type === 'DerivedTable') {
181
- return this.compileDerivedTable(table, ctx);
182
- }
183
- const base = table.schema
184
- ? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`
185
- : this.quoteIdentifier(table.name);
186
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
180
+ private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
181
+ if (!ast.ctes || ast.ctes.length === 0) return '';
182
+ // MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
183
+ const defs = ast.ctes.map(cte => {
184
+ const name = this.quoteIdentifier(cte.name);
185
+ const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
186
+ const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
187
+ return `${name}${cols} AS (${query})`;
188
+ }).join(', ');
189
+ return `WITH ${defs} `;
187
190
  }
188
191
 
189
- private compileDerivedTable(table: DerivedTableNode, ctx: CompilerContext): string {
190
- const sub = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, '');
191
- const cols = table.columnAliases?.length
192
- ? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
193
- : '';
194
- return `(${sub}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
195
- }
196
-
197
- private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
198
- if (!ast.ctes || ast.ctes.length === 0) return '';
199
- // MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
200
- const defs = ast.ctes.map(cte => {
201
- const name = this.quoteIdentifier(cte.name);
202
- const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
203
- const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
204
- return `${name}${cols} AS (${query})`;
205
- }).join(', ');
206
- return `WITH ${defs} `;
207
- }
208
-
209
- private wrapSetOperand(sql: string): string {
210
- const trimmed = sql.trim().replace(/;$/, '');
211
- return `(${trimmed})`;
212
- }
213
- }
192
+ }
@@ -1,11 +1,12 @@
1
- import { CompilerContext } from '../abstract.js';
2
- import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
3
- import { SqlDialectBase } from '../base/sql-dialect.js';
4
- import { SqliteFunctionStrategy } from './functions.js';
5
-
6
- /**
7
- * SQLite dialect implementation
8
- */
1
+ import { CompilerContext } from '../abstract.js';
2
+ import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
3
+ import { TableNode } from '../../ast/query.js';
4
+ import { SqlDialectBase } from '../base/sql-dialect.js';
5
+ import { SqliteFunctionStrategy } from './functions.js';
6
+
7
+ /**
8
+ * SQLite dialect implementation
9
+ */
9
10
  export class SqliteDialect extends SqlDialectBase {
10
11
  protected readonly dialect = 'sqlite';
11
12
  /**
@@ -14,27 +15,31 @@ export class SqliteDialect extends SqlDialectBase {
14
15
  public constructor() {
15
16
  super(new SqliteFunctionStrategy());
16
17
  }
17
-
18
- /**
19
- * Quotes an identifier using SQLite double-quote syntax
20
- * @param id - Identifier to quote
21
- * @returns Quoted identifier
22
- */
23
- quoteIdentifier(id: string): string {
24
- return `"${id}"`;
25
- }
26
-
27
- /**
28
- * Compiles JSON path expression using SQLite syntax
29
- * @param node - JSON path node
30
- * @returns SQLite JSON path expression
31
- */
32
- protected compileJsonPath(node: JsonPathNode): string {
33
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
34
- // SQLite uses json_extract(col, '$.path')
35
- return `json_extract(${col}, '${node.path}')`;
36
- }
37
-
18
+
19
+ /**
20
+ * Quotes an identifier using SQLite double-quote syntax
21
+ * @param id - Identifier to quote
22
+ * @returns Quoted identifier
23
+ */
24
+ quoteIdentifier(id: string): string {
25
+ return `"${id}"`;
26
+ }
27
+
28
+ /**
29
+ * Compiles JSON path expression using SQLite syntax
30
+ * @param node - JSON path node
31
+ * @returns SQLite JSON path expression
32
+ */
33
+ protected compileJsonPath(node: JsonPathNode): string {
34
+ const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
35
+ // SQLite uses json_extract(col, '$.path')
36
+ return `json_extract(${col}, '${node.path}')`;
37
+ }
38
+
39
+ protected compileQualifiedColumn(column: ColumnNode, _table: TableNode): string {
40
+ return this.quoteIdentifier(column.name);
41
+ }
42
+
38
43
  protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
39
44
  if (!returning || returning.length === 0) return '';
40
45
  const columns = this.formatReturningColumns(returning);
@@ -49,8 +54,8 @@ export class SqliteDialect extends SqlDialectBase {
49
54
  })
50
55
  .join(', ');
51
56
  }
52
-
53
- supportsReturning(): boolean {
54
- return true;
55
- }
56
- }
57
+
58
+ supportsReturning(): boolean {
59
+ return true;
60
+ }
61
+ }
@@ -7,11 +7,20 @@ export type QueryResult = {
7
7
  };
8
8
 
9
9
  export interface DbExecutor {
10
+ /** Capability flags so the runtime can make correct decisions without relying on optional methods. */
11
+ readonly capabilities: {
12
+ /** True if begin/commit/rollback are real and should be used to provide atomicity. */
13
+ transactions: boolean;
14
+ };
15
+
10
16
  executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
11
17
 
12
- beginTransaction?(): Promise<void>;
13
- commitTransaction?(): Promise<void>;
14
- rollbackTransaction?(): Promise<void>;
18
+ beginTransaction(): Promise<void>;
19
+ commitTransaction(): Promise<void>;
20
+ rollbackTransaction(): Promise<void>;
21
+
22
+ /** Release any underlying resources (connections, pool leases, etc). Must be idempotent. */
23
+ dispose(): Promise<void>;
15
24
  }
16
25
 
17
26
  // --- helpers ---
@@ -39,9 +48,14 @@ export interface SimpleQueryRunner {
39
48
  sql: string,
40
49
  params?: unknown[]
41
50
  ): Promise<Array<Record<string, unknown>>>;
51
+
52
+ /** Optional: used to support real transactions. */
42
53
  beginTransaction?(): Promise<void>;
43
54
  commitTransaction?(): Promise<void>;
44
55
  rollbackTransaction?(): Promise<void>;
56
+
57
+ /** Optional: release resources (connection close, pool lease release, etc). */
58
+ dispose?(): Promise<void>;
45
59
  }
46
60
 
47
61
  /**
@@ -50,14 +64,40 @@ export interface SimpleQueryRunner {
50
64
  export function createExecutorFromQueryRunner(
51
65
  runner: SimpleQueryRunner
52
66
  ): DbExecutor {
67
+ const supportsTransactions =
68
+ typeof runner.beginTransaction === 'function' &&
69
+ typeof runner.commitTransaction === 'function' &&
70
+ typeof runner.rollbackTransaction === 'function';
71
+
53
72
  return {
73
+ capabilities: {
74
+ transactions: supportsTransactions,
75
+ },
54
76
  async executeSql(sql, params) {
55
77
  const rows = await runner.query(sql, params);
56
78
  const result = rowsToQueryResult(rows);
57
79
  return [result];
58
80
  },
59
- beginTransaction: runner.beginTransaction?.bind(runner),
60
- commitTransaction: runner.commitTransaction?.bind(runner),
61
- rollbackTransaction: runner.rollbackTransaction?.bind(runner),
81
+ async beginTransaction() {
82
+ if (!supportsTransactions) {
83
+ throw new Error('Transactions are not supported by this executor');
84
+ }
85
+ await runner.beginTransaction!.call(runner);
86
+ },
87
+ async commitTransaction() {
88
+ if (!supportsTransactions) {
89
+ throw new Error('Transactions are not supported by this executor');
90
+ }
91
+ await runner.commitTransaction!.call(runner);
92
+ },
93
+ async rollbackTransaction() {
94
+ if (!supportsTransactions) {
95
+ throw new Error('Transactions are not supported by this executor');
96
+ }
97
+ await runner.rollbackTransaction!.call(runner);
98
+ },
99
+ async dispose() {
100
+ await runner.dispose?.call(runner);
101
+ },
62
102
  };
63
103
  }
@@ -17,23 +17,40 @@ export interface MssqlClientLike {
17
17
  export function createMssqlExecutor(
18
18
  client: MssqlClientLike
19
19
  ): DbExecutor {
20
+ const supportsTransactions =
21
+ typeof client.beginTransaction === 'function' &&
22
+ typeof client.commit === 'function' &&
23
+ typeof client.rollback === 'function';
24
+
20
25
  return {
26
+ capabilities: {
27
+ transactions: supportsTransactions,
28
+ },
21
29
  async executeSql(sql, params) {
22
30
  const { recordset } = await client.query(sql, params);
23
31
  const result = rowsToQueryResult(recordset ?? []);
24
32
  return [result];
25
33
  },
26
34
  async beginTransaction() {
27
- if (!client.beginTransaction) return;
28
- await client.beginTransaction();
35
+ if (!supportsTransactions) {
36
+ throw new Error('Transactions are not supported by this executor');
37
+ }
38
+ await client.beginTransaction!();
29
39
  },
30
40
  async commitTransaction() {
31
- if (!client.commit) return;
32
- await client.commit();
41
+ if (!supportsTransactions) {
42
+ throw new Error('Transactions are not supported by this executor');
43
+ }
44
+ await client.commit!();
33
45
  },
34
46
  async rollbackTransaction() {
35
- if (!client.rollback) return;
36
- await client.rollback();
47
+ if (!supportsTransactions) {
48
+ throw new Error('Transactions are not supported by this executor');
49
+ }
50
+ await client.rollback!();
51
+ },
52
+ async dispose() {
53
+ // Connection lifecycle is owned by the caller/driver. Pool lease executors should implement dispose.
37
54
  },
38
55
  };
39
56
  }
@@ -53,7 +70,7 @@ export interface TediousRequest {
53
70
  }
54
71
 
55
72
  export interface TediousRequestCtor {
56
- new (sql: string, callback: (err?: Error | null) => void): TediousRequest;
73
+ new(sql: string, callback: (err?: Error | null) => void): TediousRequest;
57
74
  }
58
75
 
59
76
  export interface TediousTypes {
@@ -140,29 +157,29 @@ export function createTediousMssqlClient(
140
157
 
141
158
  beginTransaction: connection.beginTransaction
142
159
  ? () =>
143
- new Promise<void>((resolve, reject) => {
144
- connection.beginTransaction!(err =>
145
- err ? reject(err) : resolve()
146
- );
147
- })
160
+ new Promise<void>((resolve, reject) => {
161
+ connection.beginTransaction!(err =>
162
+ err ? reject(err) : resolve()
163
+ );
164
+ })
148
165
  : undefined,
149
166
 
150
167
  commit: connection.commitTransaction
151
168
  ? () =>
152
- new Promise<void>((resolve, reject) => {
153
- connection.commitTransaction!(err =>
154
- err ? reject(err) : resolve()
155
- );
156
- })
169
+ new Promise<void>((resolve, reject) => {
170
+ connection.commitTransaction!(err =>
171
+ err ? reject(err) : resolve()
172
+ );
173
+ })
157
174
  : undefined,
158
175
 
159
176
  rollback: connection.rollbackTransaction
160
177
  ? () =>
161
- new Promise<void>((resolve, reject) => {
162
- connection.rollbackTransaction!(err =>
163
- err ? reject(err) : resolve()
164
- );
165
- })
178
+ new Promise<void>((resolve, reject) => {
179
+ connection.rollbackTransaction!(err =>
180
+ err ? reject(err) : resolve()
181
+ );
182
+ })
166
183
  : undefined,
167
184
  };
168
185
  }