metal-orm 1.0.58 → 1.0.60

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 (41) hide show
  1. package/README.md +34 -31
  2. package/dist/index.cjs +1583 -901
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +400 -129
  5. package/dist/index.d.ts +400 -129
  6. package/dist/index.js +1575 -901
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/core/ddl/schema-generator.ts +44 -1
  10. package/src/decorators/bootstrap.ts +183 -146
  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/index.ts +7 -7
  16. package/src/orm/entity-hydration.ts +72 -0
  17. package/src/orm/entity-meta.ts +13 -11
  18. package/src/orm/entity-metadata.ts +240 -238
  19. package/src/orm/entity-relation-cache.ts +39 -0
  20. package/src/orm/entity-relations.ts +207 -0
  21. package/src/orm/entity.ts +124 -410
  22. package/src/orm/execute.ts +4 -4
  23. package/src/orm/lazy-batch/belongs-to-many.ts +134 -0
  24. package/src/orm/lazy-batch/belongs-to.ts +108 -0
  25. package/src/orm/lazy-batch/has-many.ts +69 -0
  26. package/src/orm/lazy-batch/has-one.ts +68 -0
  27. package/src/orm/lazy-batch/shared.ts +125 -0
  28. package/src/orm/lazy-batch.ts +4 -492
  29. package/src/orm/relations/many-to-many.ts +2 -1
  30. package/src/query-builder/relation-cte-builder.ts +63 -0
  31. package/src/query-builder/relation-filter-utils.ts +159 -0
  32. package/src/query-builder/relation-include-strategies.ts +177 -0
  33. package/src/query-builder/relation-join-planner.ts +80 -0
  34. package/src/query-builder/relation-service.ts +119 -479
  35. package/src/query-builder/relation-types.ts +41 -10
  36. package/src/query-builder/select/projection-facet.ts +23 -23
  37. package/src/query-builder/select/select-operations.ts +145 -0
  38. package/src/query-builder/select.ts +329 -221
  39. package/src/schema/relation.ts +22 -18
  40. package/src/schema/table.ts +22 -9
  41. package/src/schema/types.ts +14 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -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,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
- 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');
57
85
  };
58
86
 
59
87
  const buildRelationDefinitions = (
60
- meta: { relations: Record<string, RelationMetadata> },
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
- relation.foreignKey,
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
- relation.foreignKey,
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: relation.pivotForeignKeyToRoot,
100
- pivotForeignKeyToTarget: relation.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<EntityTable<NonNullable<TTarget> & object>>
173
- : NonNullable<TEntity[K]> extends HasOneReference<infer TChild>
174
- ? HasOneRelation<EntityTable<NonNullable<TChild> & object>>
175
- : NonNullable<TEntity[K]> extends BelongsToReference<infer TParent>
176
- ? BelongsToRelation<EntityTable<NonNullable<TParent> & object>>
177
- : NonNullable<TEntity[K]> extends object
178
- ? BelongsToRelation<EntityTable<NonNullable<TEntity[K]> & object>>
179
- : never;
180
- };
181
- };
182
-
183
- 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>(
184
237
  ctor: EntityConstructor<TEntity>
185
- ): SelectQueryBuilder<unknown, EntityTable<TEntity>> => {
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 new SelectQueryBuilder(table as unknown as EntityTable<TEntity>);
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
- 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
-
@@ -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
- * Checks if a value is a StandardDecoratorContext.
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: StandardDecoratorContext): DecoratorMetadataBag => {
22
+ export const getOrCreateMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag => {
57
23
  const metadata = context.metadata || (context.metadata = {} as Record<PropertyKey, unknown>);
58
- const existing = metadata[METADATA_KEY] as DecoratorMetadataBag | undefined;
59
- if (existing) {
60
- return existing;
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: StandardDecoratorContext): DecoratorMetadataBag | undefined => {
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
  */