metal-orm 1.0.41 → 1.0.43
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.
- package/README.md +74 -20
- package/dist/index.cjs +180 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -96
- package/dist/index.d.ts +142 -96
- package/dist/index.js +177 -74
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/scripts/run-eslint.mjs +34 -0
- package/src/codegen/typescript.ts +32 -15
- package/src/core/ast/builders.ts +7 -2
- package/src/core/ast/expression-builders.ts +0 -2
- package/src/core/ast/expression-nodes.ts +14 -5
- package/src/core/ast/expression-visitor.ts +11 -8
- package/src/core/ast/expression.ts +2 -2
- package/src/core/ast/join-node.ts +1 -1
- package/src/core/ast/query.ts +6 -6
- package/src/core/ast/window-functions.ts +10 -2
- package/src/core/ddl/dialects/base-schema-dialect.ts +30 -3
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +4 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +2 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +13 -1
- package/src/core/ddl/dialects/render-reference.test.ts +69 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
- package/src/core/ddl/introspect/mssql.ts +42 -8
- package/src/core/ddl/introspect/mysql.ts +30 -6
- package/src/core/ddl/introspect/postgres.ts +88 -34
- package/src/core/ddl/introspect/run-select.ts +6 -4
- package/src/core/ddl/introspect/sqlite.ts +56 -11
- package/src/core/ddl/introspect/types.ts +0 -1
- package/src/core/ddl/introspect/utils.ts +3 -3
- package/src/core/ddl/schema-dialect.ts +1 -0
- package/src/core/ddl/schema-generator.ts +4 -12
- package/src/core/ddl/sql-writing.ts +4 -4
- package/src/core/dialect/abstract.ts +18 -6
- package/src/core/dialect/base/function-table-formatter.ts +3 -2
- package/src/core/dialect/base/join-compiler.ts +5 -3
- package/src/core/dialect/base/returning-strategy.ts +1 -0
- package/src/core/dialect/base/sql-dialect.ts +3 -3
- package/src/core/dialect/mssql/functions.ts +24 -25
- package/src/core/dialect/mssql/index.ts +1 -4
- package/src/core/dialect/mysql/functions.ts +0 -1
- package/src/core/dialect/postgres/functions.ts +33 -34
- package/src/core/dialect/postgres/index.ts +1 -0
- package/src/core/dialect/sqlite/functions.ts +18 -19
- package/src/core/dialect/sqlite/index.ts +2 -0
- package/src/core/execution/db-executor.ts +1 -1
- package/src/core/execution/executors/mysql-executor.ts +2 -2
- package/src/core/execution/executors/postgres-executor.ts +1 -1
- package/src/core/execution/pooling/pool.ts +2 -0
- package/src/core/functions/datetime.ts +1 -1
- package/src/core/functions/numeric.ts +1 -1
- package/src/core/functions/text.ts +1 -1
- package/src/decorators/bootstrap.ts +27 -8
- package/src/decorators/column.ts +3 -11
- package/src/decorators/decorator-metadata.ts +3 -9
- package/src/decorators/entity.ts +21 -5
- package/src/decorators/relations.ts +2 -11
- package/src/orm/entity-context.ts +8 -8
- package/src/orm/entity-meta.ts +8 -8
- package/src/orm/entity-metadata.ts +11 -9
- package/src/orm/entity.ts +28 -29
- package/src/orm/execute.ts +4 -4
- package/src/orm/hydration.ts +42 -39
- package/src/orm/identity-map.ts +1 -1
- package/src/orm/lazy-batch.ts +9 -9
- package/src/orm/orm-session.ts +24 -23
- package/src/orm/orm.ts +2 -5
- package/src/orm/relation-change-processor.ts +12 -11
- package/src/orm/relations/belongs-to.ts +11 -11
- package/src/orm/relations/has-many.ts +10 -10
- package/src/orm/relations/has-one.ts +8 -7
- package/src/orm/relations/many-to-many.ts +13 -13
- package/src/orm/runtime-types.ts +4 -4
- package/src/orm/save-graph.ts +31 -25
- package/src/orm/unit-of-work.ts +17 -17
- package/src/query-builder/delete.ts +4 -3
- package/src/query-builder/hydration-manager.ts +6 -5
- package/src/query-builder/insert.ts +12 -8
- package/src/query-builder/query-ast-service.ts +2 -2
- package/src/query-builder/raw-column-parser.ts +2 -1
- package/src/query-builder/select-helpers.ts +2 -2
- package/src/query-builder/select.ts +31 -31
- package/src/query-builder/update.ts +4 -3
- package/src/schema/column.ts +26 -26
- package/src/schema/table.ts +239 -115
- package/src/schema/types.ts +22 -22
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import { OperandNode } from '../../ast/expression.js';
|
|
2
3
|
import { SqlDialectBase } from './sql-dialect.js';
|
|
3
4
|
|
|
4
5
|
export interface FunctionTableNode {
|
|
@@ -57,9 +58,9 @@ export class FunctionTableFormatter {
|
|
|
57
58
|
*/
|
|
58
59
|
private static formatArgs(fn: FunctionTableNode, ctx?: CompilerContext, dialect?: SqlDialectBase): string {
|
|
59
60
|
return (fn.args || [])
|
|
60
|
-
.map((a:
|
|
61
|
+
.map((a: OperandNode) => {
|
|
61
62
|
if (ctx && dialect) {
|
|
62
|
-
return (dialect as
|
|
63
|
+
return (dialect as unknown as { compileOperand(n: OperandNode, c: CompilerContext): string }).compileOperand(a, ctx);
|
|
63
64
|
}
|
|
64
65
|
return String(a);
|
|
65
66
|
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CompilerContext } from '../abstract.js';
|
|
2
2
|
import { JoinNode } from '../../ast/join.js';
|
|
3
|
+
import { TableSourceNode } from '../../ast/query.js';
|
|
4
|
+
import { ExpressionNode } from '../../ast/expression.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Compiler for JOIN clauses in SELECT statements.
|
|
@@ -9,12 +11,12 @@ export class JoinCompiler {
|
|
|
9
11
|
static compileJoins(
|
|
10
12
|
joins: JoinNode[] | undefined,
|
|
11
13
|
ctx: CompilerContext,
|
|
12
|
-
compileFrom: (from:
|
|
13
|
-
compileExpression: (expr:
|
|
14
|
+
compileFrom: (from: TableSourceNode, ctx: CompilerContext) => string,
|
|
15
|
+
compileExpression: (expr: ExpressionNode, ctx: CompilerContext) => string
|
|
14
16
|
): string {
|
|
15
17
|
if (!joins || joins.length === 0) return '';
|
|
16
18
|
const parts = joins.map(j => {
|
|
17
|
-
const table = compileFrom(j.table
|
|
19
|
+
const table = compileFrom(j.table, ctx);
|
|
18
20
|
const cond = compileExpression(j.condition, ctx);
|
|
19
21
|
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
20
22
|
});
|
|
@@ -35,6 +35,7 @@ export class NoReturningStrategy implements ReturningStrategy {
|
|
|
35
35
|
* @throws Error indicating RETURNING is not supported.
|
|
36
36
|
*/
|
|
37
37
|
compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
|
|
38
|
+
void _ctx;
|
|
38
39
|
if (!returning || returning.length === 0) return '';
|
|
39
40
|
throw new Error('RETURNING is not supported by this dialect.');
|
|
40
41
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
OrderByNode,
|
|
12
12
|
TableNode
|
|
13
13
|
} from '../../ast/query.js';
|
|
14
|
-
import { ColumnNode } from '../../ast/expression.js';
|
|
14
|
+
import { ColumnNode, OperandNode } from '../../ast/expression.js';
|
|
15
15
|
import { FunctionTableFormatter } from './function-table-formatter.js';
|
|
16
16
|
import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
|
|
17
17
|
import { CteCompiler } from './cte-compiler.js';
|
|
@@ -134,7 +134,7 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
private compileUpdateAssignments(
|
|
137
|
-
assignments: { column: ColumnNode; value:
|
|
137
|
+
assignments: { column: ColumnNode; value: OperandNode }[],
|
|
138
138
|
table: TableNode,
|
|
139
139
|
ctx: CompilerContext
|
|
140
140
|
): string {
|
|
@@ -190,7 +190,7 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
protected compileFrom(ast: SelectQueryNode['from'], ctx?: CompilerContext): string {
|
|
193
|
-
const tableSource = ast
|
|
193
|
+
const tableSource = ast;
|
|
194
194
|
if (tableSource.type === 'FunctionTable') {
|
|
195
195
|
return this.compileFunctionTable(tableSource, ctx);
|
|
196
196
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
2
|
-
import { FunctionRenderContext } from '../../functions/types.js';
|
|
3
2
|
import { LiteralNode } from '../../ast/expression.js';
|
|
4
3
|
|
|
5
4
|
export class MssqlFunctionStrategy extends StandardFunctionStrategy {
|
|
@@ -84,27 +83,27 @@ export class MssqlFunctionStrategy extends StandardFunctionStrategy {
|
|
|
84
83
|
return `DATEPART(dw, ${compiledArgs[0]})`;
|
|
85
84
|
});
|
|
86
85
|
|
|
87
|
-
this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
|
|
88
|
-
if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
|
|
89
|
-
return `DATEPART(wk, ${compiledArgs[0]})`;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
|
|
93
|
-
if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
|
|
94
|
-
const [, date] = compiledArgs;
|
|
95
|
-
const partArg = node.args[0] as LiteralNode;
|
|
96
|
-
const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
|
|
97
|
-
// SQL Server 2022+ has DATETRUNC
|
|
98
|
-
return `DATETRUNC(${partClean}, ${date})`;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
this.add('GROUP_CONCAT', ctx => {
|
|
102
|
-
const arg = ctx.compiledArgs[0];
|
|
103
|
-
const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
|
|
104
|
-
const separator = ctx.compileOperand(separatorOperand);
|
|
105
|
-
const orderClause = this.buildOrderByExpression(ctx);
|
|
106
|
-
const withinGroup = orderClause ? ` WITHIN GROUP (${orderClause})` : '';
|
|
107
|
-
return `STRING_AGG(${arg}, ${separator})${withinGroup}`;
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
86
|
+
this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
|
|
87
|
+
if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
|
|
88
|
+
return `DATEPART(wk, ${compiledArgs[0]})`;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
|
|
92
|
+
if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
|
|
93
|
+
const [, date] = compiledArgs;
|
|
94
|
+
const partArg = node.args[0] as LiteralNode;
|
|
95
|
+
const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
|
|
96
|
+
// SQL Server 2022+ has DATETRUNC
|
|
97
|
+
return `DATETRUNC(${partClean}, ${date})`;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.add('GROUP_CONCAT', ctx => {
|
|
101
|
+
const arg = ctx.compiledArgs[0];
|
|
102
|
+
const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
|
|
103
|
+
const separator = ctx.compileOperand(separatorOperand);
|
|
104
|
+
const orderClause = this.buildOrderByExpression(ctx);
|
|
105
|
+
const withinGroup = orderClause ? ` WITHIN GROUP (${orderClause})` : '';
|
|
106
|
+
return `STRING_AGG(${arg}, ${separator})${withinGroup}`;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { CompilerContext } from '../abstract.js';
|
|
2
2
|
import {
|
|
3
3
|
SelectQueryNode,
|
|
4
|
-
DeleteQueryNode
|
|
5
|
-
TableSourceNode,
|
|
6
|
-
DerivedTableNode,
|
|
7
|
-
OrderByNode
|
|
4
|
+
DeleteQueryNode
|
|
8
5
|
} from '../../ast/query.js';
|
|
9
6
|
import { JsonPathNode } from '../../ast/expression.js';
|
|
10
7
|
import { MssqlFunctionStrategy } from './functions.js';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
2
|
-
import { FunctionRenderContext } from '../../functions/types.js';
|
|
3
2
|
import { LiteralNode } from '../../ast/expression.js';
|
|
4
3
|
|
|
5
4
|
export class MysqlFunctionStrategy extends StandardFunctionStrategy {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
2
|
-
import { FunctionRenderContext } from '../../functions/types.js';
|
|
3
2
|
import { LiteralNode } from '../../ast/expression.js';
|
|
4
3
|
|
|
5
4
|
export class PostgresFunctionStrategy extends StandardFunctionStrategy {
|
|
@@ -69,36 +68,36 @@ export class PostgresFunctionStrategy extends StandardFunctionStrategy {
|
|
|
69
68
|
return `TO_CHAR(${date}, ${format})`;
|
|
70
69
|
});
|
|
71
70
|
|
|
72
|
-
this.add('END_OF_MONTH', ({ compiledArgs }) => {
|
|
73
|
-
if (compiledArgs.length !== 1) throw new Error('END_OF_MONTH expects 1 argument');
|
|
74
|
-
return `(date_trunc('month', ${compiledArgs[0]}) + interval '1 month' - interval '1 day')::DATE`;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
this.add('DAY_OF_WEEK', ({ compiledArgs }) => {
|
|
78
|
-
if (compiledArgs.length !== 1) throw new Error('DAY_OF_WEEK expects 1 argument');
|
|
79
|
-
return `EXTRACT(DOW FROM ${compiledArgs[0]})`;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
|
|
83
|
-
if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
|
|
84
|
-
return `EXTRACT(WEEK FROM ${compiledArgs[0]})`;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
|
|
88
|
-
if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
|
|
89
|
-
const [, date] = compiledArgs;
|
|
90
|
-
const partArg = node.args[0] as LiteralNode;
|
|
91
|
-
const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
|
|
92
|
-
return `DATE_TRUNC('${partClean}', ${date})`;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
this.add('GROUP_CONCAT', ctx => {
|
|
96
|
-
const arg = ctx.compiledArgs[0];
|
|
97
|
-
const orderClause = this.buildOrderByExpression(ctx);
|
|
98
|
-
const orderSegment = orderClause ? ` ${orderClause}` : '';
|
|
99
|
-
const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
|
|
100
|
-
const separator = ctx.compileOperand(separatorOperand);
|
|
101
|
-
return `STRING_AGG(${arg}, ${separator}${orderSegment})`;
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
71
|
+
this.add('END_OF_MONTH', ({ compiledArgs }) => {
|
|
72
|
+
if (compiledArgs.length !== 1) throw new Error('END_OF_MONTH expects 1 argument');
|
|
73
|
+
return `(date_trunc('month', ${compiledArgs[0]}) + interval '1 month' - interval '1 day')::DATE`;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.add('DAY_OF_WEEK', ({ compiledArgs }) => {
|
|
77
|
+
if (compiledArgs.length !== 1) throw new Error('DAY_OF_WEEK expects 1 argument');
|
|
78
|
+
return `EXTRACT(DOW FROM ${compiledArgs[0]})`;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
|
|
82
|
+
if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
|
|
83
|
+
return `EXTRACT(WEEK FROM ${compiledArgs[0]})`;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
|
|
87
|
+
if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
|
|
88
|
+
const [, date] = compiledArgs;
|
|
89
|
+
const partArg = node.args[0] as LiteralNode;
|
|
90
|
+
const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
|
|
91
|
+
return `DATE_TRUNC('${partClean}', ${date})`;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.add('GROUP_CONCAT', ctx => {
|
|
95
|
+
const arg = ctx.compiledArgs[0];
|
|
96
|
+
const orderClause = this.buildOrderByExpression(ctx);
|
|
97
|
+
const orderSegment = orderClause ? ` ${orderClause}` : '';
|
|
98
|
+
const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
|
|
99
|
+
const separator = ctx.compileOperand(separatorOperand);
|
|
100
|
+
return `STRING_AGG(${arg}, ${separator}${orderSegment})`;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -36,6 +36,7 @@ export class PostgresDialect extends SqlDialectBase {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
39
|
+
void ctx;
|
|
39
40
|
if (!returning || returning.length === 0) return '';
|
|
40
41
|
const columns = this.formatReturningColumns(returning);
|
|
41
42
|
return ` RETURNING ${columns}`;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
1
|
+
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
2
|
+
import { LiteralNode } from '../../ast/expression.js';
|
|
3
|
+
|
|
4
|
+
export class SqliteFunctionStrategy extends StandardFunctionStrategy {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this.registerOverrides();
|
|
8
|
+
}
|
|
10
9
|
|
|
11
10
|
private registerOverrides() {
|
|
12
11
|
// Override Standard/Abstract definitions with SQLite specifics
|
|
@@ -110,13 +109,13 @@ export class SqliteFunctionStrategy extends StandardFunctionStrategy {
|
|
|
110
109
|
return `date(${date})`;
|
|
111
110
|
}
|
|
112
111
|
return `date(${date}, 'start of ${partClean}')`;
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
this.add('GROUP_CONCAT', ctx => {
|
|
116
|
-
const arg = ctx.compiledArgs[0];
|
|
117
|
-
const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
|
|
118
|
-
const separator = ctx.compileOperand(separatorOperand);
|
|
119
|
-
return `GROUP_CONCAT(${arg}, ${separator})`;
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.add('GROUP_CONCAT', ctx => {
|
|
115
|
+
const arg = ctx.compiledArgs[0];
|
|
116
|
+
const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
|
|
117
|
+
const separator = ctx.compileOperand(separatorOperand);
|
|
118
|
+
return `GROUP_CONCAT(${arg}, ${separator})`;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -37,10 +37,12 @@ export class SqliteDialect extends SqlDialectBase {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
protected compileQualifiedColumn(column: ColumnNode, _table: TableNode): string {
|
|
40
|
+
void _table;
|
|
40
41
|
return this.quoteIdentifier(column.name);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
45
|
+
void ctx;
|
|
44
46
|
if (!returning || returning.length === 0) return '';
|
|
45
47
|
const columns = this.formatReturningColumns(returning);
|
|
46
48
|
return ` RETURNING ${columns}`;
|
|
@@ -8,7 +8,7 @@ export interface MysqlClientLike {
|
|
|
8
8
|
query(
|
|
9
9
|
sql: string,
|
|
10
10
|
params?: unknown[]
|
|
11
|
-
): Promise<[
|
|
11
|
+
): Promise<[unknown, unknown?]>; // rows, metadata
|
|
12
12
|
beginTransaction?(): Promise<void>;
|
|
13
13
|
commit?(): Promise<void>;
|
|
14
14
|
rollback?(): Promise<void>;
|
|
@@ -27,7 +27,7 @@ export function createMysqlExecutor(
|
|
|
27
27
|
transactions: supportsTransactions,
|
|
28
28
|
},
|
|
29
29
|
async executeSql(sql, params) {
|
|
30
|
-
const [rows] = await client.query(sql, params
|
|
30
|
+
const [rows] = await client.query(sql, params);
|
|
31
31
|
|
|
32
32
|
if (!Array.isArray(rows)) {
|
|
33
33
|
// e.g. insert/update returning only headers, treat as no rows
|
|
@@ -16,7 +16,7 @@ export function createPostgresExecutor(
|
|
|
16
16
|
): DbExecutor {
|
|
17
17
|
return createExecutorFromQueryRunner({
|
|
18
18
|
async query(sql, params) {
|
|
19
|
-
const { rows } = await client.query(sql, params
|
|
19
|
+
const { rows } = await client.query(sql, params);
|
|
20
20
|
return rows;
|
|
21
21
|
},
|
|
22
22
|
async beginTransaction() {
|
|
@@ -49,6 +49,7 @@ export class Pool<TResource> {
|
|
|
49
49
|
}, interval);
|
|
50
50
|
|
|
51
51
|
// Best-effort: avoid keeping the event loop alive.
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
53
|
(this.reapTimer as any).unref?.();
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -100,6 +101,7 @@ export class Pool<TResource> {
|
|
|
100
101
|
if (idx >= 0) this.waiters.splice(idx, 1);
|
|
101
102
|
waiter.reject(new Error('Pool acquire timeout'));
|
|
102
103
|
}, timeout);
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
105
|
(timer as any).unref?.();
|
|
104
106
|
}
|
|
105
107
|
|
|
@@ -6,7 +6,7 @@ import { FunctionNode, OperandNode, isOperandNode } from '../ast/expression.js';
|
|
|
6
6
|
|
|
7
7
|
type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
|
|
8
8
|
|
|
9
|
-
const isColumnDef = (val:
|
|
9
|
+
const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
|
|
10
10
|
|
|
11
11
|
const toOperand = (input: OperandInput): OperandNode => {
|
|
12
12
|
if (isOperandNode(input)) return input;
|
|
@@ -6,7 +6,7 @@ import { FunctionNode, OperandNode, isOperandNode } from '../ast/expression.js';
|
|
|
6
6
|
|
|
7
7
|
type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
|
|
8
8
|
|
|
9
|
-
const isColumnDef = (val:
|
|
9
|
+
const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
|
|
10
10
|
|
|
11
11
|
const toOperand = (input: OperandInput): OperandNode => {
|
|
12
12
|
if (isOperandNode(input)) return input;
|
|
@@ -6,7 +6,7 @@ import { FunctionNode, OperandNode, isOperandNode } from '../ast/expression.js';
|
|
|
6
6
|
|
|
7
7
|
type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
|
|
8
8
|
|
|
9
|
-
const isColumnDef = (val:
|
|
9
|
+
const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
|
|
10
10
|
|
|
11
11
|
const toOperand = (input: OperandInput): OperandNode => {
|
|
12
12
|
if (isOperandNode(input)) return input;
|
|
@@ -18,11 +18,14 @@ import {
|
|
|
18
18
|
RelationMetadata
|
|
19
19
|
} from '../orm/entity-metadata.js';
|
|
20
20
|
|
|
21
|
+
import { tableRef, type TableRef } from '../schema/table.js';
|
|
22
|
+
|
|
21
23
|
const isTableDef = (value: unknown): value is TableDef => {
|
|
22
24
|
return typeof value === 'object' && value !== null && 'columns' in (value as TableDef);
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
26
29
|
if (typeof target === 'function' && (target as Function).prototype === undefined) {
|
|
27
30
|
return (target as () => EntityOrTableTarget)();
|
|
28
31
|
}
|
|
@@ -31,22 +34,22 @@ const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget
|
|
|
31
34
|
|
|
32
35
|
const resolveTableTarget = (
|
|
33
36
|
target: EntityOrTableTargetResolver,
|
|
34
|
-
tableMap: Map<EntityConstructor
|
|
37
|
+
tableMap: Map<EntityConstructor, TableDef>
|
|
35
38
|
): TableDef => {
|
|
36
39
|
const resolved = unwrapTarget(target);
|
|
37
40
|
if (isTableDef(resolved)) {
|
|
38
41
|
return resolved;
|
|
39
42
|
}
|
|
40
|
-
const table = tableMap.get(resolved as EntityConstructor
|
|
43
|
+
const table = tableMap.get(resolved as EntityConstructor);
|
|
41
44
|
if (!table) {
|
|
42
|
-
throw new Error(`Entity '${(resolved as EntityConstructor
|
|
45
|
+
throw new Error(`Entity '${(resolved as EntityConstructor).name}' is not registered with decorators`);
|
|
43
46
|
}
|
|
44
47
|
return table;
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
const buildRelationDefinitions = (
|
|
48
51
|
meta: { relations: Record<string, RelationMetadata> },
|
|
49
|
-
tableMap: Map<EntityConstructor
|
|
52
|
+
tableMap: Map<EntityConstructor, TableDef>
|
|
50
53
|
): Record<string, RelationDef> => {
|
|
51
54
|
const relations: Record<string, RelationDef> = {};
|
|
52
55
|
|
|
@@ -103,7 +106,7 @@ const buildRelationDefinitions = (
|
|
|
103
106
|
|
|
104
107
|
export const bootstrapEntities = (): TableDef[] => {
|
|
105
108
|
const metas = getAllEntityMetadata();
|
|
106
|
-
const tableMap = new Map<EntityConstructor
|
|
109
|
+
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
107
110
|
|
|
108
111
|
for (const meta of metas) {
|
|
109
112
|
const table = buildTableDef(meta);
|
|
@@ -119,7 +122,7 @@ export const bootstrapEntities = (): TableDef[] => {
|
|
|
119
122
|
return metas.map(meta => meta.table!) as TableDef[];
|
|
120
123
|
};
|
|
121
124
|
|
|
122
|
-
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor
|
|
125
|
+
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
|
|
123
126
|
const meta = getEntityMetadata(ctor);
|
|
124
127
|
if (!meta) return undefined;
|
|
125
128
|
if (!meta.table) {
|
|
@@ -129,11 +132,27 @@ export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor:
|
|
|
129
132
|
};
|
|
130
133
|
|
|
131
134
|
export const selectFromEntity = <TTable extends TableDef = TableDef>(
|
|
132
|
-
ctor: EntityConstructor
|
|
133
|
-
): SelectQueryBuilder<
|
|
135
|
+
ctor: EntityConstructor
|
|
136
|
+
): SelectQueryBuilder<unknown, TTable> => {
|
|
134
137
|
const table = getTableDefFromEntity(ctor);
|
|
135
138
|
if (!table) {
|
|
136
139
|
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
137
140
|
}
|
|
138
141
|
return new SelectQueryBuilder(table as TTable);
|
|
139
142
|
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Public API: opt-in ergonomic entity reference (decorator-level).
|
|
146
|
+
*
|
|
147
|
+
* Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
|
|
148
|
+
* `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
|
|
149
|
+
*/
|
|
150
|
+
export const entityRef = <TTable extends TableDef = TableDef>(
|
|
151
|
+
ctor: EntityConstructor
|
|
152
|
+
): TableRef<TTable> => {
|
|
153
|
+
const table = getTableDefFromEntity<TTable>(ctor);
|
|
154
|
+
if (!table) {
|
|
155
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
156
|
+
}
|
|
157
|
+
return tableRef(table);
|
|
158
|
+
};
|
package/src/decorators/column.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
DualModePropertyDecorator,
|
|
10
10
|
getOrCreateMetadataBag,
|
|
11
11
|
isStandardDecoratorContext,
|
|
12
|
-
registerInitializer,
|
|
13
12
|
StandardDecoratorContext
|
|
14
13
|
} from './decorator-metadata.js';
|
|
15
14
|
|
|
@@ -21,7 +20,7 @@ export interface ColumnOptions {
|
|
|
21
20
|
tsType?: ColumnDef['tsType'];
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
export type ColumnInput = ColumnOptions | ColumnDef
|
|
23
|
+
export type ColumnInput = ColumnOptions | ColumnDef;
|
|
25
24
|
|
|
26
25
|
const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
|
|
27
26
|
const asOptions = input as ColumnOptions;
|
|
@@ -60,8 +59,8 @@ const resolveConstructor = (target: unknown): EntityConstructor | undefined => {
|
|
|
60
59
|
return target as EntityConstructor;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
if (target && typeof (target as
|
|
64
|
-
return (target as
|
|
62
|
+
if (target && typeof (target as { constructor: unknown }).constructor === 'function') {
|
|
63
|
+
return (target as { constructor: unknown }).constructor as EntityConstructor;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
return undefined;
|
|
@@ -88,13 +87,6 @@ const registerColumnFromContext = (
|
|
|
88
87
|
bag.columns.push({ propertyName, column: { ...column } });
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
registerInitializer(context, function () {
|
|
92
|
-
const ctor = resolveConstructor(this);
|
|
93
|
-
if (!ctor) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
registerColumn(ctor, propertyName, column);
|
|
97
|
-
});
|
|
98
90
|
};
|
|
99
91
|
|
|
100
92
|
export function Column(definition: ColumnInput) {
|
|
@@ -4,7 +4,6 @@ export interface StandardDecoratorContext {
|
|
|
4
4
|
kind: string;
|
|
5
5
|
name?: string | symbol;
|
|
6
6
|
metadata?: Record<PropertyKey, unknown>;
|
|
7
|
-
addInitializer?(initializer: (this: unknown) => void): void;
|
|
8
7
|
static?: boolean;
|
|
9
8
|
private?: boolean;
|
|
10
9
|
}
|
|
@@ -15,7 +14,9 @@ export interface DualModePropertyDecorator {
|
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export interface DualModeClassDecorator {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
18
18
|
<TFunction extends Function>(value: TFunction): void | TFunction;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
19
20
|
<TFunction extends Function>(value: TFunction, context: StandardDecoratorContext): void | TFunction;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -27,7 +28,7 @@ export interface DecoratorMetadataBag {
|
|
|
27
28
|
const METADATA_KEY = 'metal-orm:decorators';
|
|
28
29
|
|
|
29
30
|
export const isStandardDecoratorContext = (value: unknown): value is StandardDecoratorContext => {
|
|
30
|
-
return typeof value === 'object' && value !== null && 'kind' in (value as
|
|
31
|
+
return typeof value === 'object' && value !== null && 'kind' in (value as object);
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
export const getOrCreateMetadataBag = (context: StandardDecoratorContext): DecoratorMetadataBag => {
|
|
@@ -44,10 +45,3 @@ export const getOrCreateMetadataBag = (context: StandardDecoratorContext): Decor
|
|
|
44
45
|
export const readMetadataBag = (context: StandardDecoratorContext): DecoratorMetadataBag | undefined => {
|
|
45
46
|
return context.metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
|
|
46
47
|
};
|
|
47
|
-
|
|
48
|
-
export const registerInitializer = (
|
|
49
|
-
context: StandardDecoratorContext,
|
|
50
|
-
initializer: (this: unknown) => void
|
|
51
|
-
): void => {
|
|
52
|
-
context.addInitializer?.(initializer);
|
|
53
|
-
};
|
package/src/decorators/entity.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TableHooks } from '../schema/table.js';
|
|
2
|
+
import { RelationKinds } from '../schema/relation.js';
|
|
2
3
|
import {
|
|
3
4
|
addColumnMetadata,
|
|
4
5
|
addRelationMetadata,
|
|
@@ -22,7 +23,7 @@ const toSnakeCase = (value: string): string => {
|
|
|
22
23
|
.toLowerCase();
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
const deriveTableNameFromConstructor = (ctor:
|
|
26
|
+
const deriveTableNameFromConstructor = (ctor: EntityConstructor<unknown>): string => {
|
|
26
27
|
const fallback = 'unknown';
|
|
27
28
|
const rawName = ctor.name || fallback;
|
|
28
29
|
const strippedName = rawName.replace(/Entity$/i, '');
|
|
@@ -50,14 +51,29 @@ export function Entity(options: EntityOptions = {}) {
|
|
|
50
51
|
if (bag) {
|
|
51
52
|
const meta = ensureEntityMetadata(ctor);
|
|
52
53
|
for (const entry of bag.columns) {
|
|
53
|
-
if (
|
|
54
|
-
|
|
54
|
+
if (meta.columns[entry.propertyName]) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
|
|
57
|
+
);
|
|
55
58
|
}
|
|
59
|
+
addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
|
|
56
60
|
}
|
|
57
61
|
for (const entry of bag.relations) {
|
|
58
|
-
if (
|
|
59
|
-
|
|
62
|
+
if (meta.relations[entry.propertyName]) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
|
|
65
|
+
);
|
|
60
66
|
}
|
|
67
|
+
const relationCopy =
|
|
68
|
+
entry.relation.kind === RelationKinds.BelongsToMany
|
|
69
|
+
? {
|
|
70
|
+
...entry.relation,
|
|
71
|
+
defaultPivotColumns: entry.relation.defaultPivotColumns
|
|
72
|
+
? [...entry.relation.defaultPivotColumns]
|
|
73
|
+
: undefined
|
|
74
|
+
}
|
|
75
|
+
: { ...entry.relation };
|
|
76
|
+
addRelationMetadata(ctor, entry.propertyName, relationCopy);
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
DualModePropertyDecorator,
|
|
10
10
|
getOrCreateMetadataBag,
|
|
11
11
|
isStandardDecoratorContext,
|
|
12
|
-
registerInitializer,
|
|
13
12
|
StandardDecoratorContext
|
|
14
13
|
} from './decorator-metadata.js';
|
|
15
14
|
|
|
@@ -54,8 +53,8 @@ const resolveConstructor = (instanceOrCtor: unknown): EntityConstructor | undefi
|
|
|
54
53
|
if (typeof instanceOrCtor === 'function') {
|
|
55
54
|
return instanceOrCtor as EntityConstructor;
|
|
56
55
|
}
|
|
57
|
-
if (instanceOrCtor && typeof (instanceOrCtor as
|
|
58
|
-
return (instanceOrCtor as
|
|
56
|
+
if (instanceOrCtor && typeof (instanceOrCtor as { constructor: new (...args: unknown[]) => unknown }).constructor === 'function') {
|
|
57
|
+
return (instanceOrCtor as { constructor: new (...args: unknown[]) => unknown }).constructor as EntityConstructor;
|
|
59
58
|
}
|
|
60
59
|
return undefined;
|
|
61
60
|
};
|
|
@@ -80,14 +79,6 @@ const createFieldDecorator = (
|
|
|
80
79
|
if (!bag.relations.some(entry => entry.propertyName === propertyName)) {
|
|
81
80
|
bag.relations.push({ propertyName, relation: relationMetadata });
|
|
82
81
|
}
|
|
83
|
-
|
|
84
|
-
registerInitializer(ctx, function () {
|
|
85
|
-
const ctor = resolveConstructor(this);
|
|
86
|
-
if (!ctor) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
registerRelation(ctor, propertyName, relationMetadata);
|
|
90
|
-
});
|
|
91
82
|
return;
|
|
92
83
|
}
|
|
93
84
|
|