metal-orm 1.1.11 → 1.1.18
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
package/src/tree/tree-types.ts
CHANGED
|
@@ -1,299 +1,299 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tree Behavior Types
|
|
3
|
-
*
|
|
4
|
-
* Core type definitions for the Nested Set (MPTT) tree implementation.
|
|
5
|
-
* These types support hierarchical data structures in relational databases.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { TableDef } from '../schema/table.js';
|
|
9
|
-
import type { ColumnDef } from '../schema/column-types.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Configuration options for tree behavior.
|
|
13
|
-
*/
|
|
14
|
-
export interface TreeConfig {
|
|
15
|
-
/** Column name for parent reference (default: 'parentId') */
|
|
16
|
-
parentKey: string;
|
|
17
|
-
/** Column name for left boundary (default: 'lft') */
|
|
18
|
-
leftKey: string;
|
|
19
|
-
/** Column name for right boundary (default: 'rght') */
|
|
20
|
-
rightKey: string;
|
|
21
|
-
/** Optional column name for cached depth level */
|
|
22
|
-
depthKey?: string;
|
|
23
|
-
/** Columns used for scoping multiple trees in one table (e.g., ['tenantId']) */
|
|
24
|
-
scope?: string[];
|
|
25
|
-
/** Whether to load and delete children individually to trigger hooks (default: false) */
|
|
26
|
-
cascadeCallbacks?: boolean;
|
|
27
|
-
/** Custom ordering for tree recovery (default: primary key ASC) */
|
|
28
|
-
recoverOrder?: Record<string, 'ASC' | 'DESC'>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Default tree configuration values.
|
|
33
|
-
*/
|
|
34
|
-
export const DEFAULT_TREE_CONFIG: Readonly<Required<Omit<TreeConfig, 'depthKey' | 'scope' | 'recoverOrder'>>> = {
|
|
35
|
-
parentKey: 'parentId',
|
|
36
|
-
leftKey: 'lft',
|
|
37
|
-
rightKey: 'rght',
|
|
38
|
-
cascadeCallbacks: false,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Represents a node in the tree with its nested set boundaries.
|
|
43
|
-
* @typeParam T - The entity type
|
|
44
|
-
*/
|
|
45
|
-
export interface TreeNode<T> {
|
|
46
|
-
/** The underlying entity */
|
|
47
|
-
entity: T;
|
|
48
|
-
/** Left boundary value */
|
|
49
|
-
lft: number;
|
|
50
|
-
/** Right boundary value */
|
|
51
|
-
rght: number;
|
|
52
|
-
/** Depth level (0 = root), if available */
|
|
53
|
-
depth?: number;
|
|
54
|
-
/** Whether this node has no children */
|
|
55
|
-
isLeaf: boolean;
|
|
56
|
-
/** Whether this node has no parent */
|
|
57
|
-
isRoot: boolean;
|
|
58
|
-
/** Number of descendants (calculated from lft/rght) */
|
|
59
|
-
childCount: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Options for finding children/descendants.
|
|
64
|
-
*/
|
|
65
|
-
export interface FindChildrenOptions {
|
|
66
|
-
/** Only return immediate children (default: false = all descendants) */
|
|
67
|
-
direct?: boolean;
|
|
68
|
-
/** Nest results into a tree structure with .children property */
|
|
69
|
-
threaded?: boolean;
|
|
70
|
-
/** Maximum depth to fetch (undefined = unlimited) */
|
|
71
|
-
depth?: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Options for generating a tree list (for dropdowns/selects).
|
|
76
|
-
*/
|
|
77
|
-
export interface TreeListOptions {
|
|
78
|
-
/** Dot-separated path to the field used as key (default: primary key) */
|
|
79
|
-
keyPath?: string;
|
|
80
|
-
/** Dot-separated path to the field used as value (default: primary key) */
|
|
81
|
-
valuePath?: string;
|
|
82
|
-
/** Prefix string repeated per depth level (default: '__') */
|
|
83
|
-
spacer?: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Options for finding the path from a node to root.
|
|
88
|
-
*/
|
|
89
|
-
export interface FindPathOptions {
|
|
90
|
-
/** Include the node itself in the path (default: true) */
|
|
91
|
-
includeSelf?: boolean;
|
|
92
|
-
/** Order of results: 'asc' = root first, 'desc' = node first (default: 'asc') */
|
|
93
|
-
order?: 'asc' | 'desc';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Options for moving a node within the tree.
|
|
98
|
-
*/
|
|
99
|
-
export interface MoveOptions {
|
|
100
|
-
/** Position relative to target: 'before', 'after', 'firstChild', 'lastChild' */
|
|
101
|
-
position?: 'before' | 'after' | 'firstChild' | 'lastChild';
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Result of a tree recovery operation.
|
|
106
|
-
*/
|
|
107
|
-
export interface RecoverResult {
|
|
108
|
-
/** Number of nodes processed */
|
|
109
|
-
processed: number;
|
|
110
|
-
/** Whether the recovery was successful */
|
|
111
|
-
success: boolean;
|
|
112
|
-
/** Any errors encountered */
|
|
113
|
-
errors?: string[];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Scope values for multi-tree tables.
|
|
118
|
-
*/
|
|
119
|
-
export type TreeScope = Record<string, unknown>;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Internal representation of nested set boundaries for calculations.
|
|
123
|
-
*/
|
|
124
|
-
export interface NestedSetBounds {
|
|
125
|
-
lft: number;
|
|
126
|
-
rght: number;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Data required to insert a new node into the tree.
|
|
131
|
-
*/
|
|
132
|
-
export interface TreeInsertData {
|
|
133
|
-
/** Parent node ID (null for root) */
|
|
134
|
-
parentId: unknown;
|
|
135
|
-
/** Calculated left boundary */
|
|
136
|
-
lft: number;
|
|
137
|
-
/** Calculated right boundary */
|
|
138
|
-
rght: number;
|
|
139
|
-
/** Calculated depth (if depthKey is configured) */
|
|
140
|
-
depth?: number;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Data required to move a node within the tree.
|
|
145
|
-
*/
|
|
146
|
-
export interface TreeMoveData {
|
|
147
|
-
/** New parent node ID */
|
|
148
|
-
newParentId: unknown;
|
|
149
|
-
/** New left boundary */
|
|
150
|
-
newLft: number;
|
|
151
|
-
/** New right boundary */
|
|
152
|
-
newRght: number;
|
|
153
|
-
/** New depth (if depthKey is configured) */
|
|
154
|
-
newDepth?: number;
|
|
155
|
-
/** Delta to apply to lft/rght of affected nodes */
|
|
156
|
-
delta: number;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Represents a node with its children in a threaded tree structure.
|
|
161
|
-
* @typeParam T - The entity type
|
|
162
|
-
*/
|
|
163
|
-
export interface ThreadedNode<T> {
|
|
164
|
-
/** The node data */
|
|
165
|
-
node: T;
|
|
166
|
-
/** Child nodes */
|
|
167
|
-
children: ThreadedNode<T>[];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* A tree list entry for dropdown/select rendering.
|
|
172
|
-
*/
|
|
173
|
-
export interface TreeListEntry<K = unknown, V = string> {
|
|
174
|
-
/** The key (usually primary key) */
|
|
175
|
-
key: K;
|
|
176
|
-
/** The display value with depth prefix */
|
|
177
|
-
value: V;
|
|
178
|
-
/** The depth level */
|
|
179
|
-
depth: number;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Validates that a table has the required tree columns.
|
|
184
|
-
* @param table - The table definition to validate
|
|
185
|
-
* @param config - The tree configuration
|
|
186
|
-
* @returns Validation result with any missing columns
|
|
187
|
-
*/
|
|
188
|
-
export interface TreeValidationResult {
|
|
189
|
-
valid: boolean;
|
|
190
|
-
missingColumns: string[];
|
|
191
|
-
warnings: string[];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Extracts tree column definitions from a table.
|
|
196
|
-
*/
|
|
197
|
-
export interface TreeColumns {
|
|
198
|
-
parentColumn: ColumnDef;
|
|
199
|
-
leftColumn: ColumnDef;
|
|
200
|
-
rightColumn: ColumnDef;
|
|
201
|
-
depthColumn?: ColumnDef;
|
|
202
|
-
scopeColumns: ColumnDef[];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Type guard to check if a value is a valid TreeConfig.
|
|
207
|
-
*/
|
|
208
|
-
export function isTreeConfig(value: unknown): value is TreeConfig {
|
|
209
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
210
|
-
const obj = value as Record<string, unknown>;
|
|
211
|
-
return (
|
|
212
|
-
typeof obj.parentKey === 'string' &&
|
|
213
|
-
typeof obj.leftKey === 'string' &&
|
|
214
|
-
typeof obj.rightKey === 'string'
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Merges partial config with defaults.
|
|
220
|
-
*/
|
|
221
|
-
export function resolveTreeConfig(partial: Partial<TreeConfig>): TreeConfig {
|
|
222
|
-
return {
|
|
223
|
-
parentKey: partial.parentKey ?? DEFAULT_TREE_CONFIG.parentKey,
|
|
224
|
-
leftKey: partial.leftKey ?? DEFAULT_TREE_CONFIG.leftKey,
|
|
225
|
-
rightKey: partial.rightKey ?? DEFAULT_TREE_CONFIG.rightKey,
|
|
226
|
-
depthKey: partial.depthKey,
|
|
227
|
-
scope: partial.scope,
|
|
228
|
-
cascadeCallbacks: partial.cascadeCallbacks ?? DEFAULT_TREE_CONFIG.cascadeCallbacks,
|
|
229
|
-
recoverOrder: partial.recoverOrder,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Validates that a table definition contains the required tree columns.
|
|
235
|
-
*/
|
|
236
|
-
export function validateTreeTable(table: TableDef, config: TreeConfig): TreeValidationResult {
|
|
237
|
-
const missingColumns: string[] = [];
|
|
238
|
-
const warnings: string[] = [];
|
|
239
|
-
|
|
240
|
-
const requiredColumns = [config.parentKey, config.leftKey, config.rightKey];
|
|
241
|
-
for (const col of requiredColumns) {
|
|
242
|
-
if (!(col in table.columns)) {
|
|
243
|
-
missingColumns.push(col);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (config.depthKey && !(config.depthKey in table.columns)) {
|
|
248
|
-
warnings.push(`Optional depth column '${config.depthKey}' not found in table '${table.name}'`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (config.scope) {
|
|
252
|
-
for (const scopeCol of config.scope) {
|
|
253
|
-
if (!(scopeCol in table.columns)) {
|
|
254
|
-
missingColumns.push(scopeCol);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const leftCol = table.columns[config.leftKey];
|
|
260
|
-
const rightCol = table.columns[config.rightKey];
|
|
261
|
-
if (leftCol && leftCol.type !== 'int' && leftCol.type !== 'integer') {
|
|
262
|
-
warnings.push(`Left column '${config.leftKey}' should be an integer type`);
|
|
263
|
-
}
|
|
264
|
-
if (rightCol && rightCol.type !== 'int' && rightCol.type !== 'integer') {
|
|
265
|
-
warnings.push(`Right column '${config.rightKey}' should be an integer type`);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
valid: missingColumns.length === 0,
|
|
270
|
-
missingColumns,
|
|
271
|
-
warnings,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Extracts tree-related columns from a table definition.
|
|
277
|
-
*/
|
|
278
|
-
export function getTreeColumns(table: TableDef, config: TreeConfig): TreeColumns {
|
|
279
|
-
const parentColumn = table.columns[config.parentKey];
|
|
280
|
-
const leftColumn = table.columns[config.leftKey];
|
|
281
|
-
const rightColumn = table.columns[config.rightKey];
|
|
282
|
-
const depthColumn = config.depthKey ? table.columns[config.depthKey] : undefined;
|
|
283
|
-
const scopeColumns = (config.scope ?? []).map(s => table.columns[s]).filter(Boolean);
|
|
284
|
-
|
|
285
|
-
if (!parentColumn || !leftColumn || !rightColumn) {
|
|
286
|
-
throw new Error(
|
|
287
|
-
`Table '${table.name}' is missing required tree columns. ` +
|
|
288
|
-
`Required: ${config.parentKey}, ${config.leftKey}, ${config.rightKey}`
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
parentColumn,
|
|
294
|
-
leftColumn,
|
|
295
|
-
rightColumn,
|
|
296
|
-
depthColumn,
|
|
297
|
-
scopeColumns,
|
|
298
|
-
};
|
|
299
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tree Behavior Types
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the Nested Set (MPTT) tree implementation.
|
|
5
|
+
* These types support hierarchical data structures in relational databases.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TableDef } from '../schema/table.js';
|
|
9
|
+
import type { ColumnDef } from '../schema/column-types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for tree behavior.
|
|
13
|
+
*/
|
|
14
|
+
export interface TreeConfig {
|
|
15
|
+
/** Column name for parent reference (default: 'parentId') */
|
|
16
|
+
parentKey: string;
|
|
17
|
+
/** Column name for left boundary (default: 'lft') */
|
|
18
|
+
leftKey: string;
|
|
19
|
+
/** Column name for right boundary (default: 'rght') */
|
|
20
|
+
rightKey: string;
|
|
21
|
+
/** Optional column name for cached depth level */
|
|
22
|
+
depthKey?: string;
|
|
23
|
+
/** Columns used for scoping multiple trees in one table (e.g., ['tenantId']) */
|
|
24
|
+
scope?: string[];
|
|
25
|
+
/** Whether to load and delete children individually to trigger hooks (default: false) */
|
|
26
|
+
cascadeCallbacks?: boolean;
|
|
27
|
+
/** Custom ordering for tree recovery (default: primary key ASC) */
|
|
28
|
+
recoverOrder?: Record<string, 'ASC' | 'DESC'>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Default tree configuration values.
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_TREE_CONFIG: Readonly<Required<Omit<TreeConfig, 'depthKey' | 'scope' | 'recoverOrder'>>> = {
|
|
35
|
+
parentKey: 'parentId',
|
|
36
|
+
leftKey: 'lft',
|
|
37
|
+
rightKey: 'rght',
|
|
38
|
+
cascadeCallbacks: false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents a node in the tree with its nested set boundaries.
|
|
43
|
+
* @typeParam T - The entity type
|
|
44
|
+
*/
|
|
45
|
+
export interface TreeNode<T> {
|
|
46
|
+
/** The underlying entity */
|
|
47
|
+
entity: T;
|
|
48
|
+
/** Left boundary value */
|
|
49
|
+
lft: number;
|
|
50
|
+
/** Right boundary value */
|
|
51
|
+
rght: number;
|
|
52
|
+
/** Depth level (0 = root), if available */
|
|
53
|
+
depth?: number;
|
|
54
|
+
/** Whether this node has no children */
|
|
55
|
+
isLeaf: boolean;
|
|
56
|
+
/** Whether this node has no parent */
|
|
57
|
+
isRoot: boolean;
|
|
58
|
+
/** Number of descendants (calculated from lft/rght) */
|
|
59
|
+
childCount: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Options for finding children/descendants.
|
|
64
|
+
*/
|
|
65
|
+
export interface FindChildrenOptions {
|
|
66
|
+
/** Only return immediate children (default: false = all descendants) */
|
|
67
|
+
direct?: boolean;
|
|
68
|
+
/** Nest results into a tree structure with .children property */
|
|
69
|
+
threaded?: boolean;
|
|
70
|
+
/** Maximum depth to fetch (undefined = unlimited) */
|
|
71
|
+
depth?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Options for generating a tree list (for dropdowns/selects).
|
|
76
|
+
*/
|
|
77
|
+
export interface TreeListOptions {
|
|
78
|
+
/** Dot-separated path to the field used as key (default: primary key) */
|
|
79
|
+
keyPath?: string;
|
|
80
|
+
/** Dot-separated path to the field used as value (default: primary key) */
|
|
81
|
+
valuePath?: string;
|
|
82
|
+
/** Prefix string repeated per depth level (default: '__') */
|
|
83
|
+
spacer?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Options for finding the path from a node to root.
|
|
88
|
+
*/
|
|
89
|
+
export interface FindPathOptions {
|
|
90
|
+
/** Include the node itself in the path (default: true) */
|
|
91
|
+
includeSelf?: boolean;
|
|
92
|
+
/** Order of results: 'asc' = root first, 'desc' = node first (default: 'asc') */
|
|
93
|
+
order?: 'asc' | 'desc';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Options for moving a node within the tree.
|
|
98
|
+
*/
|
|
99
|
+
export interface MoveOptions {
|
|
100
|
+
/** Position relative to target: 'before', 'after', 'firstChild', 'lastChild' */
|
|
101
|
+
position?: 'before' | 'after' | 'firstChild' | 'lastChild';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Result of a tree recovery operation.
|
|
106
|
+
*/
|
|
107
|
+
export interface RecoverResult {
|
|
108
|
+
/** Number of nodes processed */
|
|
109
|
+
processed: number;
|
|
110
|
+
/** Whether the recovery was successful */
|
|
111
|
+
success: boolean;
|
|
112
|
+
/** Any errors encountered */
|
|
113
|
+
errors?: string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Scope values for multi-tree tables.
|
|
118
|
+
*/
|
|
119
|
+
export type TreeScope = Record<string, unknown>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Internal representation of nested set boundaries for calculations.
|
|
123
|
+
*/
|
|
124
|
+
export interface NestedSetBounds {
|
|
125
|
+
lft: number;
|
|
126
|
+
rght: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Data required to insert a new node into the tree.
|
|
131
|
+
*/
|
|
132
|
+
export interface TreeInsertData {
|
|
133
|
+
/** Parent node ID (null for root) */
|
|
134
|
+
parentId: unknown;
|
|
135
|
+
/** Calculated left boundary */
|
|
136
|
+
lft: number;
|
|
137
|
+
/** Calculated right boundary */
|
|
138
|
+
rght: number;
|
|
139
|
+
/** Calculated depth (if depthKey is configured) */
|
|
140
|
+
depth?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Data required to move a node within the tree.
|
|
145
|
+
*/
|
|
146
|
+
export interface TreeMoveData {
|
|
147
|
+
/** New parent node ID */
|
|
148
|
+
newParentId: unknown;
|
|
149
|
+
/** New left boundary */
|
|
150
|
+
newLft: number;
|
|
151
|
+
/** New right boundary */
|
|
152
|
+
newRght: number;
|
|
153
|
+
/** New depth (if depthKey is configured) */
|
|
154
|
+
newDepth?: number;
|
|
155
|
+
/** Delta to apply to lft/rght of affected nodes */
|
|
156
|
+
delta: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Represents a node with its children in a threaded tree structure.
|
|
161
|
+
* @typeParam T - The entity type
|
|
162
|
+
*/
|
|
163
|
+
export interface ThreadedNode<T> {
|
|
164
|
+
/** The node data */
|
|
165
|
+
node: T;
|
|
166
|
+
/** Child nodes */
|
|
167
|
+
children: ThreadedNode<T>[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A tree list entry for dropdown/select rendering.
|
|
172
|
+
*/
|
|
173
|
+
export interface TreeListEntry<K = unknown, V = string> {
|
|
174
|
+
/** The key (usually primary key) */
|
|
175
|
+
key: K;
|
|
176
|
+
/** The display value with depth prefix */
|
|
177
|
+
value: V;
|
|
178
|
+
/** The depth level */
|
|
179
|
+
depth: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validates that a table has the required tree columns.
|
|
184
|
+
* @param table - The table definition to validate
|
|
185
|
+
* @param config - The tree configuration
|
|
186
|
+
* @returns Validation result with any missing columns
|
|
187
|
+
*/
|
|
188
|
+
export interface TreeValidationResult {
|
|
189
|
+
valid: boolean;
|
|
190
|
+
missingColumns: string[];
|
|
191
|
+
warnings: string[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Extracts tree column definitions from a table.
|
|
196
|
+
*/
|
|
197
|
+
export interface TreeColumns {
|
|
198
|
+
parentColumn: ColumnDef;
|
|
199
|
+
leftColumn: ColumnDef;
|
|
200
|
+
rightColumn: ColumnDef;
|
|
201
|
+
depthColumn?: ColumnDef;
|
|
202
|
+
scopeColumns: ColumnDef[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Type guard to check if a value is a valid TreeConfig.
|
|
207
|
+
*/
|
|
208
|
+
export function isTreeConfig(value: unknown): value is TreeConfig {
|
|
209
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
210
|
+
const obj = value as Record<string, unknown>;
|
|
211
|
+
return (
|
|
212
|
+
typeof obj.parentKey === 'string' &&
|
|
213
|
+
typeof obj.leftKey === 'string' &&
|
|
214
|
+
typeof obj.rightKey === 'string'
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Merges partial config with defaults.
|
|
220
|
+
*/
|
|
221
|
+
export function resolveTreeConfig(partial: Partial<TreeConfig>): TreeConfig {
|
|
222
|
+
return {
|
|
223
|
+
parentKey: partial.parentKey ?? DEFAULT_TREE_CONFIG.parentKey,
|
|
224
|
+
leftKey: partial.leftKey ?? DEFAULT_TREE_CONFIG.leftKey,
|
|
225
|
+
rightKey: partial.rightKey ?? DEFAULT_TREE_CONFIG.rightKey,
|
|
226
|
+
depthKey: partial.depthKey,
|
|
227
|
+
scope: partial.scope,
|
|
228
|
+
cascadeCallbacks: partial.cascadeCallbacks ?? DEFAULT_TREE_CONFIG.cascadeCallbacks,
|
|
229
|
+
recoverOrder: partial.recoverOrder,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Validates that a table definition contains the required tree columns.
|
|
235
|
+
*/
|
|
236
|
+
export function validateTreeTable(table: TableDef, config: TreeConfig): TreeValidationResult {
|
|
237
|
+
const missingColumns: string[] = [];
|
|
238
|
+
const warnings: string[] = [];
|
|
239
|
+
|
|
240
|
+
const requiredColumns = [config.parentKey, config.leftKey, config.rightKey];
|
|
241
|
+
for (const col of requiredColumns) {
|
|
242
|
+
if (!(col in table.columns)) {
|
|
243
|
+
missingColumns.push(col);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (config.depthKey && !(config.depthKey in table.columns)) {
|
|
248
|
+
warnings.push(`Optional depth column '${config.depthKey}' not found in table '${table.name}'`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (config.scope) {
|
|
252
|
+
for (const scopeCol of config.scope) {
|
|
253
|
+
if (!(scopeCol in table.columns)) {
|
|
254
|
+
missingColumns.push(scopeCol);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const leftCol = table.columns[config.leftKey];
|
|
260
|
+
const rightCol = table.columns[config.rightKey];
|
|
261
|
+
if (leftCol && leftCol.type !== 'int' && leftCol.type !== 'integer') {
|
|
262
|
+
warnings.push(`Left column '${config.leftKey}' should be an integer type`);
|
|
263
|
+
}
|
|
264
|
+
if (rightCol && rightCol.type !== 'int' && rightCol.type !== 'integer') {
|
|
265
|
+
warnings.push(`Right column '${config.rightKey}' should be an integer type`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
valid: missingColumns.length === 0,
|
|
270
|
+
missingColumns,
|
|
271
|
+
warnings,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Extracts tree-related columns from a table definition.
|
|
277
|
+
*/
|
|
278
|
+
export function getTreeColumns(table: TableDef, config: TreeConfig): TreeColumns {
|
|
279
|
+
const parentColumn = table.columns[config.parentKey];
|
|
280
|
+
const leftColumn = table.columns[config.leftKey];
|
|
281
|
+
const rightColumn = table.columns[config.rightKey];
|
|
282
|
+
const depthColumn = config.depthKey ? table.columns[config.depthKey] : undefined;
|
|
283
|
+
const scopeColumns = (config.scope ?? []).map(s => table.columns[s]).filter(Boolean);
|
|
284
|
+
|
|
285
|
+
if (!parentColumn || !leftColumn || !rightColumn) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Table '${table.name}' is missing required tree columns. ` +
|
|
288
|
+
`Required: ${config.parentKey}, ${config.leftKey}, ${config.rightKey}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
parentColumn,
|
|
294
|
+
leftColumn,
|
|
295
|
+
rightColumn,
|
|
296
|
+
depthColumn,
|
|
297
|
+
scopeColumns,
|
|
298
|
+
};
|
|
299
|
+
}
|