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.
- package/README.md +195 -37
- package/dist/index.cjs +1014 -538
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1267 -371
- package/dist/index.d.ts +1267 -371
- package/dist/index.js +1012 -536
- 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/adapters.ts +8 -2
- package/src/core/ast/builders.ts +105 -76
- package/src/core/ast/expression-builders.ts +430 -392
- package/src/core/ast/expression-nodes.ts +14 -5
- package/src/core/ast/expression-visitor.ts +56 -14
- package/src/core/ast/helpers.ts +23 -0
- package/src/core/ast/join-node.ts +18 -2
- 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 +37 -4
- package/src/core/ddl/dialects/index.ts +1 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
- package/src/core/ddl/dialects/render-reference.test.ts +69 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
- package/src/core/ddl/introspect/context.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +53 -8
- package/src/core/ddl/introspect/mysql.ts +32 -6
- package/src/core/ddl/introspect/postgres.ts +102 -34
- package/src/core/ddl/introspect/registry.ts +14 -0
- package/src/core/ddl/introspect/run-select.ts +19 -4
- package/src/core/ddl/introspect/sqlite.ts +78 -11
- package/src/core/ddl/introspect/types.ts +0 -1
- package/src/core/ddl/introspect/utils.ts +21 -3
- package/src/core/ddl/naming-strategy.ts +6 -0
- package/src/core/ddl/schema-dialect.ts +20 -6
- package/src/core/ddl/schema-diff.ts +22 -0
- package/src/core/ddl/schema-generator.ts +26 -12
- package/src/core/ddl/schema-plan-executor.ts +6 -0
- package/src/core/ddl/schema-types.ts +6 -0
- package/src/core/ddl/sql-writing.ts +4 -4
- package/src/core/dialect/abstract.ts +19 -7
- 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 +12 -5
- package/src/core/functions/datetime.ts +58 -34
- package/src/core/functions/numeric.ts +96 -31
- package/src/core/functions/standard-strategy.ts +35 -0
- package/src/core/functions/text.ts +84 -23
- package/src/core/functions/types.ts +23 -8
- package/src/decorators/bootstrap.ts +42 -11
- package/src/decorators/column.ts +20 -11
- package/src/decorators/decorator-metadata.ts +30 -9
- package/src/decorators/entity.ts +29 -5
- package/src/decorators/index.ts +3 -0
- package/src/decorators/relations.ts +34 -11
- package/src/orm/als.ts +34 -9
- package/src/orm/entity-context.ts +62 -8
- package/src/orm/entity-meta.ts +8 -8
- package/src/orm/entity-metadata.ts +131 -16
- package/src/orm/entity.ts +28 -29
- package/src/orm/execute.ts +19 -4
- package/src/orm/hydration.ts +42 -39
- package/src/orm/identity-map.ts +1 -1
- package/src/orm/lazy-batch.ts +74 -104
- 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 +54 -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/index.ts +74 -0
- package/src/query/target.ts +46 -0
- package/src/query-builder/delete-query-state.ts +30 -0
- package/src/query-builder/delete.ts +64 -18
- package/src/query-builder/hydration-manager.ts +52 -5
- package/src/query-builder/insert-query-state.ts +30 -0
- package/src/query-builder/insert.ts +58 -10
- package/src/query-builder/query-ast-service.ts +7 -2
- package/src/query-builder/query-resolution.ts +78 -0
- package/src/query-builder/raw-column-parser.ts +7 -1
- package/src/query-builder/relation-alias.ts +7 -0
- package/src/query-builder/relation-conditions.ts +61 -48
- package/src/query-builder/relation-service.ts +68 -63
- package/src/query-builder/relation-utils.ts +3 -0
- package/src/query-builder/select/cte-facet.ts +40 -0
- package/src/query-builder/select/from-facet.ts +80 -0
- package/src/query-builder/select/join-facet.ts +62 -0
- package/src/query-builder/select/predicate-facet.ts +103 -0
- package/src/query-builder/select/projection-facet.ts +69 -0
- package/src/query-builder/select/relation-facet.ts +81 -0
- package/src/query-builder/select/setop-facet.ts +36 -0
- package/src/query-builder/select-helpers.ts +15 -2
- package/src/query-builder/select-query-builder-deps.ts +19 -1
- package/src/query-builder/select-query-state.ts +2 -1
- package/src/query-builder/select.ts +795 -1163
- package/src/query-builder/update-query-state.ts +52 -0
- package/src/query-builder/update.ts +69 -18
- package/src/schema/column.ts +26 -26
- package/src/schema/table-guards.ts +31 -0
- package/src/schema/table.ts +47 -18
- 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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
40
|
+
const table = tableMap.get(resolved as EntityConstructor);
|
|
41
41
|
if (!table) {
|
|
42
|
-
throw new Error(`Entity '${(resolved as EntityConstructor
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
133
|
-
): SelectQueryBuilder<
|
|
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
|
+
};
|
package/src/decorators/column.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
64
|
-
return (target as
|
|
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
|
|
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
|
-
};
|
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,
|
|
@@ -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:
|
|
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 (
|
|
54
|
-
|
|
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 (
|
|
59
|
-
|
|
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
|
}
|