metal-orm 1.0.42 → 1.0.44

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 (122) hide show
  1. package/README.md +195 -37
  2. package/dist/index.cjs +1014 -538
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1267 -371
  5. package/dist/index.d.ts +1267 -371
  6. package/dist/index.js +1012 -536
  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/adapters.ts +8 -2
  12. package/src/core/ast/builders.ts +105 -76
  13. package/src/core/ast/expression-builders.ts +430 -392
  14. package/src/core/ast/expression-nodes.ts +14 -5
  15. package/src/core/ast/expression-visitor.ts +56 -14
  16. package/src/core/ast/helpers.ts +23 -0
  17. package/src/core/ast/join-node.ts +18 -2
  18. package/src/core/ast/query.ts +6 -6
  19. package/src/core/ast/window-functions.ts +10 -2
  20. package/src/core/ddl/dialects/base-schema-dialect.ts +37 -4
  21. package/src/core/ddl/dialects/index.ts +1 -0
  22. package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
  23. package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
  24. package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
  25. package/src/core/ddl/dialects/render-reference.test.ts +69 -0
  26. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
  27. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  28. package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
  29. package/src/core/ddl/introspect/context.ts +6 -0
  30. package/src/core/ddl/introspect/functions/postgres.ts +13 -0
  31. package/src/core/ddl/introspect/mssql.ts +53 -8
  32. package/src/core/ddl/introspect/mysql.ts +32 -6
  33. package/src/core/ddl/introspect/postgres.ts +102 -34
  34. package/src/core/ddl/introspect/registry.ts +14 -0
  35. package/src/core/ddl/introspect/run-select.ts +19 -4
  36. package/src/core/ddl/introspect/sqlite.ts +78 -11
  37. package/src/core/ddl/introspect/types.ts +0 -1
  38. package/src/core/ddl/introspect/utils.ts +21 -3
  39. package/src/core/ddl/naming-strategy.ts +6 -0
  40. package/src/core/ddl/schema-dialect.ts +20 -6
  41. package/src/core/ddl/schema-diff.ts +22 -0
  42. package/src/core/ddl/schema-generator.ts +26 -12
  43. package/src/core/ddl/schema-plan-executor.ts +6 -0
  44. package/src/core/ddl/schema-types.ts +6 -0
  45. package/src/core/ddl/sql-writing.ts +4 -4
  46. package/src/core/dialect/abstract.ts +19 -7
  47. package/src/core/dialect/base/function-table-formatter.ts +3 -2
  48. package/src/core/dialect/base/join-compiler.ts +5 -3
  49. package/src/core/dialect/base/returning-strategy.ts +1 -0
  50. package/src/core/dialect/base/sql-dialect.ts +3 -3
  51. package/src/core/dialect/mssql/functions.ts +24 -25
  52. package/src/core/dialect/mssql/index.ts +1 -4
  53. package/src/core/dialect/mysql/functions.ts +0 -1
  54. package/src/core/dialect/postgres/functions.ts +33 -34
  55. package/src/core/dialect/postgres/index.ts +1 -0
  56. package/src/core/dialect/sqlite/functions.ts +18 -19
  57. package/src/core/dialect/sqlite/index.ts +2 -0
  58. package/src/core/execution/db-executor.ts +1 -1
  59. package/src/core/execution/executors/mysql-executor.ts +2 -2
  60. package/src/core/execution/executors/postgres-executor.ts +1 -1
  61. package/src/core/execution/pooling/pool.ts +12 -5
  62. package/src/core/functions/datetime.ts +58 -34
  63. package/src/core/functions/numeric.ts +96 -31
  64. package/src/core/functions/standard-strategy.ts +35 -0
  65. package/src/core/functions/text.ts +84 -23
  66. package/src/core/functions/types.ts +23 -8
  67. package/src/decorators/bootstrap.ts +42 -11
  68. package/src/decorators/column.ts +20 -11
  69. package/src/decorators/decorator-metadata.ts +30 -9
  70. package/src/decorators/entity.ts +29 -5
  71. package/src/decorators/index.ts +3 -0
  72. package/src/decorators/relations.ts +34 -11
  73. package/src/orm/als.ts +34 -9
  74. package/src/orm/entity-context.ts +62 -8
  75. package/src/orm/entity-meta.ts +8 -8
  76. package/src/orm/entity-metadata.ts +131 -16
  77. package/src/orm/entity.ts +28 -29
  78. package/src/orm/execute.ts +19 -4
  79. package/src/orm/hydration.ts +42 -39
  80. package/src/orm/identity-map.ts +1 -1
  81. package/src/orm/lazy-batch.ts +74 -104
  82. package/src/orm/orm-session.ts +24 -23
  83. package/src/orm/orm.ts +2 -5
  84. package/src/orm/relation-change-processor.ts +12 -11
  85. package/src/orm/relations/belongs-to.ts +11 -11
  86. package/src/orm/relations/has-many.ts +54 -10
  87. package/src/orm/relations/has-one.ts +8 -7
  88. package/src/orm/relations/many-to-many.ts +13 -13
  89. package/src/orm/runtime-types.ts +4 -4
  90. package/src/orm/save-graph.ts +31 -25
  91. package/src/orm/unit-of-work.ts +17 -17
  92. package/src/query/index.ts +74 -0
  93. package/src/query/target.ts +46 -0
  94. package/src/query-builder/delete-query-state.ts +30 -0
  95. package/src/query-builder/delete.ts +64 -18
  96. package/src/query-builder/hydration-manager.ts +52 -5
  97. package/src/query-builder/insert-query-state.ts +30 -0
  98. package/src/query-builder/insert.ts +58 -10
  99. package/src/query-builder/query-ast-service.ts +7 -2
  100. package/src/query-builder/query-resolution.ts +78 -0
  101. package/src/query-builder/raw-column-parser.ts +7 -1
  102. package/src/query-builder/relation-alias.ts +7 -0
  103. package/src/query-builder/relation-conditions.ts +61 -48
  104. package/src/query-builder/relation-service.ts +68 -63
  105. package/src/query-builder/relation-utils.ts +3 -0
  106. package/src/query-builder/select/cte-facet.ts +40 -0
  107. package/src/query-builder/select/from-facet.ts +80 -0
  108. package/src/query-builder/select/join-facet.ts +62 -0
  109. package/src/query-builder/select/predicate-facet.ts +103 -0
  110. package/src/query-builder/select/projection-facet.ts +69 -0
  111. package/src/query-builder/select/relation-facet.ts +81 -0
  112. package/src/query-builder/select/setop-facet.ts +36 -0
  113. package/src/query-builder/select-helpers.ts +15 -2
  114. package/src/query-builder/select-query-builder-deps.ts +19 -1
  115. package/src/query-builder/select-query-state.ts +2 -1
  116. package/src/query-builder/select.ts +795 -1163
  117. package/src/query-builder/update-query-state.ts +52 -0
  118. package/src/query-builder/update.ts +69 -18
  119. package/src/schema/column.ts +26 -26
  120. package/src/schema/table-guards.ts +31 -0
  121. package/src/schema/table.ts +47 -18
  122. package/src/schema/types.ts +22 -22
@@ -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;
@@ -23,22 +23,30 @@ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
23
23
  });
24
24
 
25
25
  /**
26
- * Helper: LOWER(str)
26
+ * Converts a string to lowercase.
27
+ * @param value - The string value.
28
+ * @returns A FunctionNode representing the LOWER SQL function.
27
29
  */
28
30
  export const lower = (value: OperandInput): FunctionNode => fn('LOWER', [value]);
29
31
 
30
32
  /**
31
- * Helper: UPPER(str)
33
+ * Converts a string to uppercase.
34
+ * @param value - The string value.
35
+ * @returns A FunctionNode representing the UPPER SQL function.
32
36
  */
33
37
  export const upper = (value: OperandInput): FunctionNode => fn('UPPER', [value]);
34
38
 
35
39
  /**
36
- * Helper: ASCII(str)
40
+ * Returns the ASCII code of the first character of a string.
41
+ * @param value - The string value.
42
+ * @returns A FunctionNode representing the ASCII SQL function.
37
43
  */
38
44
  export const ascii = (value: OperandInput): FunctionNode => fn('ASCII', [value]);
39
45
 
40
46
  /**
41
- * Helper: CHAR(code[, code...])
47
+ * Returns a string from one or more ASCII codes.
48
+ * @param codes - The ASCII codes.
49
+ * @returns A FunctionNode representing the CHAR SQL function.
42
50
  */
43
51
  export const char = (...codes: OperandInput[]): FunctionNode => {
44
52
  if (codes.length === 0) throw new Error('char() expects at least 1 argument');
@@ -46,33 +54,46 @@ export const char = (...codes: OperandInput[]): FunctionNode => {
46
54
  };
47
55
 
48
56
  /**
49
- * Helper: CHAR_LENGTH(str)
57
+ * Returns the number of characters in a string.
58
+ * @param value - The string value.
59
+ * @returns A FunctionNode representing the CHAR_LENGTH SQL function.
50
60
  */
51
61
  export const charLength = (value: OperandInput): FunctionNode => fn('CHAR_LENGTH', [value]);
52
62
 
53
63
  /**
54
- * Helper: LENGTH(str)
64
+ * Returns the length of a string in bytes or characters.
65
+ * @param value - The string value.
66
+ * @returns A FunctionNode representing the LENGTH SQL function.
55
67
  */
56
68
  export const length = (value: OperandInput): FunctionNode => fn('LENGTH', [value]);
57
69
 
58
70
  /**
59
- * Helper: TRIM([chars FROM] str)
71
+ * Removes leading and trailing whitespace or specified characters from a string.
72
+ * @param value - The string value.
73
+ * @param chars - The characters to trim (optional).
74
+ * @returns A FunctionNode representing the TRIM SQL function.
60
75
  */
61
76
  export const trim = (value: OperandInput, chars?: OperandInput): FunctionNode =>
62
77
  chars === undefined ? fn('TRIM', [value]) : fn('TRIM', [value, chars]);
63
78
 
64
79
  /**
65
- * Helper: LTRIM(str)
80
+ * Removes leading whitespace from a string.
81
+ * @param value - The string value.
82
+ * @returns A FunctionNode representing the LTRIM SQL function.
66
83
  */
67
84
  export const ltrim = (value: OperandInput): FunctionNode => fn('LTRIM', [value]);
68
85
 
69
86
  /**
70
- * Helper: RTRIM(str)
87
+ * Removes trailing whitespace from a string.
88
+ * @param value - The string value.
89
+ * @returns A FunctionNode representing the RTRIM SQL function.
71
90
  */
72
91
  export const rtrim = (value: OperandInput): FunctionNode => fn('RTRIM', [value]);
73
92
 
74
93
  /**
75
- * Helper: CONCAT(arg1, arg2, ...)
94
+ * Concatenates two or more strings.
95
+ * @param args - The strings to concatenate.
96
+ * @returns A FunctionNode representing the CONCAT SQL function.
76
97
  */
77
98
  export const concat = (...args: OperandInput[]): FunctionNode => {
78
99
  if (args.length < 2) throw new Error('concat() expects at least 2 arguments');
@@ -80,7 +101,10 @@ export const concat = (...args: OperandInput[]): FunctionNode => {
80
101
  };
81
102
 
82
103
  /**
83
- * Helper: CONCAT_WS(separator, arg1, arg2, ...)
104
+ * Concatenates strings with a separator.
105
+ * @param separator - The separator string.
106
+ * @param args - The strings to concatenate.
107
+ * @returns A FunctionNode representing the CONCAT_WS SQL function.
84
108
  */
85
109
  export const concatWs = (separator: OperandInput, ...args: OperandInput[]): FunctionNode => {
86
110
  if (args.length < 1) throw new Error('concatWs() expects at least 2 arguments including the separator');
@@ -88,61 +112,98 @@ export const concatWs = (separator: OperandInput, ...args: OperandInput[]): Func
88
112
  };
89
113
 
90
114
  /**
91
- * Helper: SUBSTR(str, start[, length])
115
+ * Extracts a substring from a string.
116
+ * @param value - The string value.
117
+ * @param start - The starting position.
118
+ * @param length - The length of the substring (optional).
119
+ * @returns A FunctionNode representing the SUBSTR SQL function.
92
120
  */
93
121
  export const substr = (value: OperandInput, start: OperandInput, length?: OperandInput): FunctionNode =>
94
122
  length === undefined ? fn('SUBSTR', [value, start]) : fn('SUBSTR', [value, start, length]);
95
123
 
96
124
  /**
97
- * Helper: LEFT(str, length)
125
+ * Returns the leftmost characters of a string.
126
+ * @param value - The string value.
127
+ * @param len - The number of characters to return.
128
+ * @returns A FunctionNode representing the LEFT SQL function.
98
129
  */
99
130
  export const left = (value: OperandInput, len: OperandInput): FunctionNode => fn('LEFT', [value, len]);
100
131
 
101
132
  /**
102
- * Helper: RIGHT(str, length)
133
+ * Returns the rightmost characters of a string.
134
+ * @param value - The string value.
135
+ * @param len - The number of characters to return.
136
+ * @returns A FunctionNode representing the RIGHT SQL function.
103
137
  */
104
138
  export const right = (value: OperandInput, len: OperandInput): FunctionNode => fn('RIGHT', [value, len]);
105
139
 
106
140
  /**
107
- * Helper: POSITION(substring IN string)
141
+ * Returns the position of a substring in a string.
142
+ * @param substring - The substring to search for.
143
+ * @param value - The string to search in.
144
+ * @returns A FunctionNode representing the POSITION SQL function.
108
145
  */
109
146
  export const position = (substring: OperandInput, value: OperandInput): FunctionNode => fn('POSITION', [substring, value]);
110
147
 
111
148
  /**
112
- * Helper: INSTR(string, substring)
149
+ * Returns the position of a substring in a string.
150
+ * @param value - The string to search in.
151
+ * @param substring - The substring to search for.
152
+ * @returns A FunctionNode representing the INSTR SQL function.
113
153
  */
114
154
  export const instr = (value: OperandInput, substring: OperandInput): FunctionNode => fn('INSTR', [value, substring]);
115
155
 
116
156
  /**
117
- * Helper: LOCATE(substring, string[, start])
157
+ * Returns the position of a substring in a string, optionally starting from a position.
158
+ * @param substring - The substring to search for.
159
+ * @param value - The string to search in.
160
+ * @param start - The starting position (optional).
161
+ * @returns A FunctionNode representing the LOCATE SQL function.
118
162
  */
119
163
  export const locate = (substring: OperandInput, value: OperandInput, start?: OperandInput): FunctionNode =>
120
164
  start === undefined ? fn('LOCATE', [substring, value]) : fn('LOCATE', [substring, value, start]);
121
165
 
122
166
  /**
123
- * Helper: REPLACE(string, search, replace)
167
+ * Replaces occurrences of a substring in a string.
168
+ * @param value - The string to search in.
169
+ * @param search - The substring to replace.
170
+ * @param replacement - The replacement string.
171
+ * @returns A FunctionNode representing the REPLACE SQL function.
124
172
  */
125
173
  export const replace = (value: OperandInput, search: OperandInput, replacement: OperandInput): FunctionNode =>
126
174
  fn('REPLACE', [value, search, replacement]);
127
175
 
128
176
  /**
129
- * Helper: REPEAT(string, count)
177
+ * Repeats a string a specified number of times.
178
+ * @param value - The string to repeat.
179
+ * @param count - The number of times to repeat.
180
+ * @returns A FunctionNode representing the REPEAT SQL function.
130
181
  */
131
182
  export const repeat = (value: OperandInput, count: OperandInput): FunctionNode => fn('REPEAT', [value, count]);
132
183
 
133
184
  /**
134
- * Helper: LPAD(string, length, padstr)
185
+ * Left-pads a string to a certain length with another string.
186
+ * @param value - The string to pad.
187
+ * @param len - The length to pad to.
188
+ * @param pad - The padding string.
189
+ * @returns A FunctionNode representing the LPAD SQL function.
135
190
  */
136
191
  export const lpad = (value: OperandInput, len: OperandInput, pad: OperandInput): FunctionNode =>
137
192
  fn('LPAD', [value, len, pad]);
138
193
 
139
194
  /**
140
- * Helper: RPAD(string, length, padstr)
195
+ * Right-pads a string to a certain length with another string.
196
+ * @param value - The string to pad.
197
+ * @param len - The length to pad to.
198
+ * @param pad - The padding string.
199
+ * @returns A FunctionNode representing the RPAD SQL function.
141
200
  */
142
201
  export const rpad = (value: OperandInput, len: OperandInput, pad: OperandInput): FunctionNode =>
143
202
  fn('RPAD', [value, len, pad]);
144
203
 
145
204
  /**
146
- * Helper: SPACE(count)
205
+ * Returns a string consisting of a specified number of spaces.
206
+ * @param count - The number of spaces.
207
+ * @returns A FunctionNode representing the SPACE SQL function.
147
208
  */
148
209
  export const space = (count: OperandInput): FunctionNode => fn('SPACE', [count]);
@@ -1,18 +1,33 @@
1
- import { FunctionNode, OperandNode } from '../ast/expression.js';
2
-
3
- export interface FunctionRenderContext {
4
- node: FunctionNode;
5
- compiledArgs: string[];
6
- /** Helper to compile additional operands (e.g., separators or ORDER BY columns) */
7
- compileOperand: (operand: OperandNode) => string;
8
- }
1
+ import { FunctionNode, OperandNode } from '../ast/expression.js';
9
2
 
3
+ /**
4
+ * Context provided to function renderers.
5
+ */
6
+ export interface FunctionRenderContext {
7
+ /** The function node being rendered. */
8
+ node: FunctionNode;
9
+ /** The compiled arguments for the function. */
10
+ compiledArgs: string[];
11
+ /** Helper to compile additional operands (e.g., separators or ORDER BY columns). */
12
+ compileOperand: (operand: OperandNode) => string;
13
+ }
14
+
15
+ /**
16
+ * A function that renders a SQL function call.
17
+ * @param ctx - The rendering context.
18
+ * @returns The rendered SQL string.
19
+ */
10
20
  export type FunctionRenderer = (ctx: FunctionRenderContext) => string;
11
21
 
22
+ /**
23
+ * Strategy for rendering SQL functions in a specific dialect.
24
+ */
12
25
  export interface FunctionStrategy {
13
26
  /**
14
27
  * Returns a renderer for a specific function name (e.g. "DATE_ADD").
15
28
  * Returns undefined if this dialect doesn't support the function.
29
+ * @param functionName - The name of the function.
30
+ * @returns The renderer function or undefined.
16
31
  */
17
32
  getRenderer(functionName: string): FunctionRenderer | undefined;
18
33
  }
@@ -8,6 +8,7 @@ import {
8
8
  type RelationDef
9
9
  } from '../schema/relation.js';
10
10
  import { TableDef } from '../schema/table.js';
11
+ import { isTableDef } from '../schema/table-guards.js';
11
12
  import {
12
13
  buildTableDef,
13
14
  EntityConstructor,
@@ -18,11 +19,10 @@ import {
18
19
  RelationMetadata
19
20
  } from '../orm/entity-metadata.js';
20
21
 
21
- const isTableDef = (value: unknown): value is TableDef => {
22
- return typeof value === 'object' && value !== null && 'columns' in (value as TableDef);
23
- };
22
+ import { tableRef, type TableRef } from '../schema/table.js';
24
23
 
25
24
  const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
25
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
26
26
  if (typeof target === 'function' && (target as Function).prototype === undefined) {
27
27
  return (target as () => EntityOrTableTarget)();
28
28
  }
@@ -31,22 +31,22 @@ const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget
31
31
 
32
32
  const resolveTableTarget = (
33
33
  target: EntityOrTableTargetResolver,
34
- tableMap: Map<EntityConstructor<any>, TableDef>
34
+ tableMap: Map<EntityConstructor, TableDef>
35
35
  ): TableDef => {
36
36
  const resolved = unwrapTarget(target);
37
37
  if (isTableDef(resolved)) {
38
38
  return resolved;
39
39
  }
40
- const table = tableMap.get(resolved as EntityConstructor<any>);
40
+ const table = tableMap.get(resolved as EntityConstructor);
41
41
  if (!table) {
42
- throw new Error(`Entity '${(resolved as EntityConstructor<any>).name}' is not registered with decorators`);
42
+ throw new Error(`Entity '${(resolved as EntityConstructor).name}' is not registered with decorators`);
43
43
  }
44
44
  return table;
45
45
  };
46
46
 
47
47
  const buildRelationDefinitions = (
48
48
  meta: { relations: Record<string, RelationMetadata> },
49
- tableMap: Map<EntityConstructor<any>, TableDef>
49
+ tableMap: Map<EntityConstructor, TableDef>
50
50
  ): Record<string, RelationDef> => {
51
51
  const relations: Record<string, RelationDef> = {};
52
52
 
@@ -101,9 +101,13 @@ const buildRelationDefinitions = (
101
101
  return relations;
102
102
  };
103
103
 
104
+ /**
105
+ * Bootstraps all entities by building their table definitions and relations.
106
+ * @returns An array of table definitions for all bootstrapped entities.
107
+ */
104
108
  export const bootstrapEntities = (): TableDef[] => {
105
109
  const metas = getAllEntityMetadata();
106
- const tableMap = new Map<EntityConstructor<any>, TableDef>();
110
+ const tableMap = new Map<EntityConstructor, TableDef>();
107
111
 
108
112
  for (const meta of metas) {
109
113
  const table = buildTableDef(meta);
@@ -119,7 +123,13 @@ export const bootstrapEntities = (): TableDef[] => {
119
123
  return metas.map(meta => meta.table!) as TableDef[];
120
124
  };
121
125
 
122
- export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor<any>): TTable | undefined => {
126
+ /**
127
+ * Gets the table definition for a given entity constructor.
128
+ * Bootstraps entities if necessary.
129
+ * @param ctor - The entity constructor.
130
+ * @returns The table definition or undefined if not found.
131
+ */
132
+ export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
123
133
  const meta = getEntityMetadata(ctor);
124
134
  if (!meta) return undefined;
125
135
  if (!meta.table) {
@@ -128,12 +138,33 @@ export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor:
128
138
  return meta.table as TTable;
129
139
  };
130
140
 
141
+ /**
142
+ * Creates a select query builder for the given entity.
143
+ * @param ctor - The entity constructor.
144
+ * @returns A select query builder for the entity.
145
+ */
131
146
  export const selectFromEntity = <TTable extends TableDef = TableDef>(
132
- ctor: EntityConstructor<any>
133
- ): SelectQueryBuilder<any, TTable> => {
147
+ ctor: EntityConstructor
148
+ ): SelectQueryBuilder<unknown, TTable> => {
134
149
  const table = getTableDefFromEntity(ctor);
135
150
  if (!table) {
136
151
  throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
137
152
  }
138
153
  return new SelectQueryBuilder(table as TTable);
139
154
  };
155
+
156
+ /**
157
+ * Public API: opt-in ergonomic entity reference (decorator-level).
158
+ *
159
+ * Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
160
+ * `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
161
+ */
162
+ export const entityRef = <TTable extends TableDef = TableDef>(
163
+ ctor: EntityConstructor
164
+ ): TableRef<TTable> => {
165
+ const table = getTableDefFromEntity<TTable>(ctor);
166
+ if (!table) {
167
+ throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
168
+ }
169
+ return tableRef(table);
170
+ };
@@ -9,10 +9,12 @@ import {
9
9
  DualModePropertyDecorator,
10
10
  getOrCreateMetadataBag,
11
11
  isStandardDecoratorContext,
12
- registerInitializer,
13
12
  StandardDecoratorContext
14
13
  } from './decorator-metadata.js';
15
14
 
15
+ /**
16
+ * Options for defining a column in an entity.
17
+ */
16
18
  export interface ColumnOptions {
17
19
  type: ColumnType;
18
20
  args?: ColumnDef['args'];
@@ -21,7 +23,10 @@ export interface ColumnOptions {
21
23
  tsType?: ColumnDef['tsType'];
22
24
  }
23
25
 
24
- export type ColumnInput = ColumnOptions | ColumnDef<any, any>;
26
+ /**
27
+ * Input type for column definitions, either as options object or direct ColumnDef.
28
+ */
29
+ export type ColumnInput = ColumnOptions | ColumnDef;
25
30
 
26
31
  const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
27
32
  const asOptions = input as ColumnOptions;
@@ -60,8 +65,8 @@ const resolveConstructor = (target: unknown): EntityConstructor | undefined => {
60
65
  return target as EntityConstructor;
61
66
  }
62
67
 
63
- if (target && typeof (target as any).constructor === 'function') {
64
- return (target as any).constructor as EntityConstructor;
68
+ if (target && typeof (target as { constructor: unknown }).constructor === 'function') {
69
+ return (target as { constructor: unknown }).constructor as EntityConstructor;
65
70
  }
66
71
 
67
72
  return undefined;
@@ -88,15 +93,13 @@ const registerColumnFromContext = (
88
93
  bag.columns.push({ propertyName, column: { ...column } });
89
94
  }
90
95
 
91
- registerInitializer(context, function () {
92
- const ctor = resolveConstructor(this);
93
- if (!ctor) {
94
- return;
95
- }
96
- registerColumn(ctor, propertyName, column);
97
- });
98
96
  };
99
97
 
98
+ /**
99
+ * Decorator to define a column on an entity property.
100
+ * @param definition - The column definition or options.
101
+ * @returns A property decorator that registers the column metadata.
102
+ */
100
103
  export function Column(definition: ColumnInput) {
101
104
  const normalized = normalizeColumnInput(definition);
102
105
  const decorator: DualModePropertyDecorator = (targetOrValue, propertyKeyOrContext) => {
@@ -116,6 +119,12 @@ export function Column(definition: ColumnInput) {
116
119
  return decorator;
117
120
  }
118
121
 
122
+ /**
123
+ * Decorator to define a primary key column on an entity property.
124
+ * Sets the primary flag to true and delegates to Column decorator.
125
+ * @param definition - The column definition or options.
126
+ * @returns A property decorator that registers the primary key column metadata.
127
+ */
119
128
  export function PrimaryKey(definition: ColumnInput) {
120
129
  const normalized = normalizeColumnInput(definition);
121
130
  normalized.primary = true;
@@ -1,24 +1,37 @@
1
1
  import { ColumnDefLike, RelationMetadata } from '../orm/entity-metadata.js';
2
2
 
3
+ /**
4
+ * Context object provided by standard decorators in newer TypeScript versions.
5
+ */
3
6
  export interface StandardDecoratorContext {
4
7
  kind: string;
5
8
  name?: string | symbol;
6
9
  metadata?: Record<PropertyKey, unknown>;
7
- addInitializer?(initializer: (this: unknown) => void): void;
8
10
  static?: boolean;
9
11
  private?: boolean;
10
12
  }
11
13
 
14
+ /**
15
+ * Dual-mode property decorator that supports both legacy and standard decorator syntax.
16
+ */
12
17
  export interface DualModePropertyDecorator {
13
18
  (target: object, propertyKey: string | symbol): void;
14
19
  (value: unknown, context: StandardDecoratorContext): void;
15
20
  }
16
21
 
22
+ /**
23
+ * Dual-mode class decorator that supports both legacy and standard decorator syntax.
24
+ */
17
25
  export interface DualModeClassDecorator {
26
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
18
27
  <TFunction extends Function>(value: TFunction): void | TFunction;
28
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
19
29
  <TFunction extends Function>(value: TFunction, context: StandardDecoratorContext): void | TFunction;
20
30
  }
21
31
 
32
+ /**
33
+ * Bag for storing decorator metadata during the decoration phase.
34
+ */
22
35
  export interface DecoratorMetadataBag {
23
36
  columns: Array<{ propertyName: string; column: ColumnDefLike }>;
24
37
  relations: Array<{ propertyName: string; relation: RelationMetadata }>;
@@ -26,10 +39,20 @@ export interface DecoratorMetadataBag {
26
39
 
27
40
  const METADATA_KEY = 'metal-orm:decorators';
28
41
 
42
+ /**
43
+ * Checks if a value is a StandardDecoratorContext.
44
+ * @param value - The value to check.
45
+ * @returns True if the value is a StandardDecoratorContext.
46
+ */
29
47
  export const isStandardDecoratorContext = (value: unknown): value is StandardDecoratorContext => {
30
- return typeof value === 'object' && value !== null && 'kind' in (value as any);
48
+ return typeof value === 'object' && value !== null && 'kind' in (value as object);
31
49
  };
32
50
 
51
+ /**
52
+ * Gets or creates a metadata bag for the given decorator context.
53
+ * @param context - The decorator context.
54
+ * @returns The metadata bag.
55
+ */
33
56
  export const getOrCreateMetadataBag = (context: StandardDecoratorContext): DecoratorMetadataBag => {
34
57
  const metadata = context.metadata || (context.metadata = {} as Record<PropertyKey, unknown>);
35
58
  const existing = metadata[METADATA_KEY] as DecoratorMetadataBag | undefined;
@@ -41,13 +64,11 @@ export const getOrCreateMetadataBag = (context: StandardDecoratorContext): Decor
41
64
  return bag;
42
65
  };
43
66
 
67
+ /**
68
+ * Reads the metadata bag from the given decorator context.
69
+ * @param context - The decorator context.
70
+ * @returns The metadata bag if present.
71
+ */
44
72
  export const readMetadataBag = (context: StandardDecoratorContext): DecoratorMetadataBag | undefined => {
45
73
  return context.metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
46
74
  };
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,
@@ -8,6 +9,9 @@ import {
8
9
  } from '../orm/entity-metadata.js';
9
10
  import { DualModeClassDecorator, isStandardDecoratorContext, readMetadataBag } from './decorator-metadata.js';
10
11
 
12
+ /**
13
+ * Options for defining an entity.
14
+ */
11
15
  export interface EntityOptions {
12
16
  tableName?: string;
13
17
  hooks?: TableHooks;
@@ -22,7 +26,7 @@ const toSnakeCase = (value: string): string => {
22
26
  .toLowerCase();
23
27
  };
24
28
 
25
- const deriveTableNameFromConstructor = (ctor: Function): string => {
29
+ const deriveTableNameFromConstructor = (ctor: EntityConstructor<unknown>): string => {
26
30
  const fallback = 'unknown';
27
31
  const rawName = ctor.name || fallback;
28
32
  const strippedName = rawName.replace(/Entity$/i, '');
@@ -33,6 +37,11 @@ const deriveTableNameFromConstructor = (ctor: Function): string => {
33
37
  return normalized.endsWith('s') ? normalized : `${normalized}s`;
34
38
  };
35
39
 
40
+ /**
41
+ * Class decorator to mark a class as an entity and configure its table mapping.
42
+ * @param options - Configuration options for the entity.
43
+ * @returns A class decorator that registers the entity metadata.
44
+ */
36
45
  export function Entity(options: EntityOptions = {}) {
37
46
  const decorator: DualModeClassDecorator = value => {
38
47
  const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
@@ -50,14 +59,29 @@ export function Entity(options: EntityOptions = {}) {
50
59
  if (bag) {
51
60
  const meta = ensureEntityMetadata(ctor);
52
61
  for (const entry of bag.columns) {
53
- if (!meta.columns[entry.propertyName]) {
54
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
62
+ if (meta.columns[entry.propertyName]) {
63
+ throw new Error(
64
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
65
+ );
55
66
  }
67
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
56
68
  }
57
69
  for (const entry of bag.relations) {
58
- if (!meta.relations[entry.propertyName]) {
59
- addRelationMetadata(ctor, entry.propertyName, entry.relation);
70
+ if (meta.relations[entry.propertyName]) {
71
+ throw new Error(
72
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
73
+ );
60
74
  }
75
+ const relationCopy =
76
+ entry.relation.kind === RelationKinds.BelongsToMany
77
+ ? {
78
+ ...entry.relation,
79
+ defaultPivotColumns: entry.relation.defaultPivotColumns
80
+ ? [...entry.relation.defaultPivotColumns]
81
+ : undefined
82
+ }
83
+ : { ...entry.relation };
84
+ addRelationMetadata(ctor, entry.propertyName, relationCopy);
61
85
  }
62
86
  }
63
87
  }
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Decorators for defining entities, columns, and relations in Metal ORM.
3
+ */
1
4
  export * from './entity.js';
2
5
  export * from './column.js';
3
6
  export * from './relations.js';