metal-orm 1.0.42 → 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.
Files changed (86) hide show
  1. package/README.md +22 -7
  2. package/dist/index.cjs +130 -74
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +121 -96
  5. package/dist/index.d.ts +121 -96
  6. package/dist/index.js +128 -74
  7. package/dist/index.js.map +1 -1
  8. package/package.json +8 -2
  9. package/scripts/run-eslint.mjs +34 -0
  10. package/src/codegen/typescript.ts +32 -15
  11. package/src/core/ast/builders.ts +7 -2
  12. package/src/core/ast/expression-builders.ts +0 -2
  13. package/src/core/ast/expression-nodes.ts +14 -5
  14. package/src/core/ast/expression-visitor.ts +11 -8
  15. package/src/core/ast/join-node.ts +1 -1
  16. package/src/core/ast/query.ts +6 -6
  17. package/src/core/ast/window-functions.ts +10 -2
  18. package/src/core/ddl/dialects/base-schema-dialect.ts +30 -3
  19. package/src/core/ddl/dialects/mssql-schema-dialect.ts +4 -0
  20. package/src/core/ddl/dialects/mysql-schema-dialect.ts +2 -0
  21. package/src/core/ddl/dialects/postgres-schema-dialect.ts +13 -1
  22. package/src/core/ddl/dialects/render-reference.test.ts +69 -0
  23. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
  24. package/src/core/ddl/introspect/mssql.ts +42 -8
  25. package/src/core/ddl/introspect/mysql.ts +30 -6
  26. package/src/core/ddl/introspect/postgres.ts +88 -34
  27. package/src/core/ddl/introspect/run-select.ts +6 -4
  28. package/src/core/ddl/introspect/sqlite.ts +56 -11
  29. package/src/core/ddl/introspect/types.ts +0 -1
  30. package/src/core/ddl/introspect/utils.ts +3 -3
  31. package/src/core/ddl/schema-dialect.ts +1 -0
  32. package/src/core/ddl/schema-generator.ts +4 -12
  33. package/src/core/ddl/sql-writing.ts +4 -4
  34. package/src/core/dialect/abstract.ts +18 -6
  35. package/src/core/dialect/base/function-table-formatter.ts +3 -2
  36. package/src/core/dialect/base/join-compiler.ts +5 -3
  37. package/src/core/dialect/base/returning-strategy.ts +1 -0
  38. package/src/core/dialect/base/sql-dialect.ts +3 -3
  39. package/src/core/dialect/mssql/functions.ts +24 -25
  40. package/src/core/dialect/mssql/index.ts +1 -4
  41. package/src/core/dialect/mysql/functions.ts +0 -1
  42. package/src/core/dialect/postgres/functions.ts +33 -34
  43. package/src/core/dialect/postgres/index.ts +1 -0
  44. package/src/core/dialect/sqlite/functions.ts +18 -19
  45. package/src/core/dialect/sqlite/index.ts +2 -0
  46. package/src/core/execution/db-executor.ts +1 -1
  47. package/src/core/execution/executors/mysql-executor.ts +2 -2
  48. package/src/core/execution/executors/postgres-executor.ts +1 -1
  49. package/src/core/execution/pooling/pool.ts +2 -0
  50. package/src/core/functions/datetime.ts +1 -1
  51. package/src/core/functions/numeric.ts +1 -1
  52. package/src/core/functions/text.ts +1 -1
  53. package/src/decorators/bootstrap.ts +27 -8
  54. package/src/decorators/column.ts +3 -11
  55. package/src/decorators/decorator-metadata.ts +3 -9
  56. package/src/decorators/entity.ts +21 -5
  57. package/src/decorators/relations.ts +2 -11
  58. package/src/orm/entity-context.ts +8 -8
  59. package/src/orm/entity-meta.ts +8 -8
  60. package/src/orm/entity-metadata.ts +11 -9
  61. package/src/orm/entity.ts +28 -29
  62. package/src/orm/execute.ts +4 -4
  63. package/src/orm/hydration.ts +42 -39
  64. package/src/orm/identity-map.ts +1 -1
  65. package/src/orm/lazy-batch.ts +9 -9
  66. package/src/orm/orm-session.ts +24 -23
  67. package/src/orm/orm.ts +2 -5
  68. package/src/orm/relation-change-processor.ts +12 -11
  69. package/src/orm/relations/belongs-to.ts +11 -11
  70. package/src/orm/relations/has-many.ts +10 -10
  71. package/src/orm/relations/has-one.ts +8 -7
  72. package/src/orm/relations/many-to-many.ts +13 -13
  73. package/src/orm/runtime-types.ts +4 -4
  74. package/src/orm/save-graph.ts +31 -25
  75. package/src/orm/unit-of-work.ts +17 -17
  76. package/src/query-builder/delete.ts +4 -3
  77. package/src/query-builder/hydration-manager.ts +6 -5
  78. package/src/query-builder/insert.ts +12 -8
  79. package/src/query-builder/query-ast-service.ts +2 -2
  80. package/src/query-builder/raw-column-parser.ts +2 -1
  81. package/src/query-builder/select-helpers.ts +2 -2
  82. package/src/query-builder/select.ts +31 -31
  83. package/src/query-builder/update.ts +4 -3
  84. package/src/schema/column.ts +26 -26
  85. package/src/schema/table.ts +47 -18
  86. package/src/schema/types.ts +22 -22
@@ -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 { FunctionRenderContext } from '../../functions/types.js';
3
- import { LiteralNode } from '../../ast/expression.js';
4
-
5
- export class SqliteFunctionStrategy extends StandardFunctionStrategy {
6
- constructor() {
7
- super();
8
- this.registerOverrides();
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}`;
@@ -36,7 +36,7 @@ export function rowsToQueryResult(
36
36
  }
37
37
 
38
38
  const columns = Object.keys(rows[0]);
39
- const values = rows.map(row => columns.map(c => (row as any)[c]));
39
+ const values = rows.map(row => columns.map(c => row[c]));
40
40
  return { columns, values };
41
41
  }
42
42
 
@@ -8,7 +8,7 @@ export interface MysqlClientLike {
8
8
  query(
9
9
  sql: string,
10
10
  params?: unknown[]
11
- ): Promise<[any, any?]>; // rows, metadata
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 as any[]);
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 as any[]);
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: any): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in 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: any): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in 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: any): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in 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<any>, TableDef>
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<any>);
43
+ const table = tableMap.get(resolved as EntityConstructor);
41
44
  if (!table) {
42
- throw new Error(`Entity '${(resolved as EntityConstructor<any>).name}' is not registered with decorators`);
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<any>, TableDef>
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<any>, TableDef>();
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<any>): TTable | undefined => {
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<any>
133
- ): SelectQueryBuilder<any, TTable> => {
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
+ };
@@ -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<any, any>;
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 any).constructor === 'function') {
64
- return (target as any).constructor as EntityConstructor;
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 any);
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
- };
@@ -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: Function): string => {
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 (!meta.columns[entry.propertyName]) {
54
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
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 (!meta.relations[entry.propertyName]) {
59
- addRelationMetadata(ctor, entry.propertyName, entry.relation);
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 any).constructor === 'function') {
58
- return (instanceOrCtor as any).constructor as EntityConstructor;
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
 
@@ -8,23 +8,23 @@ export interface EntityContext {
8
8
  dialect: Dialect;
9
9
  executor: DbExecutor;
10
10
 
11
- getEntity(table: TableDef, pk: any): any;
12
- setEntity(table: TableDef, pk: any, entity: any): void;
11
+ getEntity(table: TableDef, pk: unknown): unknown;
12
+ setEntity(table: TableDef, pk: unknown, entity: unknown): void;
13
13
 
14
- trackNew(table: TableDef, entity: any, pk?: any): void;
15
- trackManaged(table: TableDef, pk: any, entity: any): void;
14
+ trackNew(table: TableDef, entity: unknown, pk?: unknown): void;
15
+ trackManaged(table: TableDef, pk: unknown, entity: unknown): void;
16
16
 
17
- markDirty(entity: any): void;
18
- markRemoved(entity: any): void;
17
+ markDirty(entity: unknown): void;
18
+ markRemoved(entity: unknown): void;
19
19
 
20
20
  getEntitiesForTable(table: TableDef): TrackedEntity[];
21
21
 
22
22
  registerRelationChange(
23
- root: any,
23
+ root: unknown,
24
24
  relationKey: RelationKey,
25
25
  rootTable: TableDef,
26
26
  relationName: string,
27
27
  relation: RelationDef,
28
- change: RelationChange<any>
28
+ change: RelationChange<unknown>
29
29
  ): void;
30
30
  }
@@ -21,9 +21,9 @@ export interface EntityMeta<TTable extends TableDef> {
21
21
  /** Relations that should be loaded lazily */
22
22
  lazyRelations: (keyof RelationMap<TTable>)[];
23
23
  /** Cache for relation promises */
24
- relationCache: Map<string, Promise<any>>;
24
+ relationCache: Map<string, Promise<unknown>>;
25
25
  /** Hydration data for relations */
26
- relationHydration: Map<string, Map<string, any>>;
26
+ relationHydration: Map<string, Map<string, unknown>>;
27
27
  /** Relation wrapper instances */
28
28
  relationWrappers: Map<string, unknown>;
29
29
  }
@@ -40,7 +40,7 @@ export const getHydrationRows = <TTable extends TableDef>(
40
40
  meta: EntityMeta<TTable>,
41
41
  relationName: string,
42
42
  key: unknown
43
- ): Record<string, any>[] | undefined => {
43
+ ): Record<string, unknown>[] | undefined => {
44
44
  const map = meta.relationHydration.get(relationName);
45
45
  if (!map) return undefined;
46
46
  const rows = map.get(toKey(key));
@@ -60,7 +60,7 @@ export const getHydrationRecord = <TTable extends TableDef>(
60
60
  meta: EntityMeta<TTable>,
61
61
  relationName: string,
62
62
  key: unknown
63
- ): Record<string, any> | undefined => {
63
+ ): Record<string, unknown> | undefined => {
64
64
  const map = meta.relationHydration.get(relationName);
65
65
  if (!map) return undefined;
66
66
  const value = map.get(toKey(key));
@@ -68,7 +68,7 @@ export const getHydrationRecord = <TTable extends TableDef>(
68
68
  if (Array.isArray(value)) {
69
69
  return value[0];
70
70
  }
71
- return value;
71
+ return value as Record<string, unknown>;
72
72
  };
73
73
 
74
74
  /**
@@ -77,9 +77,9 @@ export const getHydrationRecord = <TTable extends TableDef>(
77
77
  * @returns Entity metadata or undefined if not found
78
78
  * @typeParam TTable - Table definition type
79
79
  */
80
- export const getEntityMeta = <TTable extends TableDef>(entity: any): EntityMeta<TTable> | undefined => {
80
+ export const getEntityMeta = <TTable extends TableDef>(entity: unknown): EntityMeta<TTable> | undefined => {
81
81
  if (!entity || typeof entity !== 'object') return undefined;
82
- return (entity as any)[ENTITY_META];
82
+ return (entity as { [ENTITY_META]: EntityMeta<TTable> })[ENTITY_META];
83
83
  };
84
84
 
85
85
  /**
@@ -87,6 +87,6 @@ export const getEntityMeta = <TTable extends TableDef>(entity: any): EntityMeta<
87
87
  * @param entity - Entity instance to check
88
88
  * @returns True if the entity has metadata, false otherwise
89
89
  */
90
- export const hasEntityMeta = (entity: any): entity is { [ENTITY_META]: EntityMeta<TableDef> } => {
90
+ export const hasEntityMeta = (entity: unknown): entity is { [ENTITY_META]: EntityMeta<TableDef> } => {
91
91
  return Boolean(getEntityMeta(entity));
92
92
  };