metal-orm 1.1.8 → 1.1.10

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 (75) hide show
  1. package/README.md +769 -764
  2. package/dist/index.cjs +2352 -226
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +605 -40
  5. package/dist/index.d.ts +605 -40
  6. package/dist/index.js +2324 -226
  7. package/dist/index.js.map +1 -1
  8. package/package.json +22 -17
  9. package/src/bulk/bulk-context.ts +83 -0
  10. package/src/bulk/bulk-delete-executor.ts +89 -0
  11. package/src/bulk/bulk-executor.base.ts +73 -0
  12. package/src/bulk/bulk-insert-executor.ts +74 -0
  13. package/src/bulk/bulk-types.ts +70 -0
  14. package/src/bulk/bulk-update-executor.ts +192 -0
  15. package/src/bulk/bulk-upsert-executor.ts +95 -0
  16. package/src/bulk/bulk-utils.ts +91 -0
  17. package/src/bulk/index.ts +18 -0
  18. package/src/codegen/typescript.ts +30 -21
  19. package/src/core/ast/expression-builders.ts +107 -10
  20. package/src/core/ast/expression-nodes.ts +52 -22
  21. package/src/core/ast/expression-visitor.ts +23 -13
  22. package/src/core/dialect/abstract.ts +30 -17
  23. package/src/core/dialect/mysql/index.ts +20 -5
  24. package/src/core/execution/db-executor.ts +96 -64
  25. package/src/core/execution/executors/better-sqlite3-executor.ts +94 -0
  26. package/src/core/execution/executors/mssql-executor.ts +66 -34
  27. package/src/core/execution/executors/mysql-executor.ts +98 -66
  28. package/src/core/execution/executors/postgres-executor.ts +33 -11
  29. package/src/core/execution/executors/sqlite-executor.ts +86 -30
  30. package/src/decorators/bootstrap.ts +482 -398
  31. package/src/decorators/column-decorator.ts +87 -96
  32. package/src/decorators/decorator-metadata.ts +100 -24
  33. package/src/decorators/entity.ts +27 -24
  34. package/src/decorators/relations.ts +231 -149
  35. package/src/decorators/transformers/transformer-decorators.ts +26 -29
  36. package/src/decorators/validators/country-validators-decorators.ts +9 -15
  37. package/src/dto/apply-filter.ts +568 -551
  38. package/src/index.ts +16 -9
  39. package/src/orm/entity-hydration.ts +116 -72
  40. package/src/orm/entity-metadata.ts +347 -301
  41. package/src/orm/entity-relations.ts +264 -207
  42. package/src/orm/entity.ts +199 -199
  43. package/src/orm/execute.ts +13 -13
  44. package/src/orm/lazy-batch/morph-many.ts +70 -0
  45. package/src/orm/lazy-batch/morph-one.ts +69 -0
  46. package/src/orm/lazy-batch/morph-to.ts +59 -0
  47. package/src/orm/lazy-batch.ts +4 -1
  48. package/src/orm/orm-session.ts +170 -104
  49. package/src/orm/pooled-executor-factory.ts +99 -58
  50. package/src/orm/query-logger.ts +49 -40
  51. package/src/orm/relation-change-processor.ts +198 -96
  52. package/src/orm/relations/belongs-to.ts +143 -143
  53. package/src/orm/relations/has-many.ts +204 -204
  54. package/src/orm/relations/has-one.ts +174 -174
  55. package/src/orm/relations/many-to-many.ts +288 -288
  56. package/src/orm/relations/morph-many.ts +156 -0
  57. package/src/orm/relations/morph-one.ts +151 -0
  58. package/src/orm/relations/morph-to.ts +162 -0
  59. package/src/orm/save-graph.ts +116 -1
  60. package/src/query-builder/expression-table-mapper.ts +5 -0
  61. package/src/query-builder/hydration-manager.ts +345 -345
  62. package/src/query-builder/hydration-planner.ts +178 -148
  63. package/src/query-builder/relation-conditions.ts +171 -151
  64. package/src/query-builder/relation-cte-builder.ts +5 -1
  65. package/src/query-builder/relation-filter-utils.ts +9 -6
  66. package/src/query-builder/relation-include-strategies.ts +44 -2
  67. package/src/query-builder/relation-join-strategies.ts +8 -1
  68. package/src/query-builder/relation-service.ts +250 -241
  69. package/src/query-builder/select/cursor-pagination.ts +323 -0
  70. package/src/query-builder/select/select-operations.ts +110 -105
  71. package/src/query-builder/select.ts +42 -1
  72. package/src/query-builder/update-include.ts +4 -0
  73. package/src/schema/relation.ts +296 -188
  74. package/src/schema/types.ts +138 -123
  75. package/src/tree/tree-decorator.ts +127 -137
@@ -1,105 +1,120 @@
1
- import type { ColumnDef } from './column-types.js';
2
- export type { ColumnDef };
3
- import { TableDef } from './table.js';
4
- import {
5
- RelationDef,
6
- HasManyRelation,
7
- HasOneRelation,
8
- BelongsToRelation,
9
- BelongsToManyRelation
10
- } from './relation.js';
11
-
12
- /**
13
- * Resolves a relation definition to its target table type.
14
- */
15
- export type RelationTargetTable<TRel extends RelationDef> =
16
- TRel extends HasManyRelation<infer TTarget> ? TTarget :
17
- TRel extends HasOneRelation<infer TTarget> ? TTarget :
18
- TRel extends BelongsToRelation<infer TTarget> ? TTarget :
19
- TRel extends BelongsToManyRelation<infer TTarget, TableDef> ? TTarget :
20
- never;
21
-
22
- export type JsonValue = string | number | boolean | null | JsonArray | JsonObject;
23
- export type JsonArray = Array<JsonValue>;
24
- export interface JsonObject { [key: string]: JsonValue }
25
-
26
- type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
27
-
28
- /**
29
- * Maps a ColumnDef to its TypeScript type representation
30
- */
31
- export type ColumnToTs<T extends ColumnDef> =
32
- [unknown] extends [T['tsType']]
33
- ? NormalizedColumnType<T> extends 'int' | 'integer' ? number :
34
- NormalizedColumnType<T> extends 'bigint' ? number | bigint :
35
- NormalizedColumnType<T> extends 'decimal' | 'float' | 'double' ? number :
36
- NormalizedColumnType<T> extends 'boolean' ? boolean :
37
- NormalizedColumnType<T> extends 'json' ? JsonValue :
38
- NormalizedColumnType<T> extends 'blob' | 'binary' | 'varbinary' | 'bytea' ? Buffer :
39
- NormalizedColumnType<T> extends 'date' | 'datetime' | 'timestamp' | 'timestamptz' ? string :
40
- string
41
- : Exclude<T['tsType'], undefined>;
42
-
43
- /**
44
- * Infers a row shape from a table definition
45
- */
46
- export type InferRow<TTable extends TableDef> = {
47
- [K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
48
- };
49
-
50
- type RelationResult<T extends RelationDef> =
51
- T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
52
- T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
53
- T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
54
- T extends BelongsToManyRelation<infer TTarget, infer TPivot> ? (InferRow<TTarget> & { _pivot?: InferRow<TPivot> })[] :
55
- never;
56
-
57
- /**
58
- * Maps relation names to the expected row results
59
- */
60
- export type RelationMap<TTable extends TableDef> = {
61
- [K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
62
- };
63
-
64
- type RelationWrapper<TRel extends RelationDef> =
65
- TRel extends HasManyRelation<infer TTarget>
66
- ? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
67
- : TRel extends HasOneRelation<infer TTarget>
68
- ? HasOneReference<EntityInstance<TTarget>>
69
- : TRel extends BelongsToManyRelation<infer TTarget, infer TPivot>
70
- ? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
71
- & ReadonlyArray<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
72
- : TRel extends BelongsToRelation<infer TTarget>
73
- ? BelongsToReference<EntityInstance<TTarget>>
74
- : never;
75
-
76
- export interface HasManyCollection<TChild> {
77
- length: number;
78
- [Symbol.iterator](): Iterator<TChild>;
79
- load(): Promise<TChild[]>;
80
- getItems(): TChild[];
81
- add(data: Partial<TChild>): TChild;
82
- attach(entity: TChild): void;
83
- remove(entity: TChild): void;
84
- clear(): void;
85
- }
86
-
87
- export interface BelongsToReferenceApi<TParent extends object = object> {
88
- load(): Promise<TParent | null>;
89
- get(): TParent | null;
90
- set(data: Partial<TParent> | TParent | null): TParent | null;
91
- }
92
-
93
- export type BelongsToReference<TParent extends object = object> = BelongsToReferenceApi<TParent> & Partial<TParent>;
94
-
95
- export interface HasOneReferenceApi<TChild extends object = object> {
96
- load(): Promise<TChild | null>;
97
- get(): TChild | null;
98
- set(data: Partial<TChild> | TChild | null): TChild | null;
99
- }
100
-
101
- export type HasOneReference<TChild extends object = object> = HasOneReferenceApi<TChild> & Partial<TChild>;
102
-
1
+ import type { ColumnDef } from './column-types.js';
2
+ export type { ColumnDef };
3
+ import { TableDef } from './table.js';
4
+ import {
5
+ RelationDef,
6
+ HasManyRelation,
7
+ HasOneRelation,
8
+ BelongsToRelation,
9
+ BelongsToManyRelation,
10
+ MorphToRelation,
11
+ MorphOneRelation,
12
+ MorphManyRelation
13
+ } from './relation.js';
14
+
15
+ /**
16
+ * Resolves a relation definition to its target table type.
17
+ */
18
+ export type RelationTargetTable<TRel extends RelationDef> =
19
+ TRel extends HasManyRelation<infer TTarget> ? TTarget :
20
+ TRel extends HasOneRelation<infer TTarget> ? TTarget :
21
+ TRel extends BelongsToRelation<infer TTarget> ? TTarget :
22
+ TRel extends BelongsToManyRelation<infer TTarget, TableDef> ? TTarget :
23
+ TRel extends MorphOneRelation<infer TTarget> ? TTarget :
24
+ TRel extends MorphManyRelation<infer TTarget> ? TTarget :
25
+ TRel extends MorphToRelation ? never :
26
+ never;
27
+
28
+ export type JsonValue = string | number | boolean | null | JsonArray | JsonObject;
29
+ export type JsonArray = Array<JsonValue>;
30
+ export interface JsonObject { [key: string]: JsonValue }
31
+
32
+ type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
33
+
34
+ /**
35
+ * Maps a ColumnDef to its TypeScript type representation
36
+ */
37
+ export type ColumnToTs<T extends ColumnDef> =
38
+ [unknown] extends [T['tsType']]
39
+ ? NormalizedColumnType<T> extends 'int' | 'integer' ? number :
40
+ NormalizedColumnType<T> extends 'bigint' ? number | bigint :
41
+ NormalizedColumnType<T> extends 'decimal' | 'float' | 'double' ? number :
42
+ NormalizedColumnType<T> extends 'boolean' ? boolean :
43
+ NormalizedColumnType<T> extends 'json' ? JsonValue :
44
+ NormalizedColumnType<T> extends 'blob' | 'binary' | 'varbinary' | 'bytea' ? Buffer :
45
+ NormalizedColumnType<T> extends 'date' | 'datetime' | 'timestamp' | 'timestamptz' ? string :
46
+ string
47
+ : Exclude<T['tsType'], undefined>;
48
+
49
+ /**
50
+ * Infers a row shape from a table definition
51
+ */
52
+ export type InferRow<TTable extends TableDef> = {
53
+ [K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
54
+ };
55
+
56
+ type RelationResult<T extends RelationDef> =
57
+ T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
58
+ T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
59
+ T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
60
+ T extends BelongsToManyRelation<infer TTarget, infer TPivot> ? (InferRow<TTarget> & { _pivot?: InferRow<TPivot> })[] :
61
+ T extends MorphOneRelation<infer TTarget> ? InferRow<TTarget> | null :
62
+ T extends MorphManyRelation<infer TTarget> ? InferRow<TTarget>[] :
63
+ T extends MorphToRelation ? Record<string, unknown> | null :
64
+ never;
65
+
66
+ /**
67
+ * Maps relation names to the expected row results
68
+ */
69
+ export type RelationMap<TTable extends TableDef> = {
70
+ [K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
71
+ };
72
+
73
+ type RelationWrapper<TRel extends RelationDef> =
74
+ TRel extends HasManyRelation<infer TTarget>
75
+ ? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
76
+ : TRel extends HasOneRelation<infer TTarget>
77
+ ? HasOneReference<EntityInstance<TTarget>>
78
+ : TRel extends BelongsToManyRelation<infer TTarget, infer TPivot>
79
+ ? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
80
+ & ReadonlyArray<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
81
+ : TRel extends BelongsToRelation<infer TTarget>
82
+ ? BelongsToReference<EntityInstance<TTarget>>
83
+ : TRel extends MorphOneRelation<infer TTarget>
84
+ ? HasOneReference<EntityInstance<TTarget>>
85
+ : TRel extends MorphManyRelation<infer TTarget>
86
+ ? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
87
+ : TRel extends MorphToRelation
88
+ ? BelongsToReference<EntityInstance<TableDef>>
89
+ : never;
90
+
91
+ export interface HasManyCollection<TChild> {
92
+ length: number;
93
+ [Symbol.iterator](): Iterator<TChild>;
94
+ load(): Promise<TChild[]>;
95
+ getItems(): TChild[];
96
+ add(data: Partial<TChild>): TChild;
97
+ attach(entity: TChild): void;
98
+ remove(entity: TChild): void;
99
+ clear(): void;
100
+ }
101
+
102
+ export interface BelongsToReferenceApi<TParent extends object = object> {
103
+ load(): Promise<TParent | null>;
104
+ get(): TParent | null;
105
+ set(data: Partial<TParent> | TParent | null): TParent | null;
106
+ }
107
+
108
+ export type BelongsToReference<TParent extends object = object> = BelongsToReferenceApi<TParent> & Partial<TParent>;
109
+
110
+ export interface HasOneReferenceApi<TChild extends object = object> {
111
+ load(): Promise<TChild | null>;
112
+ get(): TChild | null;
113
+ set(data: Partial<TChild> | TChild | null): TChild | null;
114
+ }
115
+
116
+ export type HasOneReference<TChild extends object = object> = HasOneReferenceApi<TChild> & Partial<TChild>;
117
+
103
118
  export interface ManyToManyCollection<TTarget, TPivot extends object | undefined = undefined> {
104
119
  length: number;
105
120
  [Symbol.iterator](): Iterator<TTarget>;
@@ -111,24 +126,24 @@ export interface ManyToManyCollection<TTarget, TPivot extends object | undefined
111
126
  /** @internal Type-level marker for the related pivot entity */
112
127
  readonly __pivotType?: TPivot;
113
128
  }
114
-
115
- export type EntityInstance<
116
- TTable extends TableDef,
117
- TRow = InferRow<TTable>
118
- > = TRow & {
119
- [K in keyof RelationMap<TTable>]: RelationWrapper<TTable['relations'][K]>;
120
- } & {
121
- $load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
122
- };
123
-
124
- export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
125
-
126
- type IsAny<T> = 0 extends (1 & T) ? true : false;
127
-
128
- export type SelectableKeys<T> = {
129
- [K in keyof T]-?: IsAny<T[K]> extends true
130
- ? never
131
- : NonNullable<T[K]> extends Primitive
132
- ? K
133
- : never
134
- }[keyof T];
129
+
130
+ export type EntityInstance<
131
+ TTable extends TableDef,
132
+ TRow = InferRow<TTable>
133
+ > = TRow & {
134
+ [K in keyof RelationMap<TTable>]: RelationWrapper<TTable['relations'][K]>;
135
+ } & {
136
+ $load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
137
+ };
138
+
139
+ export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
140
+
141
+ type IsAny<T> = 0 extends (1 & T) ? true : false;
142
+
143
+ export type SelectableKeys<T> = {
144
+ [K in keyof T]-?: IsAny<T[K]> extends true
145
+ ? never
146
+ : NonNullable<T[K]> extends Primitive
147
+ ? K
148
+ : never
149
+ }[keyof T];
@@ -5,16 +5,16 @@
5
5
  * Integrates with MetalORM's decorator entity system.
6
6
  */
7
7
 
8
- import type { TreeConfig } from './tree-types.js';
9
- import { resolveTreeConfig } from './tree-types.js';
10
- import { RelationKinds, belongsTo, hasMany } from '../schema/relation.js';
11
- import { addRelationMetadata, getEntityMetadata, type EntityConstructor } from '../orm/entity-metadata.js';
12
- import {
13
- type DecoratorTreeMetadata,
14
- getOrCreateMetadataBag,
15
- readMetadataBag,
16
- readMetadataBagFromConstructor
17
- } from '../decorators/decorator-metadata.js';
8
+ import type { TreeConfig } from './tree-types.js';
9
+ import { resolveTreeConfig } from './tree-types.js';
10
+ import { RelationKinds, belongsTo, hasMany } from '../schema/relation.js';
11
+ import { addRelationMetadata, getEntityMetadata, type EntityConstructor } from '../orm/entity-metadata.js';
12
+ import {
13
+ type DecoratorTreeMetadata,
14
+ readMetadataBag,
15
+ readMetadataBagFromConstructor,
16
+ resolveFieldDecoratorInfo
17
+ } from '../decorators/decorator-metadata.js';
18
18
 
19
19
  /**
20
20
  * Symbol key for storing tree metadata on entity classes.
@@ -81,25 +81,27 @@ export interface TreeDecoratorOptions {
81
81
  * }
82
82
  * ```
83
83
  */
84
- export function Tree(options: TreeDecoratorOptions = {}) {
85
- return function <T extends abstract new (...args: unknown[]) => object>(
86
- target: T,
87
- context: ClassDecoratorContext<T>
88
- ): T {
89
- const config = resolveTreeConfig(options);
90
- const metadataBag = readMetadataBag(context) ?? readMetadataBagFromConstructor(target);
91
- const metadata: TreeMetadata = {
92
- config,
93
- parentProperty: metadataBag?.tree?.parentProperty,
94
- childrenProperty: metadataBag?.tree?.childrenProperty
95
- };
96
-
97
- setTreeMetadataOnClass(target, metadata);
98
- registerTreeRelations(target as unknown as EntityConstructor, metadata);
99
-
100
- return target;
101
- };
102
- }
84
+ export function Tree(options: TreeDecoratorOptions = {}) {
85
+ return function <T extends abstract new (...args: unknown[]) => object>(
86
+ target: T,
87
+ context?: ClassDecoratorContext<T>
88
+ ): T {
89
+ const config = resolveTreeConfig(options);
90
+ const metadataBag = context
91
+ ? readMetadataBag(context) ?? readMetadataBagFromConstructor(target)
92
+ : readMetadataBagFromConstructor(target);
93
+ const metadata: TreeMetadata = {
94
+ config,
95
+ parentProperty: metadataBag?.tree?.parentProperty,
96
+ childrenProperty: metadataBag?.tree?.childrenProperty
97
+ };
98
+
99
+ setTreeMetadataOnClass(target, metadata);
100
+ registerTreeRelations(target as unknown as EntityConstructor, metadata);
101
+
102
+ return target;
103
+ };
104
+ }
103
105
 
104
106
  /**
105
107
  * Decorator to mark a property as the parent relation in a tree entity.
@@ -110,22 +112,16 @@ export function Tree(options: TreeDecoratorOptions = {}) {
110
112
  * parent?: Category;
111
113
  * ```
112
114
  */
113
- export function TreeParent() {
114
- return function <T, V>(
115
- _value: undefined,
116
- context: ClassFieldDecoratorContext<T, V>
117
- ): void {
118
- if (!context.name) {
119
- throw new Error('TreeParent decorator requires a property name');
120
- }
121
- if (context.private) {
122
- throw new Error('TreeParent decorator does not support private fields');
123
- }
124
- const propertyName = String(context.name);
125
- const bag = getOrCreateMetadataBag(context);
126
- bag.tree = { ...bag.tree, parentProperty: propertyName };
127
- };
128
- }
115
+ export function TreeParent() {
116
+ return function (targetOrValue: unknown, contextOrProperty: unknown): void {
117
+ const { propertyName, bag } = resolveFieldDecoratorInfo(
118
+ targetOrValue,
119
+ contextOrProperty,
120
+ 'TreeParent'
121
+ );
122
+ bag.tree = { ...bag.tree, parentProperty: propertyName };
123
+ };
124
+ }
129
125
 
130
126
  /**
131
127
  * Decorator to mark a property as the children relation in a tree entity.
@@ -136,54 +132,48 @@ export function TreeParent() {
136
132
  * children?: Category[];
137
133
  * ```
138
134
  */
139
- export function TreeChildren() {
140
- return function <T, V>(
141
- _value: undefined,
142
- context: ClassFieldDecoratorContext<T, V>
143
- ): void {
144
- if (!context.name) {
145
- throw new Error('TreeChildren decorator requires a property name');
146
- }
147
- if (context.private) {
148
- throw new Error('TreeChildren decorator does not support private fields');
149
- }
150
- const propertyName = String(context.name);
151
- const bag = getOrCreateMetadataBag(context);
152
- bag.tree = { ...bag.tree, childrenProperty: propertyName };
153
- };
154
- }
155
-
156
- /**
157
- * Synchronizes tree metadata + self-relations for entities using @Tree decorators.
158
- * Safe to call multiple times (idempotent).
159
- */
160
- export function syncTreeEntityMetadata(
161
- ctor: EntityConstructor,
162
- treeMetadata?: DecoratorTreeMetadata
163
- ): void {
164
- const current = getTreeMetadata(ctor);
165
- if (!current) return;
166
- const metadataBag = readMetadataBagFromConstructor(ctor);
167
-
168
- const nextParentProperty =
169
- treeMetadata?.parentProperty ?? metadataBag?.tree?.parentProperty ?? current.parentProperty;
170
- const nextChildrenProperty =
171
- treeMetadata?.childrenProperty ?? metadataBag?.tree?.childrenProperty ?? current.childrenProperty;
172
-
173
- if (nextParentProperty !== current.parentProperty || nextChildrenProperty !== current.childrenProperty) {
174
- setTreeMetadata(ctor, {
175
- ...current,
176
- parentProperty: nextParentProperty,
177
- childrenProperty: nextChildrenProperty
178
- });
179
- }
180
-
181
- registerTreeRelations(ctor, {
182
- ...current,
183
- parentProperty: nextParentProperty,
184
- childrenProperty: nextChildrenProperty
185
- });
186
- }
135
+ export function TreeChildren() {
136
+ return function (targetOrValue: unknown, contextOrProperty: unknown): void {
137
+ const { propertyName, bag } = resolveFieldDecoratorInfo(
138
+ targetOrValue,
139
+ contextOrProperty,
140
+ 'TreeChildren'
141
+ );
142
+ bag.tree = { ...bag.tree, childrenProperty: propertyName };
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Synchronizes tree metadata + self-relations for entities using @Tree decorators.
148
+ * Safe to call multiple times (idempotent).
149
+ */
150
+ export function syncTreeEntityMetadata(
151
+ ctor: EntityConstructor,
152
+ treeMetadata?: DecoratorTreeMetadata
153
+ ): void {
154
+ const current = getTreeMetadata(ctor);
155
+ if (!current) return;
156
+ const metadataBag = readMetadataBagFromConstructor(ctor);
157
+
158
+ const nextParentProperty =
159
+ treeMetadata?.parentProperty ?? metadataBag?.tree?.parentProperty ?? current.parentProperty;
160
+ const nextChildrenProperty =
161
+ treeMetadata?.childrenProperty ?? metadataBag?.tree?.childrenProperty ?? current.childrenProperty;
162
+
163
+ if (nextParentProperty !== current.parentProperty || nextChildrenProperty !== current.childrenProperty) {
164
+ setTreeMetadata(ctor, {
165
+ ...current,
166
+ parentProperty: nextParentProperty,
167
+ childrenProperty: nextChildrenProperty
168
+ });
169
+ }
170
+
171
+ registerTreeRelations(ctor, {
172
+ ...current,
173
+ parentProperty: nextParentProperty,
174
+ childrenProperty: nextChildrenProperty
175
+ });
176
+ }
187
177
 
188
178
  /**
189
179
  * Gets tree metadata from an entity class.
@@ -197,50 +187,50 @@ export function getTreeMetadata(
197
187
  /**
198
188
  * Sets tree metadata on an entity class.
199
189
  */
200
- export function setTreeMetadata(
201
- target: new (...args: unknown[]) => object,
202
- metadata: TreeMetadata
203
- ): void {
204
- (target as unknown as Record<symbol, TreeMetadata>)[TREE_METADATA_KEY] = metadata;
205
- }
206
-
207
- const registerTreeRelations = (
208
- target: EntityConstructor,
209
- metadata: TreeMetadata
210
- ): void => {
211
- const entityMeta = getEntityMetadata(target);
212
- if (!entityMeta) return;
213
-
214
- if (metadata.parentProperty && !entityMeta.relations[metadata.parentProperty]) {
215
- addRelationMetadata(target, metadata.parentProperty, {
216
- kind: RelationKinds.BelongsTo,
217
- propertyKey: metadata.parentProperty,
218
- target: () => target,
219
- foreignKey: metadata.config.parentKey
220
- });
221
- if (entityMeta.table && !entityMeta.table.relations[metadata.parentProperty]) {
222
- entityMeta.table.relations[metadata.parentProperty] = belongsTo(
223
- entityMeta.table,
224
- metadata.config.parentKey
225
- );
226
- }
227
- }
228
-
229
- if (metadata.childrenProperty && !entityMeta.relations[metadata.childrenProperty]) {
230
- addRelationMetadata(target, metadata.childrenProperty, {
231
- kind: RelationKinds.HasMany,
232
- propertyKey: metadata.childrenProperty,
233
- target: () => target,
234
- foreignKey: metadata.config.parentKey
235
- });
236
- if (entityMeta.table && !entityMeta.table.relations[metadata.childrenProperty]) {
237
- entityMeta.table.relations[metadata.childrenProperty] = hasMany(
238
- entityMeta.table,
239
- metadata.config.parentKey
240
- );
241
- }
242
- }
243
- };
190
+ export function setTreeMetadata(
191
+ target: new (...args: unknown[]) => object,
192
+ metadata: TreeMetadata
193
+ ): void {
194
+ (target as unknown as Record<symbol, TreeMetadata>)[TREE_METADATA_KEY] = metadata;
195
+ }
196
+
197
+ const registerTreeRelations = (
198
+ target: EntityConstructor,
199
+ metadata: TreeMetadata
200
+ ): void => {
201
+ const entityMeta = getEntityMetadata(target);
202
+ if (!entityMeta) return;
203
+
204
+ if (metadata.parentProperty && !entityMeta.relations[metadata.parentProperty]) {
205
+ addRelationMetadata(target, metadata.parentProperty, {
206
+ kind: RelationKinds.BelongsTo,
207
+ propertyKey: metadata.parentProperty,
208
+ target: () => target,
209
+ foreignKey: metadata.config.parentKey
210
+ });
211
+ if (entityMeta.table && !entityMeta.table.relations[metadata.parentProperty]) {
212
+ entityMeta.table.relations[metadata.parentProperty] = belongsTo(
213
+ entityMeta.table,
214
+ metadata.config.parentKey
215
+ );
216
+ }
217
+ }
218
+
219
+ if (metadata.childrenProperty && !entityMeta.relations[metadata.childrenProperty]) {
220
+ addRelationMetadata(target, metadata.childrenProperty, {
221
+ kind: RelationKinds.HasMany,
222
+ propertyKey: metadata.childrenProperty,
223
+ target: () => target,
224
+ foreignKey: metadata.config.parentKey
225
+ });
226
+ if (entityMeta.table && !entityMeta.table.relations[metadata.childrenProperty]) {
227
+ entityMeta.table.relations[metadata.childrenProperty] = hasMany(
228
+ entityMeta.table,
229
+ metadata.config.parentKey
230
+ );
231
+ }
232
+ }
233
+ };
244
234
 
245
235
  /**
246
236
  * Sets tree metadata on an entity class (internal, handles abstract classes).