metal-orm 1.1.11 → 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.
- package/README.md +758 -769
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +100 -96
- package/scripts/generate-entities/schema.mjs +122 -122
- package/scripts/generate-entities/tree-detection.mjs +92 -92
- package/scripts/inflection/pt-br.mjs +468 -468
- package/scripts/run-eslint.mjs +33 -34
- package/scripts/show-sql-seed.sql +102 -0
- package/scripts/show-sql.mjs +321 -325
- package/src/codegen/naming-strategy.ts +54 -54
- package/src/codegen/typescript.ts +482 -482
- package/src/core/ast/adapters.ts +1 -1
- package/src/core/ast/builders.ts +123 -123
- package/src/core/ast/expression-builders.ts +745 -745
- package/src/core/ast/expression-nodes.ts +304 -304
- package/src/core/ast/expression-visitor.ts +212 -212
- package/src/core/ast/expression.ts +20 -20
- package/src/core/ast/helpers.ts +14 -14
- package/src/core/ast/join.ts +18 -18
- package/src/core/ast/query.ts +200 -200
- package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/render-reference.test.ts +70 -70
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
- package/src/core/ddl/introspect/catalogs/index.ts +5 -5
- package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
- package/src/core/ddl/introspect/context.ts +15 -15
- package/src/core/ddl/introspect/functions/postgres.ts +35 -35
- package/src/core/ddl/introspect/registry.ts +40 -40
- package/src/core/ddl/introspect/run-select.ts +35 -35
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/ddl/naming-strategy.ts +16 -16
- package/src/core/ddl/schema-dialect.ts +56 -56
- package/src/core/ddl/schema-diff.ts +234 -234
- package/src/core/ddl/schema-generator.ts +1 -1
- package/src/core/ddl/schema-introspect.ts +25 -25
- package/src/core/ddl/sql-writing.ts +171 -171
- package/src/core/dialect/abstract.ts +541 -541
- package/src/core/dialect/base/cte-compiler.ts +33 -33
- package/src/core/dialect/base/function-table-formatter.ts +103 -103
- package/src/core/dialect/base/groupby-compiler.ts +21 -21
- package/src/core/dialect/base/join-compiler.ts +25 -25
- package/src/core/dialect/base/orderby-compiler.ts +35 -35
- package/src/core/dialect/base/pagination-strategy.ts +32 -32
- package/src/core/dialect/base/returning-strategy.ts +57 -57
- package/src/core/dialect/base/sql-dialect.ts +294 -294
- package/src/core/dialect/dialect-factory.ts +91 -91
- package/src/core/dialect/mssql/functions.ts +120 -120
- package/src/core/dialect/mssql/index.ts +317 -317
- package/src/core/dialect/mysql/functions.ts +103 -103
- package/src/core/dialect/mysql/index.ts +105 -105
- package/src/core/dialect/postgres/functions.ts +108 -108
- package/src/core/dialect/postgres/index.ts +91 -91
- package/src/core/dialect/sqlite/functions.ts +129 -129
- package/src/core/dialect/sqlite/index.ts +67 -67
- package/src/core/driver/database-driver.ts +19 -19
- package/src/core/driver/mssql-driver.ts +23 -23
- package/src/core/driver/mysql-driver.ts +23 -23
- package/src/core/driver/postgres-driver.ts +23 -23
- package/src/core/driver/sqlite-driver.ts +23 -23
- package/src/core/execution/db-executor.ts +92 -92
- package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
- package/src/core/execution/executors/mssql-executor.ts +184 -184
- package/src/core/execution/executors/mysql-executor.ts +88 -88
- package/src/core/execution/executors/postgres-executor.ts +26 -26
- package/src/core/execution/executors/sqlite-executor.ts +42 -42
- package/src/core/execution/pooling/pool-types.ts +30 -30
- package/src/core/execution/pooling/pool.ts +275 -275
- package/src/core/functions/array.ts +35 -35
- package/src/core/functions/control-flow.ts +79 -79
- package/src/core/functions/datetime.ts +237 -237
- package/src/core/functions/json.ts +70 -70
- package/src/core/functions/numeric.ts +297 -297
- package/src/core/functions/text.ts +334 -334
- package/src/core/functions/types.ts +33 -33
- package/src/core/hydration/types.ts +51 -51
- package/src/core/sql/sql-operator-config.ts +39 -39
- package/src/core/sql/sql.ts +152 -152
- package/src/decorators/column-decorator.ts +87 -87
- package/src/decorators/decorator-metadata.ts +143 -143
- package/src/decorators/entity.ts +93 -93
- package/src/decorators/index.ts +39 -39
- package/src/decorators/relations.ts +231 -231
- package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
- package/src/decorators/transformers/transformer-decorators.ts +134 -134
- package/src/decorators/transformers/transformer-executor.ts +171 -171
- package/src/decorators/transformers/transformer-metadata.ts +73 -73
- package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
- package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
- package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
- package/src/decorators/validators/country-validator-registry.ts +64 -64
- package/src/decorators/validators/country-validators-decorators.ts +203 -203
- package/src/decorators/validators/country-validators.ts +105 -105
- package/src/decorators/validators/validator-metadata.ts +59 -59
- package/src/dto/dto-types.ts +226 -226
- package/src/dto/filter-types.ts +141 -141
- package/src/dto/index.ts +95 -95
- package/src/dto/openapi/generators/base.ts +29 -29
- package/src/dto/openapi/generators/column.ts +37 -37
- package/src/dto/openapi/generators/dto.ts +91 -91
- package/src/dto/openapi/generators/filter.ts +75 -75
- package/src/dto/openapi/generators/nested-dto.ts +618 -618
- package/src/dto/openapi/generators/pagination.ts +111 -111
- package/src/dto/openapi/generators/relation-filter.ts +228 -228
- package/src/dto/openapi/index.ts +12 -12
- package/src/dto/openapi/types.ts +101 -101
- package/src/dto/openapi/utilities.ts +95 -95
- package/src/dto/pagination-utils.ts +150 -150
- package/src/dto/transform.ts +197 -197
- package/src/index.ts +86 -86
- package/src/orm/als.ts +58 -58
- package/src/orm/domain-event-bus.ts +119 -119
- package/src/orm/entity-context.ts +86 -86
- package/src/orm/entity-materializer.ts +159 -159
- package/src/orm/entity-meta.ts +97 -97
- package/src/orm/entity-registry.ts +39 -39
- package/src/orm/entity-relation-cache.ts +39 -39
- package/src/orm/execute.ts +194 -194
- package/src/orm/execution-context.ts +18 -18
- package/src/orm/hydration-context.ts +26 -26
- package/src/orm/hydration.ts +112 -112
- package/src/orm/identity-map.ts +61 -61
- package/src/orm/interceptor-pipeline.ts +16 -16
- package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
- package/src/orm/lazy-batch/belongs-to.ts +108 -108
- package/src/orm/lazy-batch/has-many.ts +69 -69
- package/src/orm/lazy-batch/has-one.ts +68 -68
- package/src/orm/lazy-batch/shared.ts +125 -125
- package/src/orm/orm-session.ts +620 -620
- package/src/orm/orm.ts +34 -34
- package/src/orm/pooled-executor-factory.ts +121 -121
- package/src/orm/query-logger.ts +40 -40
- package/src/orm/relation-change-processor.ts +420 -420
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -156
- package/src/orm/relations/morph-one.ts +151 -151
- package/src/orm/relations/morph-to.ts +162 -162
- package/src/orm/runtime-types.ts +92 -92
- package/src/orm/save-graph.ts +561 -561
- package/src/orm/transaction-runner.ts +24 -24
- package/src/orm/unit-of-work.ts +426 -426
- package/src/query/index.ts +69 -69
- package/src/query/target.ts +46 -46
- package/src/query-builder/column-selector.ts +81 -81
- package/src/query-builder/delete.ts +141 -141
- package/src/query-builder/insert-query-state.ts +149 -149
- package/src/query-builder/insert.ts +117 -117
- package/src/query-builder/query-ast-service.ts +287 -287
- package/src/query-builder/query-resolution.ts +78 -78
- package/src/query-builder/raw-column-parser.ts +38 -38
- package/src/query-builder/relation-filter-utils.ts +153 -153
- package/src/query-builder/relation-join-planner.ts +10 -10
- package/src/query-builder/relation-manager.ts +87 -87
- package/src/query-builder/relation-projection-helper.ts +102 -102
- package/src/query-builder/relation-types.ts +9 -9
- package/src/query-builder/relation-utils.ts +15 -15
- package/src/query-builder/select/cte-facet.ts +40 -40
- package/src/query-builder/select/from-facet.ts +80 -80
- package/src/query-builder/select/join-facet.ts +62 -62
- package/src/query-builder/select/predicate-facet.ts +104 -104
- package/src/query-builder/select/projection-facet.ts +70 -70
- package/src/query-builder/select/relation-facet.ts +81 -81
- package/src/query-builder/select/setop-facet.ts +36 -36
- package/src/query-builder/select-helpers.ts +1 -1
- package/src/query-builder/select-query-builder-deps.ts +130 -130
- package/src/query-builder/select-query-state.ts +207 -207
- package/src/query-builder/update.ts +151 -151
- package/src/schema/column-types.ts +322 -322
- package/src/schema/table-guards.ts +37 -37
- package/src/schema/table.ts +281 -281
- package/src/tree/index.ts +13 -13
- package/src/tree/nested-set-strategy.ts +545 -545
- package/src/tree/tree-decorator.ts +397 -397
- package/src/tree/tree-manager.ts +754 -754
- package/src/tree/tree-query.ts +427 -427
- 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
|
+
}
|