metal-orm 1.1.10 → 1.1.16

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 (192) hide show
  1. package/README.md +758 -769
  2. package/dist/index.cjs +106 -43
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +5 -5
  5. package/dist/index.d.ts +5 -5
  6. package/dist/index.js +106 -43
  7. package/dist/index.js.map +1 -1
  8. package/package.json +100 -96
  9. package/scripts/generate-entities/render.mjs +21 -12
  10. package/scripts/generate-entities/schema.mjs +209 -195
  11. package/scripts/generate-entities/tree-detection.mjs +159 -153
  12. package/scripts/inflection/pt-br.mjs +468 -468
  13. package/scripts/run-eslint.mjs +33 -34
  14. package/scripts/show-sql-seed.sql +102 -0
  15. package/scripts/show-sql.mjs +321 -325
  16. package/src/bulk/bulk-delete-executor.ts +3 -5
  17. package/src/bulk/bulk-insert-executor.ts +7 -7
  18. package/src/bulk/bulk-update-executor.ts +2 -2
  19. package/src/bulk/bulk-upsert-executor.ts +5 -7
  20. package/src/codegen/naming-strategy.ts +54 -54
  21. package/src/codegen/typescript.ts +482 -482
  22. package/src/core/ast/adapters.ts +1 -1
  23. package/src/core/ast/builders.ts +123 -123
  24. package/src/core/ast/expression-builders.ts +745 -745
  25. package/src/core/ast/expression-nodes.ts +304 -304
  26. package/src/core/ast/expression-visitor.ts +212 -212
  27. package/src/core/ast/expression.ts +20 -20
  28. package/src/core/ast/helpers.ts +14 -14
  29. package/src/core/ast/join.ts +18 -18
  30. package/src/core/ast/query.ts +200 -200
  31. package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
  32. package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
  33. package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
  34. package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
  35. package/src/core/ddl/dialects/render-reference.test.ts +70 -70
  36. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
  37. package/src/core/ddl/introspect/catalogs/index.ts +5 -5
  38. package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
  39. package/src/core/ddl/introspect/context.ts +15 -15
  40. package/src/core/ddl/introspect/functions/postgres.ts +35 -35
  41. package/src/core/ddl/introspect/mysql.ts +113 -36
  42. package/src/core/ddl/introspect/registry.ts +40 -40
  43. package/src/core/ddl/introspect/run-select.ts +35 -35
  44. package/src/core/ddl/introspect/utils.ts +56 -56
  45. package/src/core/ddl/naming-strategy.ts +16 -16
  46. package/src/core/ddl/schema-dialect.ts +56 -56
  47. package/src/core/ddl/schema-diff.ts +234 -234
  48. package/src/core/ddl/schema-generator.ts +1 -1
  49. package/src/core/ddl/schema-introspect.ts +25 -25
  50. package/src/core/ddl/sql-writing.ts +171 -171
  51. package/src/core/dialect/abstract.ts +541 -541
  52. package/src/core/dialect/base/cte-compiler.ts +33 -33
  53. package/src/core/dialect/base/function-table-formatter.ts +103 -103
  54. package/src/core/dialect/base/groupby-compiler.ts +21 -21
  55. package/src/core/dialect/base/join-compiler.ts +25 -25
  56. package/src/core/dialect/base/orderby-compiler.ts +35 -35
  57. package/src/core/dialect/base/pagination-strategy.ts +32 -32
  58. package/src/core/dialect/base/returning-strategy.ts +57 -57
  59. package/src/core/dialect/base/sql-dialect.ts +294 -294
  60. package/src/core/dialect/dialect-factory.ts +91 -91
  61. package/src/core/dialect/mssql/functions.ts +120 -120
  62. package/src/core/dialect/mssql/index.ts +317 -317
  63. package/src/core/dialect/mysql/functions.ts +103 -103
  64. package/src/core/dialect/mysql/index.ts +105 -105
  65. package/src/core/dialect/postgres/functions.ts +108 -108
  66. package/src/core/dialect/postgres/index.ts +91 -91
  67. package/src/core/dialect/sqlite/functions.ts +129 -129
  68. package/src/core/dialect/sqlite/index.ts +67 -67
  69. package/src/core/driver/database-driver.ts +19 -19
  70. package/src/core/driver/mssql-driver.ts +23 -23
  71. package/src/core/driver/mysql-driver.ts +23 -23
  72. package/src/core/driver/postgres-driver.ts +23 -23
  73. package/src/core/driver/sqlite-driver.ts +23 -23
  74. package/src/core/execution/db-executor.ts +92 -92
  75. package/src/core/execution/executors/better-sqlite3-executor.ts +94 -94
  76. package/src/core/execution/executors/mssql-executor.ts +184 -184
  77. package/src/core/execution/executors/mysql-executor.ts +88 -88
  78. package/src/core/execution/executors/postgres-executor.ts +26 -26
  79. package/src/core/execution/executors/sqlite-executor.ts +42 -42
  80. package/src/core/execution/pooling/pool-types.ts +30 -30
  81. package/src/core/execution/pooling/pool.ts +275 -275
  82. package/src/core/functions/array.ts +35 -35
  83. package/src/core/functions/control-flow.ts +79 -79
  84. package/src/core/functions/datetime.ts +237 -237
  85. package/src/core/functions/json.ts +70 -70
  86. package/src/core/functions/numeric.ts +297 -297
  87. package/src/core/functions/text.ts +334 -334
  88. package/src/core/functions/types.ts +33 -33
  89. package/src/core/hydration/types.ts +51 -51
  90. package/src/core/sql/sql-operator-config.ts +39 -39
  91. package/src/core/sql/sql.ts +152 -152
  92. package/src/decorators/column-decorator.ts +87 -87
  93. package/src/decorators/decorator-metadata.ts +143 -143
  94. package/src/decorators/entity.ts +93 -93
  95. package/src/decorators/index.ts +39 -39
  96. package/src/decorators/relations.ts +231 -231
  97. package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
  98. package/src/decorators/transformers/transformer-decorators.ts +134 -134
  99. package/src/decorators/transformers/transformer-executor.ts +171 -171
  100. package/src/decorators/transformers/transformer-metadata.ts +73 -73
  101. package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
  102. package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
  103. package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
  104. package/src/decorators/validators/country-validator-registry.ts +64 -64
  105. package/src/decorators/validators/country-validators-decorators.ts +203 -203
  106. package/src/decorators/validators/country-validators.ts +105 -105
  107. package/src/decorators/validators/validator-metadata.ts +59 -59
  108. package/src/dto/dto-types.ts +226 -226
  109. package/src/dto/filter-types.ts +141 -141
  110. package/src/dto/index.ts +95 -95
  111. package/src/dto/openapi/generators/base.ts +29 -29
  112. package/src/dto/openapi/generators/column.ts +37 -37
  113. package/src/dto/openapi/generators/dto.ts +91 -91
  114. package/src/dto/openapi/generators/filter.ts +75 -75
  115. package/src/dto/openapi/generators/nested-dto.ts +618 -618
  116. package/src/dto/openapi/generators/pagination.ts +111 -111
  117. package/src/dto/openapi/generators/relation-filter.ts +228 -228
  118. package/src/dto/openapi/index.ts +12 -12
  119. package/src/dto/openapi/types.ts +101 -101
  120. package/src/dto/openapi/utilities.ts +95 -95
  121. package/src/dto/pagination-utils.ts +150 -150
  122. package/src/dto/transform.ts +197 -197
  123. package/src/index.ts +86 -86
  124. package/src/orm/als.ts +58 -58
  125. package/src/orm/domain-event-bus.ts +119 -119
  126. package/src/orm/entity-context.ts +86 -86
  127. package/src/orm/entity-materializer.ts +159 -159
  128. package/src/orm/entity-meta.ts +97 -97
  129. package/src/orm/entity-registry.ts +39 -39
  130. package/src/orm/entity-relation-cache.ts +39 -39
  131. package/src/orm/execute.ts +194 -194
  132. package/src/orm/execution-context.ts +18 -18
  133. package/src/orm/hydration-context.ts +26 -26
  134. package/src/orm/hydration.ts +112 -112
  135. package/src/orm/identity-map.ts +61 -61
  136. package/src/orm/interceptor-pipeline.ts +16 -16
  137. package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
  138. package/src/orm/lazy-batch/belongs-to.ts +108 -108
  139. package/src/orm/lazy-batch/has-many.ts +69 -69
  140. package/src/orm/lazy-batch/has-one.ts +68 -68
  141. package/src/orm/lazy-batch/shared.ts +125 -125
  142. package/src/orm/orm-session.ts +620 -620
  143. package/src/orm/orm.ts +34 -34
  144. package/src/orm/pooled-executor-factory.ts +121 -121
  145. package/src/orm/query-logger.ts +40 -40
  146. package/src/orm/relation-change-processor.ts +420 -420
  147. package/src/orm/relations/belongs-to.ts +143 -143
  148. package/src/orm/relations/has-many.ts +204 -204
  149. package/src/orm/relations/has-one.ts +174 -174
  150. package/src/orm/relations/many-to-many.ts +288 -288
  151. package/src/orm/relations/morph-many.ts +156 -156
  152. package/src/orm/relations/morph-one.ts +151 -151
  153. package/src/orm/relations/morph-to.ts +162 -162
  154. package/src/orm/runtime-types.ts +92 -92
  155. package/src/orm/save-graph.ts +561 -561
  156. package/src/orm/transaction-runner.ts +24 -24
  157. package/src/orm/unit-of-work.ts +426 -426
  158. package/src/query/index.ts +69 -69
  159. package/src/query/target.ts +46 -46
  160. package/src/query-builder/column-selector.ts +81 -81
  161. package/src/query-builder/delete.ts +141 -141
  162. package/src/query-builder/insert-query-state.ts +149 -149
  163. package/src/query-builder/insert.ts +117 -117
  164. package/src/query-builder/query-ast-service.ts +287 -287
  165. package/src/query-builder/query-resolution.ts +78 -78
  166. package/src/query-builder/raw-column-parser.ts +38 -38
  167. package/src/query-builder/relation-filter-utils.ts +153 -153
  168. package/src/query-builder/relation-join-planner.ts +10 -10
  169. package/src/query-builder/relation-manager.ts +87 -87
  170. package/src/query-builder/relation-projection-helper.ts +102 -102
  171. package/src/query-builder/relation-types.ts +9 -9
  172. package/src/query-builder/relation-utils.ts +15 -15
  173. package/src/query-builder/select/cte-facet.ts +40 -40
  174. package/src/query-builder/select/from-facet.ts +80 -80
  175. package/src/query-builder/select/join-facet.ts +62 -62
  176. package/src/query-builder/select/predicate-facet.ts +104 -104
  177. package/src/query-builder/select/projection-facet.ts +70 -70
  178. package/src/query-builder/select/relation-facet.ts +81 -81
  179. package/src/query-builder/select/setop-facet.ts +36 -36
  180. package/src/query-builder/select-helpers.ts +1 -1
  181. package/src/query-builder/select-query-builder-deps.ts +130 -130
  182. package/src/query-builder/select-query-state.ts +207 -207
  183. package/src/query-builder/update.ts +151 -151
  184. package/src/schema/column-types.ts +322 -322
  185. package/src/schema/table-guards.ts +37 -37
  186. package/src/schema/table.ts +281 -281
  187. package/src/tree/index.ts +13 -13
  188. package/src/tree/nested-set-strategy.ts +545 -545
  189. package/src/tree/tree-decorator.ts +397 -397
  190. package/src/tree/tree-manager.ts +754 -754
  191. package/src/tree/tree-query.ts +427 -427
  192. package/src/tree/tree-types.ts +299 -299
@@ -1,397 +1,397 @@
1
- /**
2
- * Tree Decorators
3
- *
4
- * Level 3 API: Decorator-based tree configuration for entity classes.
5
- * Integrates with MetalORM's decorator entity system.
6
- */
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
- readMetadataBag,
15
- readMetadataBagFromConstructor,
16
- resolveFieldDecoratorInfo
17
- } from '../decorators/decorator-metadata.js';
18
-
19
- /**
20
- * Symbol key for storing tree metadata on entity classes.
21
- */
22
- const TREE_METADATA_KEY = Symbol('metal-orm:tree');
23
-
24
- /**
25
- * Stored tree metadata for an entity class.
26
- */
27
- export interface TreeMetadata {
28
- config: TreeConfig;
29
- parentProperty?: string;
30
- childrenProperty?: string;
31
- }
32
-
33
- /**
34
- * Options for the @Tree decorator.
35
- */
36
- export interface TreeDecoratorOptions {
37
- /** Column name for parent reference (default: 'parentId') */
38
- parentKey?: string;
39
- /** Column name for left boundary (default: 'lft') */
40
- leftKey?: string;
41
- /** Column name for right boundary (default: 'rght') */
42
- rightKey?: string;
43
- /** Optional column name for cached depth level */
44
- depthKey?: string;
45
- /** Columns used for scoping multiple trees in one table */
46
- scope?: string[];
47
- /** Whether to load and delete children individually to trigger hooks */
48
- cascadeCallbacks?: boolean;
49
- /** Custom ordering for tree recovery */
50
- recoverOrder?: Record<string, 'ASC' | 'DESC'>;
51
- }
52
-
53
- /**
54
- * Decorator to mark an entity class as a tree/hierarchical entity.
55
- *
56
- * @example
57
- * ```ts
58
- * @Entity()
59
- * @Tree({ parentKey: 'parentId', leftKey: 'lft', rightKey: 'rght' })
60
- * class Category {
61
- * @PrimaryKey(col.int())
62
- * id!: number;
63
- *
64
- * @Column(col.varchar(255))
65
- * name!: string;
66
- *
67
- * @Column(col.int().nullable())
68
- * parentId!: number | null;
69
- *
70
- * @Column(col.int())
71
- * lft!: number;
72
- *
73
- * @Column(col.int())
74
- * rght!: number;
75
- *
76
- * @TreeParent()
77
- * parent?: Category;
78
- *
79
- * @TreeChildren()
80
- * children?: Category[];
81
- * }
82
- * ```
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 = 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
- }
105
-
106
- /**
107
- * Decorator to mark a property as the parent relation in a tree entity.
108
- *
109
- * @example
110
- * ```ts
111
- * @TreeParent()
112
- * parent?: Category;
113
- * ```
114
- */
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
- }
125
-
126
- /**
127
- * Decorator to mark a property as the children relation in a tree entity.
128
- *
129
- * @example
130
- * ```ts
131
- * @TreeChildren()
132
- * children?: Category[];
133
- * ```
134
- */
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
- }
177
-
178
- /**
179
- * Gets tree metadata from an entity class.
180
- */
181
- export function getTreeMetadata(
182
- target: new (...args: unknown[]) => object
183
- ): TreeMetadata | undefined {
184
- return (target as unknown as Record<symbol, TreeMetadata>)[TREE_METADATA_KEY];
185
- }
186
-
187
- /**
188
- * Sets tree metadata on an entity class.
189
- */
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
- };
234
-
235
- /**
236
- * Sets tree metadata on an entity class (internal, handles abstract classes).
237
- */
238
- function setTreeMetadataOnClass(
239
- target: abstract new (...args: unknown[]) => object,
240
- metadata: TreeMetadata
241
- ): void {
242
- (target as unknown as Record<symbol, TreeMetadata>)[TREE_METADATA_KEY] = metadata;
243
- }
244
-
245
- /**
246
- * Checks if an entity class has tree behavior configured.
247
- */
248
- export function hasTreeBehavior(
249
- target: new (...args: unknown[]) => object
250
- ): boolean {
251
- return getTreeMetadata(target) !== undefined;
252
- }
253
-
254
- /**
255
- * Gets the tree configuration from an entity class.
256
- */
257
- export function getTreeConfig(
258
- target: new (...args: unknown[]) => object
259
- ): TreeConfig | undefined {
260
- return getTreeMetadata(target)?.config;
261
- }
262
-
263
- /**
264
- * Registry for tree-enabled entity classes.
265
- * Allows looking up tree configuration by table name.
266
- */
267
- class TreeEntityRegistry {
268
- private entities = new Map<string, new (...args: unknown[]) => object>();
269
- private tableNames = new Map<new (...args: unknown[]) => object, string>();
270
-
271
- /**
272
- * Registers an entity class with its table name.
273
- */
274
- register(
275
- entityClass: new (...args: unknown[]) => object,
276
- tableName: string
277
- ): void {
278
- this.entities.set(tableName, entityClass);
279
- this.tableNames.set(entityClass, tableName);
280
- }
281
-
282
- /**
283
- * Gets the entity class for a table name.
284
- */
285
- getByTableName(tableName: string): (new (...args: unknown[]) => object) | undefined {
286
- return this.entities.get(tableName);
287
- }
288
-
289
- /**
290
- * Gets the table name for an entity class.
291
- */
292
- getTableName(entityClass: new (...args: unknown[]) => object): string | undefined {
293
- return this.tableNames.get(entityClass);
294
- }
295
-
296
- /**
297
- * Checks if a table has tree behavior.
298
- */
299
- isTreeTable(tableName: string): boolean {
300
- const entity = this.entities.get(tableName);
301
- return entity ? hasTreeBehavior(entity) : false;
302
- }
303
-
304
- /**
305
- * Gets all registered tree entities.
306
- */
307
- getAll(): Array<{
308
- entityClass: new (...args: unknown[]) => object;
309
- tableName: string;
310
- metadata: TreeMetadata;
311
- }> {
312
- const result: Array<{
313
- entityClass: new (...args: unknown[]) => object;
314
- tableName: string;
315
- metadata: TreeMetadata;
316
- }> = [];
317
-
318
- for (const [tableName, entityClass] of this.entities) {
319
- const metadata = getTreeMetadata(entityClass);
320
- if (metadata) {
321
- result.push({ entityClass, tableName, metadata });
322
- }
323
- }
324
-
325
- return result;
326
- }
327
-
328
- /**
329
- * Clears the registry.
330
- */
331
- clear(): void {
332
- this.entities.clear();
333
- this.tableNames.clear();
334
- }
335
- }
336
-
337
- /**
338
- * Global registry for tree entities.
339
- */
340
- export const treeEntityRegistry = new TreeEntityRegistry();
341
-
342
- /**
343
- * Helper to get tree bounds from an entity instance.
344
- */
345
- export function getTreeBounds(
346
- entity: object,
347
- config: TreeConfig
348
- ): { lft: number; rght: number } | null {
349
- const data = entity as Record<string, unknown>;
350
- const lft = data[config.leftKey];
351
- const rght = data[config.rightKey];
352
-
353
- if (typeof lft !== 'number' || typeof rght !== 'number') {
354
- return null;
355
- }
356
-
357
- return { lft, rght };
358
- }
359
-
360
- /**
361
- * Helper to get parent ID from an entity instance.
362
- */
363
- export function getTreeParentId(
364
- entity: object,
365
- config: TreeConfig
366
- ): unknown {
367
- return (entity as Record<string, unknown>)[config.parentKey];
368
- }
369
-
370
- /**
371
- * Helper to set tree bounds on an entity instance.
372
- */
373
- export function setTreeBounds(
374
- entity: object,
375
- config: TreeConfig,
376
- lft: number,
377
- rght: number,
378
- depth?: number
379
- ): void {
380
- const data = entity as Record<string, unknown>;
381
- data[config.leftKey] = lft;
382
- data[config.rightKey] = rght;
383
- if (config.depthKey && depth !== undefined) {
384
- data[config.depthKey] = depth;
385
- }
386
- }
387
-
388
- /**
389
- * Helper to set parent ID on an entity instance.
390
- */
391
- export function setTreeParentId(
392
- entity: object,
393
- config: TreeConfig,
394
- parentId: unknown
395
- ): void {
396
- (entity as Record<string, unknown>)[config.parentKey] = parentId;
397
- }
1
+ /**
2
+ * Tree Decorators
3
+ *
4
+ * Level 3 API: Decorator-based tree configuration for entity classes.
5
+ * Integrates with MetalORM's decorator entity system.
6
+ */
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
+ readMetadataBag,
15
+ readMetadataBagFromConstructor,
16
+ resolveFieldDecoratorInfo
17
+ } from '../decorators/decorator-metadata.js';
18
+
19
+ /**
20
+ * Symbol key for storing tree metadata on entity classes.
21
+ */
22
+ const TREE_METADATA_KEY = Symbol('metal-orm:tree');
23
+
24
+ /**
25
+ * Stored tree metadata for an entity class.
26
+ */
27
+ export interface TreeMetadata {
28
+ config: TreeConfig;
29
+ parentProperty?: string;
30
+ childrenProperty?: string;
31
+ }
32
+
33
+ /**
34
+ * Options for the @Tree decorator.
35
+ */
36
+ export interface TreeDecoratorOptions {
37
+ /** Column name for parent reference (default: 'parentId') */
38
+ parentKey?: string;
39
+ /** Column name for left boundary (default: 'lft') */
40
+ leftKey?: string;
41
+ /** Column name for right boundary (default: 'rght') */
42
+ rightKey?: string;
43
+ /** Optional column name for cached depth level */
44
+ depthKey?: string;
45
+ /** Columns used for scoping multiple trees in one table */
46
+ scope?: string[];
47
+ /** Whether to load and delete children individually to trigger hooks */
48
+ cascadeCallbacks?: boolean;
49
+ /** Custom ordering for tree recovery */
50
+ recoverOrder?: Record<string, 'ASC' | 'DESC'>;
51
+ }
52
+
53
+ /**
54
+ * Decorator to mark an entity class as a tree/hierarchical entity.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * @Entity()
59
+ * @Tree({ parentKey: 'parentId', leftKey: 'lft', rightKey: 'rght' })
60
+ * class Category {
61
+ * @PrimaryKey(col.int())
62
+ * id!: number;
63
+ *
64
+ * @Column(col.varchar(255))
65
+ * name!: string;
66
+ *
67
+ * @Column(col.int().nullable())
68
+ * parentId!: number | null;
69
+ *
70
+ * @Column(col.int())
71
+ * lft!: number;
72
+ *
73
+ * @Column(col.int())
74
+ * rght!: number;
75
+ *
76
+ * @TreeParent()
77
+ * parent?: Category;
78
+ *
79
+ * @TreeChildren()
80
+ * children?: Category[];
81
+ * }
82
+ * ```
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 = 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
+ }
105
+
106
+ /**
107
+ * Decorator to mark a property as the parent relation in a tree entity.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * @TreeParent()
112
+ * parent?: Category;
113
+ * ```
114
+ */
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
+ }
125
+
126
+ /**
127
+ * Decorator to mark a property as the children relation in a tree entity.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * @TreeChildren()
132
+ * children?: Category[];
133
+ * ```
134
+ */
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
+ }
177
+
178
+ /**
179
+ * Gets tree metadata from an entity class.
180
+ */
181
+ export function getTreeMetadata(
182
+ target: new (...args: unknown[]) => object
183
+ ): TreeMetadata | undefined {
184
+ return (target as unknown as Record<symbol, TreeMetadata>)[TREE_METADATA_KEY];
185
+ }
186
+
187
+ /**
188
+ * Sets tree metadata on an entity class.
189
+ */
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
+ };
234
+
235
+ /**
236
+ * Sets tree metadata on an entity class (internal, handles abstract classes).
237
+ */
238
+ function setTreeMetadataOnClass(
239
+ target: abstract new (...args: unknown[]) => object,
240
+ metadata: TreeMetadata
241
+ ): void {
242
+ (target as unknown as Record<symbol, TreeMetadata>)[TREE_METADATA_KEY] = metadata;
243
+ }
244
+
245
+ /**
246
+ * Checks if an entity class has tree behavior configured.
247
+ */
248
+ export function hasTreeBehavior(
249
+ target: new (...args: unknown[]) => object
250
+ ): boolean {
251
+ return getTreeMetadata(target) !== undefined;
252
+ }
253
+
254
+ /**
255
+ * Gets the tree configuration from an entity class.
256
+ */
257
+ export function getTreeConfig(
258
+ target: new (...args: unknown[]) => object
259
+ ): TreeConfig | undefined {
260
+ return getTreeMetadata(target)?.config;
261
+ }
262
+
263
+ /**
264
+ * Registry for tree-enabled entity classes.
265
+ * Allows looking up tree configuration by table name.
266
+ */
267
+ class TreeEntityRegistry {
268
+ private entities = new Map<string, new (...args: unknown[]) => object>();
269
+ private tableNames = new Map<new (...args: unknown[]) => object, string>();
270
+
271
+ /**
272
+ * Registers an entity class with its table name.
273
+ */
274
+ register(
275
+ entityClass: new (...args: unknown[]) => object,
276
+ tableName: string
277
+ ): void {
278
+ this.entities.set(tableName, entityClass);
279
+ this.tableNames.set(entityClass, tableName);
280
+ }
281
+
282
+ /**
283
+ * Gets the entity class for a table name.
284
+ */
285
+ getByTableName(tableName: string): (new (...args: unknown[]) => object) | undefined {
286
+ return this.entities.get(tableName);
287
+ }
288
+
289
+ /**
290
+ * Gets the table name for an entity class.
291
+ */
292
+ getTableName(entityClass: new (...args: unknown[]) => object): string | undefined {
293
+ return this.tableNames.get(entityClass);
294
+ }
295
+
296
+ /**
297
+ * Checks if a table has tree behavior.
298
+ */
299
+ isTreeTable(tableName: string): boolean {
300
+ const entity = this.entities.get(tableName);
301
+ return entity ? hasTreeBehavior(entity) : false;
302
+ }
303
+
304
+ /**
305
+ * Gets all registered tree entities.
306
+ */
307
+ getAll(): Array<{
308
+ entityClass: new (...args: unknown[]) => object;
309
+ tableName: string;
310
+ metadata: TreeMetadata;
311
+ }> {
312
+ const result: Array<{
313
+ entityClass: new (...args: unknown[]) => object;
314
+ tableName: string;
315
+ metadata: TreeMetadata;
316
+ }> = [];
317
+
318
+ for (const [tableName, entityClass] of this.entities) {
319
+ const metadata = getTreeMetadata(entityClass);
320
+ if (metadata) {
321
+ result.push({ entityClass, tableName, metadata });
322
+ }
323
+ }
324
+
325
+ return result;
326
+ }
327
+
328
+ /**
329
+ * Clears the registry.
330
+ */
331
+ clear(): void {
332
+ this.entities.clear();
333
+ this.tableNames.clear();
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Global registry for tree entities.
339
+ */
340
+ export const treeEntityRegistry = new TreeEntityRegistry();
341
+
342
+ /**
343
+ * Helper to get tree bounds from an entity instance.
344
+ */
345
+ export function getTreeBounds(
346
+ entity: object,
347
+ config: TreeConfig
348
+ ): { lft: number; rght: number } | null {
349
+ const data = entity as Record<string, unknown>;
350
+ const lft = data[config.leftKey];
351
+ const rght = data[config.rightKey];
352
+
353
+ if (typeof lft !== 'number' || typeof rght !== 'number') {
354
+ return null;
355
+ }
356
+
357
+ return { lft, rght };
358
+ }
359
+
360
+ /**
361
+ * Helper to get parent ID from an entity instance.
362
+ */
363
+ export function getTreeParentId(
364
+ entity: object,
365
+ config: TreeConfig
366
+ ): unknown {
367
+ return (entity as Record<string, unknown>)[config.parentKey];
368
+ }
369
+
370
+ /**
371
+ * Helper to set tree bounds on an entity instance.
372
+ */
373
+ export function setTreeBounds(
374
+ entity: object,
375
+ config: TreeConfig,
376
+ lft: number,
377
+ rght: number,
378
+ depth?: number
379
+ ): void {
380
+ const data = entity as Record<string, unknown>;
381
+ data[config.leftKey] = lft;
382
+ data[config.rightKey] = rght;
383
+ if (config.depthKey && depth !== undefined) {
384
+ data[config.depthKey] = depth;
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Helper to set parent ID on an entity instance.
390
+ */
391
+ export function setTreeParentId(
392
+ entity: object,
393
+ config: TreeConfig,
394
+ parentId: unknown
395
+ ): void {
396
+ (entity as Record<string, unknown>)[config.parentKey] = parentId;
397
+ }