metal-orm 1.0.58 → 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 +34 -31
- package/dist/index.cjs +1463 -1003
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +148 -129
- package/dist/index.d.ts +148 -129
- package/dist/index.js +1459 -1003
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ddl/schema-generator.ts +44 -1
- package/src/decorators/bootstrap.ts +183 -146
- 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 +13 -11
- 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 -410
- package/src/orm/execute.ts +4 -4
- 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 -492
- package/src/orm/relations/many-to-many.ts +2 -1
- 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 +119 -479
- package/src/query-builder/relation-types.ts +41 -10
- 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 +351 -422
- package/src/schema/relation.ts +22 -18
- package/src/schema/table.ts +22 -9
- package/src/schema/types.ts +14 -12
package/package.json
CHANGED
|
@@ -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,207 +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
8
|
type HasManyRelation,
|
|
9
9
|
type HasOneRelation,
|
|
10
10
|
type BelongsToRelation,
|
|
11
|
-
type BelongsToManyRelation,
|
|
12
|
-
type RelationDef
|
|
13
|
-
} from '../schema/relation.js';
|
|
11
|
+
type BelongsToManyRelation,
|
|
12
|
+
type RelationDef
|
|
13
|
+
} from '../schema/relation.js';
|
|
14
14
|
import { TableDef } from '../schema/table.js';
|
|
15
15
|
import { isTableDef } from '../schema/table-guards.js';
|
|
16
16
|
import {
|
|
17
17
|
buildTableDef,
|
|
18
18
|
EntityConstructor,
|
|
19
|
+
EntityMetadata,
|
|
19
20
|
EntityOrTableTarget,
|
|
20
21
|
EntityOrTableTargetResolver,
|
|
21
22
|
getAllEntityMetadata,
|
|
22
|
-
getEntityMetadata
|
|
23
|
-
RelationMetadata
|
|
23
|
+
getEntityMetadata
|
|
24
24
|
} from '../orm/entity-metadata.js';
|
|
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
|
-
|
|
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
|
+
|
|
44
44
|
const resolveTableTarget = (
|
|
45
45
|
target: EntityOrTableTargetResolver,
|
|
46
46
|
tableMap: Map<EntityConstructor, TableDef>
|
|
47
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 => {
|
|
48
75
|
const resolved = unwrapTarget(target);
|
|
49
76
|
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`);
|
|
77
|
+
return toSnakeCase(resolved.name || 'unknown');
|
|
55
78
|
}
|
|
56
|
-
|
|
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');
|
|
57
85
|
};
|
|
58
86
|
|
|
59
87
|
const buildRelationDefinitions = (
|
|
60
|
-
meta:
|
|
88
|
+
meta: EntityMetadata,
|
|
61
89
|
tableMap: Map<EntityConstructor, TableDef>
|
|
62
90
|
): Record<string, RelationDef> => {
|
|
63
91
|
const relations: Record<string, RelationDef> = {};
|
|
64
92
|
|
|
65
93
|
for (const [name, relation] of Object.entries(meta.relations)) {
|
|
66
|
-
switch (relation.kind) {
|
|
94
|
+
switch (relation.kind) {
|
|
67
95
|
case RelationKinds.HasOne: {
|
|
96
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
68
97
|
relations[name] = hasOne(
|
|
69
98
|
resolveTableTarget(relation.target, tableMap),
|
|
70
|
-
|
|
99
|
+
foreignKey,
|
|
71
100
|
relation.localKey,
|
|
72
101
|
relation.cascade
|
|
73
102
|
);
|
|
74
103
|
break;
|
|
75
104
|
}
|
|
76
105
|
case RelationKinds.HasMany: {
|
|
106
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
77
107
|
relations[name] = hasMany(
|
|
78
108
|
resolveTableTarget(relation.target, tableMap),
|
|
79
|
-
|
|
80
|
-
relation.localKey,
|
|
81
|
-
relation.cascade
|
|
82
|
-
);
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
case RelationKinds.BelongsTo: {
|
|
86
|
-
relations[name] = belongsTo(
|
|
87
|
-
resolveTableTarget(relation.target, tableMap),
|
|
88
|
-
relation.foreignKey,
|
|
109
|
+
foreignKey,
|
|
89
110
|
relation.localKey,
|
|
90
111
|
relation.cascade
|
|
91
112
|
);
|
|
92
113
|
break;
|
|
93
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
|
+
}
|
|
94
124
|
case RelationKinds.BelongsToMany: {
|
|
125
|
+
const pivotForeignKeyToRoot =
|
|
126
|
+
relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
127
|
+
const pivotForeignKeyToTarget =
|
|
128
|
+
relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
|
|
95
129
|
relations[name] = belongsToMany(
|
|
96
130
|
resolveTableTarget(relation.target, tableMap),
|
|
97
131
|
resolveTableTarget(relation.pivotTable, tableMap),
|
|
98
132
|
{
|
|
99
|
-
pivotForeignKeyToRoot
|
|
100
|
-
pivotForeignKeyToTarget
|
|
133
|
+
pivotForeignKeyToRoot,
|
|
134
|
+
pivotForeignKeyToTarget,
|
|
101
135
|
localKey: relation.localKey,
|
|
102
136
|
targetKey: relation.targetKey,
|
|
103
137
|
pivotPrimaryKey: relation.pivotPrimaryKey,
|
|
104
138
|
defaultPivotColumns: relation.defaultPivotColumns,
|
|
105
139
|
cascade: relation.cascade
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return relations;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Bootstraps all entities by building their table definitions and relations.
|
|
118
|
-
* @returns An array of table definitions for all bootstrapped entities.
|
|
119
|
-
*/
|
|
120
|
-
export const bootstrapEntities = (): TableDef[] => {
|
|
121
|
-
const metas = getAllEntityMetadata();
|
|
122
|
-
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
123
|
-
|
|
124
|
-
for (const meta of metas) {
|
|
125
|
-
const table = buildTableDef(meta);
|
|
126
|
-
tableMap.set(meta.target, table);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
for (const meta of metas) {
|
|
130
|
-
const table = meta.table!;
|
|
131
|
-
const relations = buildRelationDefinitions(meta, tableMap);
|
|
132
|
-
table.relations = relations;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return metas.map(meta => meta.table!) as TableDef[];
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Gets the table definition for a given entity constructor.
|
|
140
|
-
* Bootstraps entities if necessary.
|
|
141
|
-
* @param ctor - The entity constructor.
|
|
142
|
-
* @returns The table definition or undefined if not found.
|
|
143
|
-
*/
|
|
144
|
-
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
|
|
145
|
-
const meta = getEntityMetadata(ctor);
|
|
146
|
-
if (!meta) return undefined;
|
|
147
|
-
if (!meta.table) {
|
|
148
|
-
bootstrapEntities();
|
|
149
|
-
}
|
|
150
|
-
return meta.table as TTable;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Creates a select query builder for the given entity.
|
|
155
|
-
* @param ctor - The entity constructor.
|
|
156
|
-
* @returns A select query builder for the entity.
|
|
157
|
-
*/
|
|
158
|
-
type NonFunctionKeys<T> = {
|
|
159
|
-
[K in keyof T]-?: T[K] extends (...args: unknown[]) => unknown ? never : K
|
|
160
|
-
}[keyof T];
|
|
161
|
-
|
|
162
|
-
type RelationKeys<TEntity extends object> =
|
|
163
|
-
Exclude<NonFunctionKeys<TEntity>, SelectableKeys<TEntity>> & string;
|
|
164
|
-
|
|
165
|
-
type EntityTable<TEntity extends object> =
|
|
166
|
-
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
167
|
-
relations: {
|
|
168
|
-
[K in RelationKeys<TEntity>]:
|
|
169
|
-
NonNullable<TEntity[K]> extends HasManyCollection<infer TChild>
|
|
170
|
-
? HasManyRelation<EntityTable<NonNullable<TChild> & object>>
|
|
171
|
-
: NonNullable<TEntity[K]> extends ManyToManyCollection<infer TTarget>
|
|
172
|
-
? BelongsToManyRelation<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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>(
|
|
184
237
|
ctor: EntityConstructor<TEntity>
|
|
185
|
-
):
|
|
238
|
+
): TableRef<EntityTable<TEntity>> => {
|
|
186
239
|
const table = getTableDefFromEntity(ctor);
|
|
187
240
|
if (!table) {
|
|
188
241
|
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
189
242
|
}
|
|
190
|
-
return
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Public API: opt-in ergonomic entity reference (decorator-level).
|
|
195
|
-
*
|
|
196
|
-
* Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
|
|
197
|
-
* `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
|
|
198
|
-
*/
|
|
199
|
-
export const entityRef = <TTable extends TableDef = TableDef>(
|
|
200
|
-
ctor: EntityConstructor
|
|
201
|
-
): TableRef<TTable> => {
|
|
202
|
-
const table = getTableDefFromEntity<TTable>(ctor);
|
|
203
|
-
if (!table) {
|
|
204
|
-
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
205
|
-
}
|
|
206
|
-
return tableRef(table);
|
|
243
|
+
return tableRef(table as EntityTable<TEntity>);
|
|
207
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
|
-
|
|
@@ -1,34 +1,5 @@
|
|
|
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
|
-
*/
|
|
6
|
-
export interface StandardDecoratorContext {
|
|
7
|
-
kind: string;
|
|
8
|
-
name?: string | symbol;
|
|
9
|
-
metadata?: Record<PropertyKey, unknown>;
|
|
10
|
-
static?: boolean;
|
|
11
|
-
private?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Dual-mode property decorator that supports both legacy and standard decorator syntax.
|
|
16
|
-
*/
|
|
17
|
-
export interface DualModePropertyDecorator {
|
|
18
|
-
(target: object, propertyKey: string | symbol): void;
|
|
19
|
-
(value: unknown, context: StandardDecoratorContext): void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Dual-mode class decorator that supports both legacy and standard decorator syntax.
|
|
24
|
-
*/
|
|
25
|
-
export interface DualModeClassDecorator {
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
27
|
-
<TFunction extends Function>(value: TFunction): void | TFunction;
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
29
|
-
<TFunction extends Function>(value: TFunction, context: StandardDecoratorContext): void | TFunction;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
3
|
/**
|
|
33
4
|
* Bag for storing decorator metadata during the decoration phase.
|
|
34
5
|
*/
|
|
@@ -39,37 +10,31 @@ export interface DecoratorMetadataBag {
|
|
|
39
10
|
|
|
40
11
|
const METADATA_KEY = 'metal-orm:decorators';
|
|
41
12
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @param value - The value to check.
|
|
45
|
-
* @returns True if the value is a StandardDecoratorContext.
|
|
46
|
-
*/
|
|
47
|
-
export const isStandardDecoratorContext = (value: unknown): value is StandardDecoratorContext => {
|
|
48
|
-
return typeof value === 'object' && value !== null && 'kind' in (value as object);
|
|
13
|
+
type MetadataCarrier = {
|
|
14
|
+
metadata?: Record<PropertyKey, unknown>;
|
|
49
15
|
};
|
|
50
16
|
|
|
51
17
|
/**
|
|
52
18
|
* Gets or creates a metadata bag for the given decorator context.
|
|
53
|
-
* @param context - The decorator context.
|
|
19
|
+
* @param context - The decorator context with metadata support.
|
|
54
20
|
* @returns The metadata bag.
|
|
55
21
|
*/
|
|
56
|
-
export const getOrCreateMetadataBag = (context:
|
|
22
|
+
export const getOrCreateMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag => {
|
|
57
23
|
const metadata = context.metadata || (context.metadata = {} as Record<PropertyKey, unknown>);
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
24
|
+
let bag = metadata[METADATA_KEY] as DecoratorMetadataBag | undefined;
|
|
25
|
+
if (!bag) {
|
|
26
|
+
bag = { columns: [], relations: [] };
|
|
27
|
+
metadata[METADATA_KEY] = bag;
|
|
61
28
|
}
|
|
62
|
-
const bag: DecoratorMetadataBag = { columns: [], relations: [] };
|
|
63
|
-
metadata[METADATA_KEY] = bag;
|
|
64
29
|
return bag;
|
|
65
30
|
};
|
|
66
31
|
|
|
67
32
|
/**
|
|
68
33
|
* Reads the metadata bag from the given decorator context.
|
|
69
|
-
* @param context - The decorator context.
|
|
34
|
+
* @param context - The decorator context with metadata support.
|
|
70
35
|
* @returns The metadata bag if present.
|
|
71
36
|
*/
|
|
72
|
-
export const readMetadataBag = (context:
|
|
37
|
+
export const readMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag | undefined => {
|
|
73
38
|
return context.metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
|
|
74
39
|
};
|
|
75
40
|
|
|
@@ -87,7 +52,6 @@ export const readMetadataBagFromConstructor = (ctor: object): DecoratorMetadataB
|
|
|
87
52
|
|
|
88
53
|
/**
|
|
89
54
|
* Public helper to read decorator metadata from a class constructor.
|
|
90
|
-
* Standard decorators only; legacy metadata is intentionally ignored.
|
|
91
55
|
* @param ctor - The entity constructor.
|
|
92
56
|
* @returns The metadata bag if present.
|
|
93
57
|
*/
|