metal-orm 1.0.57 → 1.0.59
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 +23 -13
- package/dist/index.cjs +1750 -733
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +244 -157
- package/dist/index.d.ts +244 -157
- package/dist/index.js +1745 -733
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/core/ddl/schema-generator.ts +44 -1
- package/src/decorators/bootstrap.ts +186 -113
- package/src/decorators/column-decorator.ts +8 -49
- package/src/decorators/decorator-metadata.ts +10 -46
- package/src/decorators/entity.ts +30 -40
- package/src/decorators/relations.ts +30 -56
- package/src/orm/entity-hydration.ts +72 -0
- package/src/orm/entity-meta.ts +18 -13
- package/src/orm/entity-metadata.ts +240 -238
- package/src/orm/entity-relation-cache.ts +39 -0
- package/src/orm/entity-relations.ts +207 -0
- package/src/orm/entity.ts +124 -343
- package/src/orm/execute.ts +87 -20
- package/src/orm/lazy-batch/belongs-to-many.ts +134 -0
- package/src/orm/lazy-batch/belongs-to.ts +108 -0
- package/src/orm/lazy-batch/has-many.ts +69 -0
- package/src/orm/lazy-batch/has-one.ts +68 -0
- package/src/orm/lazy-batch/shared.ts +125 -0
- package/src/orm/lazy-batch.ts +4 -309
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +29 -14
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- package/src/query-builder/relation-cte-builder.ts +63 -0
- package/src/query-builder/relation-filter-utils.ts +159 -0
- package/src/query-builder/relation-include-strategies.ts +177 -0
- package/src/query-builder/relation-join-planner.ts +80 -0
- package/src/query-builder/relation-service.ts +103 -159
- package/src/query-builder/relation-types.ts +43 -12
- package/src/query-builder/select/projection-facet.ts +23 -23
- package/src/query-builder/select/select-operations.ts +145 -0
- package/src/query-builder/select.ts +373 -426
- package/src/schema/relation.ts +22 -18
- package/src/schema/table.ts +22 -9
- package/src/schema/types.ts +103 -84
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "metal-orm",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"types": "./dist/index.d.ts",
|
|
6
|
-
"engines": {
|
|
7
|
-
"node": ">=20.0.0"
|
|
8
|
-
},
|
|
9
|
-
"bin": {
|
|
10
|
-
"metal-orm-gen": "./scripts/generate-entities.mjs"
|
|
11
|
-
},
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
|
-
"import": "./dist/index.js",
|
|
16
|
-
"require": "./dist/index.cjs"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist",
|
|
21
|
-
"src",
|
|
22
|
-
"global.d.ts",
|
|
23
|
-
"scripts"
|
|
24
|
-
],
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "tsup",
|
|
27
|
-
"check": "tsc --noEmit",
|
|
28
|
-
"gen:entities": "node scripts/generate-entities.mjs",
|
|
29
|
-
"test": "vitest",
|
|
30
|
-
"test:ui": "vitest --ui",
|
|
31
|
-
"show-sql": "node scripts/show-sql.mjs",
|
|
32
|
-
"lint": "node scripts/run-eslint.mjs",
|
|
33
|
-
"lint:fix": "node scripts/run-eslint.mjs --fix"
|
|
34
|
-
},
|
|
35
|
-
"peerDependencies": {
|
|
36
|
-
"mysql2": "^3.9.0",
|
|
37
|
-
"pg": "^8.0.0",
|
|
38
|
-
"sqlite3": "^5.1.6",
|
|
39
|
-
"tedious": "^16.0.0"
|
|
40
|
-
},
|
|
41
|
-
"peerDependenciesMeta": {
|
|
42
|
-
"mysql2": {
|
|
43
|
-
"optional": true
|
|
44
|
-
},
|
|
45
|
-
"pg": {
|
|
46
|
-
"optional": true
|
|
47
|
-
},
|
|
48
|
-
"sqlite3": {
|
|
49
|
-
"optional": true
|
|
50
|
-
},
|
|
51
|
-
"tedious": {
|
|
52
|
-
"optional": true
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@vitest/ui": "^4.0.14",
|
|
57
|
-
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
58
|
-
"@typescript-eslint/parser": "^8.20.0",
|
|
59
|
-
"eslint": "^8.57.0",
|
|
60
|
-
"eslint-plugin-deprecation": "^3.0.0",
|
|
61
|
-
"mysql2": "^3.15.3",
|
|
62
|
-
"pg": "^8.16.3",
|
|
63
|
-
"sqlite3": "^5.1.7",
|
|
64
|
-
"tedious": "^19.1.3",
|
|
65
|
-
"tsup": "^8.0.0",
|
|
66
|
-
"typescript": "^5.5.0",
|
|
67
|
-
"vitest": "^4.0.14"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "metal-orm",
|
|
3
|
+
"version": "1.0.59",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.0.0"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"metal-orm-gen": "./scripts/generate-entities.mjs"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"global.d.ts",
|
|
23
|
+
"scripts"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"check": "tsc --noEmit",
|
|
28
|
+
"gen:entities": "node scripts/generate-entities.mjs",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:ui": "vitest --ui",
|
|
31
|
+
"show-sql": "node scripts/show-sql.mjs",
|
|
32
|
+
"lint": "node scripts/run-eslint.mjs",
|
|
33
|
+
"lint:fix": "node scripts/run-eslint.mjs --fix"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"mysql2": "^3.9.0",
|
|
37
|
+
"pg": "^8.0.0",
|
|
38
|
+
"sqlite3": "^5.1.6",
|
|
39
|
+
"tedious": "^16.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"mysql2": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"pg": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"sqlite3": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"tedious": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@vitest/ui": "^4.0.14",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
58
|
+
"@typescript-eslint/parser": "^8.20.0",
|
|
59
|
+
"eslint": "^8.57.0",
|
|
60
|
+
"eslint-plugin-deprecation": "^3.0.0",
|
|
61
|
+
"mysql2": "^3.15.3",
|
|
62
|
+
"pg": "^8.16.3",
|
|
63
|
+
"sqlite3": "^5.1.7",
|
|
64
|
+
"tedious": "^19.1.3",
|
|
65
|
+
"tsup": "^8.0.0",
|
|
66
|
+
"typescript": "^5.5.0",
|
|
67
|
+
"vitest": "^4.0.14"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TableDef } from '../../schema/table.js';
|
|
2
2
|
import type { ColumnDef } from '../../schema/column-types.js';
|
|
3
3
|
import type { SchemaDialect } from './schema-dialect.js';
|
|
4
|
+
import type { DbExecutor } from '../execution/db-executor.js';
|
|
4
5
|
import { resolvePrimaryKey } from './sql-writing.js';
|
|
5
6
|
import { DialectName } from './schema-dialect.js';
|
|
6
7
|
|
|
@@ -41,7 +42,8 @@ export const renderColumnDefinition = (
|
|
|
41
42
|
if (col.default !== undefined) {
|
|
42
43
|
parts.push(`DEFAULT ${dialect.renderDefault(col.default, col)}`);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
const autoIncIncludesPrimary = typeof autoInc === 'string' && /\bPRIMARY\s+KEY\b/i.test(autoInc);
|
|
46
|
+
if (options.includePrimary && col.primary && !autoIncIncludesPrimary) {
|
|
45
47
|
parts.push('PRIMARY KEY');
|
|
46
48
|
}
|
|
47
49
|
if (col.check) {
|
|
@@ -126,6 +128,47 @@ export const generateSchemaSql = (
|
|
|
126
128
|
return statements;
|
|
127
129
|
};
|
|
128
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Convenience wrapper for generateSchemaSql with rest args.
|
|
133
|
+
* @param dialect - The schema dialect used to render SQL.
|
|
134
|
+
* @param tables - The table definitions to create.
|
|
135
|
+
*/
|
|
136
|
+
export const generateSchemaSqlFor = (
|
|
137
|
+
dialect: SchemaDialect,
|
|
138
|
+
...tables: TableDef[]
|
|
139
|
+
): string[] => generateSchemaSql(tables, dialect);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generates and executes schema SQL for the provided tables.
|
|
143
|
+
* @param executor - The database executor to run statements with.
|
|
144
|
+
* @param tables - The table definitions to create.
|
|
145
|
+
* @param dialect - The schema dialect used to render SQL.
|
|
146
|
+
*/
|
|
147
|
+
export const executeSchemaSql = async (
|
|
148
|
+
executor: DbExecutor,
|
|
149
|
+
tables: TableDef[],
|
|
150
|
+
dialect: SchemaDialect
|
|
151
|
+
): Promise<void> => {
|
|
152
|
+
const statements = generateSchemaSql(tables, dialect);
|
|
153
|
+
for (const sql of statements) {
|
|
154
|
+
await executor.executeSql(sql);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Convenience wrapper for executeSchemaSql with rest args.
|
|
160
|
+
* @param executor - The database executor to run statements with.
|
|
161
|
+
* @param dialect - The schema dialect used to render SQL.
|
|
162
|
+
* @param tables - The table definitions to create.
|
|
163
|
+
*/
|
|
164
|
+
export const executeSchemaSqlFor = async (
|
|
165
|
+
executor: DbExecutor,
|
|
166
|
+
dialect: SchemaDialect,
|
|
167
|
+
...tables: TableDef[]
|
|
168
|
+
): Promise<void> => {
|
|
169
|
+
await executeSchemaSql(executor, tables, dialect);
|
|
170
|
+
};
|
|
171
|
+
|
|
129
172
|
const orderTablesByDependencies = (tables: TableDef[]): TableDef[] => {
|
|
130
173
|
const map = new Map<string, TableDef>();
|
|
131
174
|
tables.forEach(t => map.set(t.name, t));
|
|
@@ -1,171 +1,244 @@
|
|
|
1
|
-
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
2
|
-
import {
|
|
3
|
-
hasMany,
|
|
4
|
-
hasOne,
|
|
5
|
-
belongsTo,
|
|
1
|
+
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
2
|
+
import {
|
|
3
|
+
hasMany,
|
|
4
|
+
hasOne,
|
|
5
|
+
belongsTo,
|
|
6
6
|
belongsToMany,
|
|
7
7
|
RelationKinds,
|
|
8
|
-
type
|
|
9
|
-
|
|
8
|
+
type HasManyRelation,
|
|
9
|
+
type HasOneRelation,
|
|
10
|
+
type BelongsToRelation,
|
|
11
|
+
type BelongsToManyRelation,
|
|
12
|
+
type RelationDef
|
|
13
|
+
} from '../schema/relation.js';
|
|
10
14
|
import { TableDef } from '../schema/table.js';
|
|
11
15
|
import { isTableDef } from '../schema/table-guards.js';
|
|
12
16
|
import {
|
|
13
17
|
buildTableDef,
|
|
14
18
|
EntityConstructor,
|
|
19
|
+
EntityMetadata,
|
|
15
20
|
EntityOrTableTarget,
|
|
16
21
|
EntityOrTableTargetResolver,
|
|
17
22
|
getAllEntityMetadata,
|
|
18
|
-
getEntityMetadata
|
|
19
|
-
RelationMetadata
|
|
23
|
+
getEntityMetadata
|
|
20
24
|
} from '../orm/entity-metadata.js';
|
|
21
|
-
|
|
22
|
-
import { tableRef, type TableRef } from '../schema/table.js';
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
|
|
26
|
+
import { tableRef, type TableRef } from '../schema/table.js';
|
|
27
|
+
import {
|
|
28
|
+
SelectableKeys,
|
|
29
|
+
ColumnDef,
|
|
30
|
+
HasManyCollection,
|
|
31
|
+
HasOneReference,
|
|
32
|
+
BelongsToReference,
|
|
33
|
+
ManyToManyCollection
|
|
34
|
+
} from '../schema/types.js';
|
|
35
|
+
|
|
36
|
+
const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
38
|
+
if (typeof target === 'function' && (target as Function).prototype === undefined) {
|
|
39
|
+
return (target as () => EntityOrTableTarget)();
|
|
40
|
+
}
|
|
41
|
+
return target as EntityOrTableTarget;
|
|
42
|
+
};
|
|
43
|
+
|
|
33
44
|
const resolveTableTarget = (
|
|
34
45
|
target: EntityOrTableTargetResolver,
|
|
35
46
|
tableMap: Map<EntityConstructor, TableDef>
|
|
36
47
|
): TableDef => {
|
|
48
|
+
const resolved = unwrapTarget(target);
|
|
49
|
+
if (isTableDef(resolved)) {
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
const table = tableMap.get(resolved as EntityConstructor);
|
|
53
|
+
if (!table) {
|
|
54
|
+
throw new Error(`Entity '${(resolved as EntityConstructor).name}' is not registered with decorators`);
|
|
55
|
+
}
|
|
56
|
+
return table;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const toSnakeCase = (value: string): string => {
|
|
60
|
+
return value
|
|
61
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
62
|
+
.replace(/[^a-z0-9_]+/gi, '_')
|
|
63
|
+
.replace(/__+/g, '_')
|
|
64
|
+
.replace(/^_|_$/g, '')
|
|
65
|
+
.toLowerCase();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const normalizeEntityName = (value: string): string => {
|
|
69
|
+
const stripped = value.replace(/Entity$/i, '');
|
|
70
|
+
const normalized = toSnakeCase(stripped || value);
|
|
71
|
+
return normalized || 'unknown';
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const getPivotKeyBaseFromTarget = (target: EntityOrTableTargetResolver): string => {
|
|
37
75
|
const resolved = unwrapTarget(target);
|
|
38
76
|
if (isTableDef(resolved)) {
|
|
39
|
-
return resolved;
|
|
40
|
-
}
|
|
41
|
-
const table = tableMap.get(resolved as EntityConstructor);
|
|
42
|
-
if (!table) {
|
|
43
|
-
throw new Error(`Entity '${(resolved as EntityConstructor).name}' is not registered with decorators`);
|
|
77
|
+
return toSnakeCase(resolved.name || 'unknown');
|
|
44
78
|
}
|
|
45
|
-
|
|
79
|
+
const ctor = resolved as EntityConstructor;
|
|
80
|
+
return normalizeEntityName(ctor.name || 'unknown');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const getPivotKeyBaseFromRoot = (meta: EntityMetadata): string => {
|
|
84
|
+
return normalizeEntityName(meta.target.name || meta.tableName || 'unknown');
|
|
46
85
|
};
|
|
47
86
|
|
|
48
87
|
const buildRelationDefinitions = (
|
|
49
|
-
meta:
|
|
88
|
+
meta: EntityMetadata,
|
|
50
89
|
tableMap: Map<EntityConstructor, TableDef>
|
|
51
90
|
): Record<string, RelationDef> => {
|
|
52
91
|
const relations: Record<string, RelationDef> = {};
|
|
53
92
|
|
|
54
93
|
for (const [name, relation] of Object.entries(meta.relations)) {
|
|
55
|
-
switch (relation.kind) {
|
|
94
|
+
switch (relation.kind) {
|
|
56
95
|
case RelationKinds.HasOne: {
|
|
96
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
57
97
|
relations[name] = hasOne(
|
|
58
98
|
resolveTableTarget(relation.target, tableMap),
|
|
59
|
-
|
|
99
|
+
foreignKey,
|
|
60
100
|
relation.localKey,
|
|
61
101
|
relation.cascade
|
|
62
102
|
);
|
|
63
103
|
break;
|
|
64
104
|
}
|
|
65
105
|
case RelationKinds.HasMany: {
|
|
106
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
66
107
|
relations[name] = hasMany(
|
|
67
108
|
resolveTableTarget(relation.target, tableMap),
|
|
68
|
-
|
|
69
|
-
relation.localKey,
|
|
70
|
-
relation.cascade
|
|
71
|
-
);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
case RelationKinds.BelongsTo: {
|
|
75
|
-
relations[name] = belongsTo(
|
|
76
|
-
resolveTableTarget(relation.target, tableMap),
|
|
77
|
-
relation.foreignKey,
|
|
109
|
+
foreignKey,
|
|
78
110
|
relation.localKey,
|
|
79
111
|
relation.cascade
|
|
80
112
|
);
|
|
81
113
|
break;
|
|
82
114
|
}
|
|
115
|
+
case RelationKinds.BelongsTo: {
|
|
116
|
+
relations[name] = belongsTo(
|
|
117
|
+
resolveTableTarget(relation.target, tableMap),
|
|
118
|
+
relation.foreignKey,
|
|
119
|
+
relation.localKey,
|
|
120
|
+
relation.cascade
|
|
121
|
+
);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
83
124
|
case RelationKinds.BelongsToMany: {
|
|
125
|
+
const pivotForeignKeyToRoot =
|
|
126
|
+
relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
127
|
+
const pivotForeignKeyToTarget =
|
|
128
|
+
relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
|
|
84
129
|
relations[name] = belongsToMany(
|
|
85
130
|
resolveTableTarget(relation.target, tableMap),
|
|
86
131
|
resolveTableTarget(relation.pivotTable, tableMap),
|
|
87
132
|
{
|
|
88
|
-
pivotForeignKeyToRoot
|
|
89
|
-
pivotForeignKeyToTarget
|
|
133
|
+
pivotForeignKeyToRoot,
|
|
134
|
+
pivotForeignKeyToTarget,
|
|
90
135
|
localKey: relation.localKey,
|
|
91
136
|
targetKey: relation.targetKey,
|
|
92
137
|
pivotPrimaryKey: relation.pivotPrimaryKey,
|
|
93
138
|
defaultPivotColumns: relation.defaultPivotColumns,
|
|
94
139
|
cascade: relation.cascade
|
|
95
|
-
}
|
|
96
|
-
);
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return relations;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Bootstraps all entities by building their table definitions and relations.
|
|
107
|
-
* @returns An array of table definitions for all bootstrapped entities.
|
|
108
|
-
*/
|
|
109
|
-
export const bootstrapEntities = (): TableDef[] => {
|
|
110
|
-
const metas = getAllEntityMetadata();
|
|
111
|
-
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
112
|
-
|
|
113
|
-
for (const meta of metas) {
|
|
114
|
-
const table = buildTableDef(meta);
|
|
115
|
-
tableMap.set(meta.target, table);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const meta of metas) {
|
|
119
|
-
const table = meta.table!;
|
|
120
|
-
const relations = buildRelationDefinitions(meta, tableMap);
|
|
121
|
-
table.relations = relations;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return metas.map(meta => meta.table!) as TableDef[];
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Gets the table definition for a given entity constructor.
|
|
129
|
-
* Bootstraps entities if necessary.
|
|
130
|
-
* @param ctor - The entity constructor.
|
|
131
|
-
* @returns The table definition or undefined if not found.
|
|
132
|
-
*/
|
|
133
|
-
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
|
|
134
|
-
const meta = getEntityMetadata(ctor);
|
|
135
|
-
if (!meta) return undefined;
|
|
136
|
-
if (!meta.table) {
|
|
137
|
-
bootstrapEntities();
|
|
138
|
-
}
|
|
139
|
-
return meta.table as TTable;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Creates a select query builder for the given entity.
|
|
144
|
-
* @param ctor - The entity constructor.
|
|
145
|
-
* @returns A select query builder for the entity.
|
|
146
|
-
*/
|
|
147
|
-
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return relations;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Bootstraps all entities by building their table definitions and relations.
|
|
152
|
+
* @returns An array of table definitions for all bootstrapped entities.
|
|
153
|
+
*/
|
|
154
|
+
export const bootstrapEntities = (): TableDef[] => {
|
|
155
|
+
const metas = getAllEntityMetadata();
|
|
156
|
+
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
157
|
+
|
|
158
|
+
for (const meta of metas) {
|
|
159
|
+
const table = buildTableDef(meta);
|
|
160
|
+
tableMap.set(meta.target, table);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const meta of metas) {
|
|
164
|
+
const table = meta.table!;
|
|
165
|
+
const relations = buildRelationDefinitions(meta, tableMap);
|
|
166
|
+
table.relations = relations;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return metas.map(meta => meta.table!) as TableDef[];
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Gets the table definition for a given entity constructor.
|
|
174
|
+
* Bootstraps entities if necessary.
|
|
175
|
+
* @param ctor - The entity constructor.
|
|
176
|
+
* @returns The table definition or undefined if not found.
|
|
177
|
+
*/
|
|
178
|
+
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
|
|
179
|
+
const meta = getEntityMetadata(ctor);
|
|
180
|
+
if (!meta) return undefined;
|
|
181
|
+
if (!meta.table) {
|
|
182
|
+
bootstrapEntities();
|
|
183
|
+
}
|
|
184
|
+
return meta.table as TTable;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Creates a select query builder for the given entity.
|
|
189
|
+
* @param ctor - The entity constructor.
|
|
190
|
+
* @returns A select query builder for the entity.
|
|
191
|
+
*/
|
|
192
|
+
type NonFunctionKeys<T> = {
|
|
193
|
+
[K in keyof T]-?: T[K] extends (...args: unknown[]) => unknown ? never : K
|
|
194
|
+
}[keyof T];
|
|
195
|
+
|
|
196
|
+
type RelationKeys<TEntity extends object> =
|
|
197
|
+
Exclude<NonFunctionKeys<TEntity>, SelectableKeys<TEntity>> & string;
|
|
198
|
+
|
|
199
|
+
type EntityTable<TEntity extends object> =
|
|
200
|
+
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
201
|
+
relations: {
|
|
202
|
+
[K in RelationKeys<TEntity>]:
|
|
203
|
+
NonNullable<TEntity[K]> extends HasManyCollection<infer TChild>
|
|
204
|
+
? HasManyRelation<EntityTable<NonNullable<TChild> & object>>
|
|
205
|
+
: NonNullable<TEntity[K]> extends ManyToManyCollection<infer TTarget, infer TPivot>
|
|
206
|
+
? BelongsToManyRelation<
|
|
207
|
+
EntityTable<NonNullable<TTarget> & object>,
|
|
208
|
+
TPivot extends object ? EntityTable<NonNullable<TPivot> & object> : TableDef
|
|
209
|
+
>
|
|
210
|
+
: NonNullable<TEntity[K]> extends HasOneReference<infer TChild>
|
|
211
|
+
? HasOneRelation<EntityTable<NonNullable<TChild> & object>>
|
|
212
|
+
: NonNullable<TEntity[K]> extends BelongsToReference<infer TParent>
|
|
213
|
+
? BelongsToRelation<EntityTable<NonNullable<TParent> & object>>
|
|
214
|
+
: NonNullable<TEntity[K]> extends object
|
|
215
|
+
? BelongsToRelation<EntityTable<NonNullable<TEntity[K]> & object>>
|
|
216
|
+
: never;
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const selectFromEntity = <TEntity extends object>(
|
|
221
|
+
ctor: EntityConstructor<TEntity>
|
|
222
|
+
): SelectQueryBuilder<unknown, EntityTable<TEntity>> => {
|
|
223
|
+
const table = getTableDefFromEntity(ctor);
|
|
224
|
+
if (!table) {
|
|
225
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
226
|
+
}
|
|
227
|
+
return new SelectQueryBuilder(table as unknown as EntityTable<TEntity>);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Public API: opt-in ergonomic entity reference (decorator-level).
|
|
232
|
+
*
|
|
233
|
+
* Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
|
|
234
|
+
* `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
|
|
235
|
+
*/
|
|
236
|
+
export const entityRef = <TEntity extends object>(
|
|
148
237
|
ctor: EntityConstructor<TEntity>
|
|
149
|
-
):
|
|
238
|
+
): TableRef<EntityTable<TEntity>> => {
|
|
150
239
|
const table = getTableDefFromEntity(ctor);
|
|
151
240
|
if (!table) {
|
|
152
241
|
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
153
242
|
}
|
|
154
|
-
return
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Public API: opt-in ergonomic entity reference (decorator-level).
|
|
159
|
-
*
|
|
160
|
-
* Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
|
|
161
|
-
* `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
|
|
162
|
-
*/
|
|
163
|
-
export const entityRef = <TTable extends TableDef = TableDef>(
|
|
164
|
-
ctor: EntityConstructor
|
|
165
|
-
): TableRef<TTable> => {
|
|
166
|
-
const table = getTableDefFromEntity<TTable>(ctor);
|
|
167
|
-
if (!table) {
|
|
168
|
-
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
169
|
-
}
|
|
170
|
-
return tableRef(table);
|
|
243
|
+
return tableRef(table as EntityTable<TEntity>);
|
|
171
244
|
};
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { ColumnDef, ColumnType } from '../schema/column-types.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
EntityConstructor,
|
|
5
|
-
ColumnDefLike,
|
|
6
|
-
ensureEntityMetadata
|
|
7
|
-
} from '../orm/entity-metadata.js';
|
|
8
|
-
import {
|
|
9
|
-
DualModePropertyDecorator,
|
|
10
|
-
getOrCreateMetadataBag,
|
|
11
|
-
isStandardDecoratorContext,
|
|
12
|
-
StandardDecoratorContext
|
|
13
|
-
} from './decorator-metadata.js';
|
|
2
|
+
import { ColumnDefLike } from '../orm/entity-metadata.js';
|
|
3
|
+
import { getOrCreateMetadataBag } from './decorator-metadata.js';
|
|
14
4
|
|
|
15
5
|
/**
|
|
16
6
|
* Options for defining a column in an entity.
|
|
@@ -62,39 +52,21 @@ const normalizePropertyName = (name: string | symbol): string => {
|
|
|
62
52
|
return name;
|
|
63
53
|
};
|
|
64
54
|
|
|
65
|
-
const resolveConstructor = (target: unknown): EntityConstructor | undefined => {
|
|
66
|
-
if (typeof target === 'function') {
|
|
67
|
-
return target as EntityConstructor;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (target && typeof (target as { constructor: unknown }).constructor === 'function') {
|
|
71
|
-
return (target as { constructor: unknown }).constructor as EntityConstructor;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return undefined;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const registerColumn = (ctor: EntityConstructor, propertyName: string, column: ColumnDefLike): void => {
|
|
78
|
-
const meta = ensureEntityMetadata(ctor);
|
|
79
|
-
if (meta.columns[propertyName]) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
addColumnMetadata(ctor, propertyName, column);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
55
|
const registerColumnFromContext = (
|
|
86
|
-
context:
|
|
56
|
+
context: ClassFieldDecoratorContext,
|
|
87
57
|
column: ColumnDefLike
|
|
88
58
|
): void => {
|
|
89
59
|
if (!context.name) {
|
|
90
60
|
throw new Error('Column decorator requires a property name');
|
|
91
61
|
}
|
|
62
|
+
if (context.private) {
|
|
63
|
+
throw new Error('Column decorator does not support private fields');
|
|
64
|
+
}
|
|
92
65
|
const propertyName = normalizePropertyName(context.name);
|
|
93
66
|
const bag = getOrCreateMetadataBag(context);
|
|
94
67
|
if (!bag.columns.some(entry => entry.propertyName === propertyName)) {
|
|
95
68
|
bag.columns.push({ propertyName, column: { ...column } });
|
|
96
69
|
}
|
|
97
|
-
|
|
98
70
|
};
|
|
99
71
|
|
|
100
72
|
/**
|
|
@@ -104,21 +76,9 @@ const registerColumnFromContext = (
|
|
|
104
76
|
*/
|
|
105
77
|
export function Column(definition: ColumnInput) {
|
|
106
78
|
const normalized = normalizeColumnInput(definition);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
registerColumnFromContext(propertyKeyOrContext, normalized);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const propertyName = normalizePropertyName(propertyKeyOrContext);
|
|
114
|
-
const ctor = resolveConstructor(targetOrValue);
|
|
115
|
-
if (!ctor) {
|
|
116
|
-
throw new Error('Unable to resolve constructor when registering column metadata');
|
|
117
|
-
}
|
|
118
|
-
registerColumn(ctor, propertyName, { ...normalized });
|
|
79
|
+
return function (_value: unknown, context: ClassFieldDecoratorContext) {
|
|
80
|
+
registerColumnFromContext(context, normalized);
|
|
119
81
|
};
|
|
120
|
-
|
|
121
|
-
return decorator;
|
|
122
82
|
}
|
|
123
83
|
|
|
124
84
|
/**
|
|
@@ -132,4 +92,3 @@ export function PrimaryKey(definition: ColumnInput) {
|
|
|
132
92
|
normalized.primary = true;
|
|
133
93
|
return Column(normalized);
|
|
134
94
|
}
|
|
135
|
-
|