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.
Files changed (46) hide show
  1. package/README.md +23 -13
  2. package/dist/index.cjs +1750 -733
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +244 -157
  5. package/dist/index.d.ts +244 -157
  6. package/dist/index.js +1745 -733
  7. package/dist/index.js.map +1 -1
  8. package/package.json +69 -69
  9. package/src/core/ddl/schema-generator.ts +44 -1
  10. package/src/decorators/bootstrap.ts +186 -113
  11. package/src/decorators/column-decorator.ts +8 -49
  12. package/src/decorators/decorator-metadata.ts +10 -46
  13. package/src/decorators/entity.ts +30 -40
  14. package/src/decorators/relations.ts +30 -56
  15. package/src/orm/entity-hydration.ts +72 -0
  16. package/src/orm/entity-meta.ts +18 -13
  17. package/src/orm/entity-metadata.ts +240 -238
  18. package/src/orm/entity-relation-cache.ts +39 -0
  19. package/src/orm/entity-relations.ts +207 -0
  20. package/src/orm/entity.ts +124 -343
  21. package/src/orm/execute.ts +87 -20
  22. package/src/orm/lazy-batch/belongs-to-many.ts +134 -0
  23. package/src/orm/lazy-batch/belongs-to.ts +108 -0
  24. package/src/orm/lazy-batch/has-many.ts +69 -0
  25. package/src/orm/lazy-batch/has-one.ts +68 -0
  26. package/src/orm/lazy-batch/shared.ts +125 -0
  27. package/src/orm/lazy-batch.ts +4 -309
  28. package/src/orm/relations/belongs-to.ts +2 -2
  29. package/src/orm/relations/has-many.ts +23 -9
  30. package/src/orm/relations/has-one.ts +2 -2
  31. package/src/orm/relations/many-to-many.ts +29 -14
  32. package/src/orm/save-graph-types.ts +2 -2
  33. package/src/orm/save-graph.ts +18 -18
  34. package/src/query-builder/relation-conditions.ts +80 -59
  35. package/src/query-builder/relation-cte-builder.ts +63 -0
  36. package/src/query-builder/relation-filter-utils.ts +159 -0
  37. package/src/query-builder/relation-include-strategies.ts +177 -0
  38. package/src/query-builder/relation-join-planner.ts +80 -0
  39. package/src/query-builder/relation-service.ts +103 -159
  40. package/src/query-builder/relation-types.ts +43 -12
  41. package/src/query-builder/select/projection-facet.ts +23 -23
  42. package/src/query-builder/select/select-operations.ts +145 -0
  43. package/src/query-builder/select.ts +373 -426
  44. package/src/schema/relation.ts +22 -18
  45. package/src/schema/table.ts +22 -9
  46. package/src/schema/types.ts +103 -84
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "metal-orm",
3
- "version": "1.0.57",
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
- if (options.includePrimary && col.primary) {
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 RelationDef
9
- } from '../schema/relation.js';
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 { SelectableKeys, ColumnDef } from '../schema/types.js';
24
-
25
- const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
26
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
27
- if (typeof target === 'function' && (target as Function).prototype === undefined) {
28
- return (target as () => EntityOrTableTarget)();
29
- }
30
- return target as EntityOrTableTarget;
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
- return table;
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: { relations: Record<string, RelationMetadata> },
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
- relation.foreignKey,
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
- relation.foreignKey,
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: relation.pivotForeignKeyToRoot,
89
- pivotForeignKeyToTarget: relation.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
- export const selectFromEntity = <TEntity extends object>(
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
- ): SelectQueryBuilder<unknown, TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>> => {
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 new SelectQueryBuilder(table as unknown as TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>);
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
- addColumnMetadata,
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: StandardDecoratorContext,
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
- const decorator: DualModePropertyDecorator = (targetOrValue, propertyKeyOrContext) => {
108
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
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
-